/** * Footprint escape detection — the back-side safety net of * PARALLEL-WORK-COORDINATION (change: add-footprint-escape-detection, proposal 3). * * Proposals 1 and 2 plan a swarm from *predicted* (declared) write-footprints. * That prediction is advisory by construction: an agent can edit outside its * declared region. This module is the after-the-fact check that compares a task's * *declared* write-footprint against the symbols its diff *actually* modified, and * recomputes the peer conflicts the escape newly opens. It is the missing half of a * borrow checker that can only advise: OpenLore cannot reject a write, but it can * detect the escape and hand the verdict to whoever can act on it. * * Design invariants (mirror the proposal's Decision + Scope contract): * - **Pure and deterministic.** The escape set, the newly-opened conflicts, and the * registry resolutions are a deterministic function of (actual modified symbols, * declared footprint, supplied peer footprints). No persistence, no roster, no * clock, no LLM. * - **Stateless.** OpenLore holds no roster of agents/tasks/in-flight footprints. * The declared footprint and peer footprints are per-call *inputs* supplied by the * caller; nothing survives the call. * - **Dormant by default.** With no declared footprint supplied, the feature does * nothing — `structural_diff` is byte-identical to today (enforced upstream). * - **Advisory, not a block.** Escapes become {@link import('./enforcement-policy.js').GovernanceFinding}s; * gating is opt-in via `enforcement.policy`; the harness enforces. * - **Honest about reach.** Detection is structural. An escape that creates only a * *semantic* (non-call, non-write) conflict can still slip through; the disclosure * says so. */ import type { GovernanceFinding } from './enforcement-policy.js'; import type { WriteMode } from './change-footprint.js'; /** * How a symbol's source actually changed in the diff. Computed by * `structural_diff` (which holds the old/new source); this module consumes it. * * - `added` — a brand-new symbol (no base version). A pure addition by nature. * - `removed` — a symbol deleted by the diff. * - `pure-addition` — an existing symbol whose body only *gained* lines (every base * line preserved in order). A new switch case, a new registry element — the edit a * git 3-way-merge resolves trivially. * - `modifies-existing` — an existing symbol where at least one base line was changed * or removed. The edit that genuinely clobbers shared code. */ export type EditNature = 'added' | 'removed' | 'pure-addition' | 'modifies-existing'; /** A symbol the diff actually modified, with the nature of the edit. */ export interface ModifiedSymbol { /** Path-based node id (`file::name`) — the same id space declared footprints use. */ id: string; name: string; filePath: string; editNature: EditNature; } /** * A declared footprint as supplied by the caller. A structural subset of proposal * 1's `Footprint` (caller may pass the full object; only these fields are read). * Validated/normalized at the boundary by {@link normalizeDeclaredFootprint}. */ export interface DeclaredFootprintInput { taskId?: string; writeSet?: Array<{ id?: unknown; filePath?: unknown; writeMode?: unknown; }>; /** Symbol ids the task declared it would only *read*. */ readSet?: unknown[]; } /** A normalized declared footprint: clean id sets, ready for set algebra. */ export interface NormalizedFootprint { taskId: string; /** Declared write ids → declared write mode. */ writeModeById: Map; /** Files that contain at least one declared write symbol. */ writeFiles: Set; /** Declared read-only ids. */ readIds: Set; } /** How a modified symbol escaped its declared write-footprint. */ export type EscapeClass = /** Modified a symbol absent from the declared write-set, in a file never declared. */ 'out-of-scope-write' /** Modified a symbol that appeared only in the declared *read*-set. */ | 'read-set-intrusion' /** Added/modified a symbol in a declared *file* but not the declared write-set (lower severity). */ | 'scope-creep-within-file'; export interface EscapeItem { id: string; name: string; filePath: string; classification: EscapeClass; editNature: EditNature; } /** The verdict of comparing the actual diff against a peer's declared write on a shared symbol. */ export type ContentionVerdict = 'WAW' | 'resolved-by-merge'; export interface NewlyOpenedConflict { /** The shared symbol id. */ symbol: string; name: string; filePath: string; /** The peer task whose declared write-set the escape landed in. */ peerTaskId: string; verdict: ContentionVerdict; /** Plain-language reason for the verdict. */ reason: string; } /** A registration-site collision the actual diff confirmed merges cleanly. */ export interface RegistryResolution { symbol: string; name: string; filePath: string; peerTaskId: string; reason: string; } /** A write declared `append` at plan time whose diff actually modified existing code. */ export interface MisDeclaredAppend { symbol: string; name: string; filePath: string; } export interface EscapeAnalysis { declaredTaskId: string; /** Symbols the diff modified outside the declared write-set, classified. Sorted by id. */ escapes: EscapeItem[]; /** New write-write conflicts an escape opened against a supplied peer. Sorted. */ newlyOpenedConflicts: NewlyOpenedConflict[]; /** Registration-site collisions the actual diff confirmed resolve by merge. Sorted. */ registryResolutions: RegistryResolution[]; /** Declared appends the diff actually violated. Sorted. */ misDeclaredAppends: MisDeclaredAppend[]; /** Governance findings for the enforcement policy (advisory by default). */ findings: GovernanceFinding[]; summary: { modifiedSymbols: number; escapes: number; outOfScopeWrites: number; readSetIntrusions: number; scopeCreep: number; newlyOpenedConflicts: number; registryResolutions: number; misDeclaredAppends: number; }; disclosure: string; } export declare const ESCAPE_DISCLOSURE: string; /** * Normalize a caller-supplied declared footprint into clean id sets. Tolerant of * partial/foreign shapes (the caller may pass proposal 1's full `Footprint` or a * hand-built subset); malformed members are dropped, never thrown on. */ export declare function normalizeDeclaredFootprint(input: DeclaredFootprintInput | null | undefined, fallbackTaskId?: string): NormalizedFootprint; /** * Compute the escape analysis for one actual diff against its declared footprint * and a set of peer footprints. Pure and deterministic: identical inputs yield a * byte-identical result. */ export declare function analyzeEscape(modifiedSymbols: readonly ModifiedSymbol[], declared: NormalizedFootprint, peers: readonly NormalizedFootprint[]): EscapeAnalysis; //# sourceMappingURL=footprint-escape.d.ts.map