foottrace 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/README.md +96 -2
  2. package/dist/cause.js +14 -0
  3. package/dist/esm/cause.d.ts +8 -0
  4. package/dist/esm/cause.js +5 -0
  5. package/dist/esm/index.d.ts +2 -0
  6. package/dist/esm/lib/cause/admission.d.ts +27 -0
  7. package/dist/esm/lib/cause/admission.js +59 -0
  8. package/dist/esm/lib/cause/explain.d.ts +5 -0
  9. package/dist/esm/lib/cause/explain.js +114 -0
  10. package/dist/esm/lib/cause/ranking.d.ts +6 -0
  11. package/dist/esm/lib/cause/ranking.js +10 -0
  12. package/dist/esm/lib/cause/recordAdapter.d.ts +9 -0
  13. package/dist/esm/lib/cause/recordAdapter.js +49 -0
  14. package/dist/esm/lib/cause/recordChildren.d.ts +4 -0
  15. package/dist/esm/lib/cause/recordChildren.js +79 -0
  16. package/dist/esm/lib/cause/recordControl.d.ts +13 -0
  17. package/dist/esm/lib/cause/recordControl.js +66 -0
  18. package/dist/esm/lib/cause/recordCoverage.d.ts +24 -0
  19. package/dist/esm/lib/cause/recordCoverage.js +87 -0
  20. package/dist/esm/lib/cause/recordIdentity.d.ts +9 -0
  21. package/dist/esm/lib/cause/recordIdentity.js +67 -0
  22. package/dist/esm/lib/cause/recordLoading.d.ts +7 -0
  23. package/dist/esm/lib/cause/recordLoading.js +116 -0
  24. package/dist/esm/lib/cause/recordParents.d.ts +4 -0
  25. package/dist/esm/lib/cause/recordParents.js +86 -0
  26. package/dist/esm/lib/cause/recordScope.d.ts +8 -0
  27. package/dist/esm/lib/cause/recordScope.js +60 -0
  28. package/dist/esm/lib/cause/recordTypes.d.ts +67 -0
  29. package/dist/esm/lib/cause/recordTypes.js +1 -0
  30. package/dist/esm/lib/cause/recordValidation.d.ts +33 -0
  31. package/dist/esm/lib/cause/recordValidation.js +198 -0
  32. package/dist/esm/lib/cause/session.d.ts +30 -0
  33. package/dist/esm/lib/cause/session.js +161 -0
  34. package/dist/esm/lib/cause/storedRecordAdapter.d.ts +11 -0
  35. package/dist/esm/lib/cause/storedRecordAdapter.js +57 -0
  36. package/dist/esm/lib/cause/strategies.d.ts +3 -0
  37. package/dist/esm/lib/cause/strategies.js +21 -0
  38. package/dist/esm/lib/cause/types.d.ts +90 -0
  39. package/dist/esm/lib/cause/types.js +1 -0
  40. package/dist/esm/lib/cause/validation.d.ts +24 -0
  41. package/dist/esm/lib/cause/validation.js +134 -0
  42. package/dist/esm/lib/cause/walk.d.ts +12 -0
  43. package/dist/esm/lib/cause/walk.js +174 -0
  44. package/dist/esm/lib/memory/types.d.ts +4 -1
  45. package/dist/esm/lib/memory/types.js +1 -1
  46. package/dist/lib/cause/admission.js +67 -0
  47. package/dist/lib/cause/explain.js +119 -0
  48. package/dist/lib/cause/ranking.js +14 -0
  49. package/dist/lib/cause/recordAdapter.js +55 -0
  50. package/dist/lib/cause/recordChildren.js +83 -0
  51. package/dist/lib/cause/recordControl.js +74 -0
  52. package/dist/lib/cause/recordCoverage.js +99 -0
  53. package/dist/lib/cause/recordIdentity.js +73 -0
  54. package/dist/lib/cause/recordLoading.js +121 -0
  55. package/dist/lib/cause/recordParents.js +90 -0
  56. package/dist/lib/cause/recordScope.js +66 -0
  57. package/dist/lib/cause/recordTypes.js +2 -0
  58. package/dist/lib/cause/recordValidation.js +213 -0
  59. package/dist/lib/cause/session.js +165 -0
  60. package/dist/lib/cause/storedRecordAdapter.js +61 -0
  61. package/dist/lib/cause/strategies.js +26 -0
  62. package/dist/lib/cause/types.js +2 -0
  63. package/dist/lib/cause/validation.js +150 -0
  64. package/dist/lib/cause/walk.js +180 -0
  65. package/dist/lib/memory/types.js +1 -1
  66. package/dist/types/cause.d.ts +8 -0
  67. package/dist/types/index.d.ts +2 -0
  68. package/dist/types/lib/cause/admission.d.ts +27 -0
  69. package/dist/types/lib/cause/explain.d.ts +5 -0
  70. package/dist/types/lib/cause/ranking.d.ts +6 -0
  71. package/dist/types/lib/cause/recordAdapter.d.ts +9 -0
  72. package/dist/types/lib/cause/recordChildren.d.ts +4 -0
  73. package/dist/types/lib/cause/recordControl.d.ts +13 -0
  74. package/dist/types/lib/cause/recordCoverage.d.ts +24 -0
  75. package/dist/types/lib/cause/recordIdentity.d.ts +9 -0
  76. package/dist/types/lib/cause/recordLoading.d.ts +7 -0
  77. package/dist/types/lib/cause/recordParents.d.ts +4 -0
  78. package/dist/types/lib/cause/recordScope.d.ts +8 -0
  79. package/dist/types/lib/cause/recordTypes.d.ts +67 -0
  80. package/dist/types/lib/cause/recordValidation.d.ts +33 -0
  81. package/dist/types/lib/cause/session.d.ts +30 -0
  82. package/dist/types/lib/cause/storedRecordAdapter.d.ts +11 -0
  83. package/dist/types/lib/cause/strategies.d.ts +3 -0
  84. package/dist/types/lib/cause/types.d.ts +90 -0
  85. package/dist/types/lib/cause/validation.d.ts +24 -0
  86. package/dist/types/lib/cause/walk.d.ts +12 -0
  87. package/dist/types/lib/memory/types.d.ts +4 -1
  88. package/package.json +67 -13
@@ -0,0 +1,180 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.pathTo = exports.frontier = exports.walk = void 0;
4
+ const validation_js_1 = require("./validation.js");
5
+ /** BFS discovers the shallowest arrival; a repeated diamond node is not a cycle. */
6
+ async function walk(session, seeds, direction) {
7
+ const queue = [], seen = new Set(), marks = [], edges = [], missing = [];
8
+ const enqueue = (id, depth) => {
9
+ if (seen.has(id))
10
+ return;
11
+ const refusal = session.admit(id);
12
+ if (refusal) {
13
+ missing.push(refusal);
14
+ return;
15
+ }
16
+ seen.add(id);
17
+ queue.push([id, depth]);
18
+ };
19
+ for (const id of seeds)
20
+ enqueue(id, 0);
21
+ for (let index = 0; index < queue.length; index++) {
22
+ const [id, depth] = queue[index], stopped = session.stopGap(id);
23
+ if (stopped) {
24
+ missing.push(stopped);
25
+ break;
26
+ }
27
+ const marked = await session.read('attention', id, validation_js_1.attention);
28
+ if (marked.gap)
29
+ missing.push(marked.gap);
30
+ if (marked.value?.length) {
31
+ marks.push(Object.freeze({ nodeId: id, attention: marked.value }));
32
+ if (marked.value.some((item) => item.kind === 'not-collected'))
33
+ missing.push((0, validation_js_1.gap)('not-collected', id));
34
+ }
35
+ const coverage = await session.read('gaps', id, validation_js_1.gaps);
36
+ if (coverage.gap)
37
+ missing.push(coverage.gap);
38
+ if (coverage.value)
39
+ missing.push(...coverage.value);
40
+ const page = await session.read(direction, id, (raw) => (0, validation_js_1.adjacency)(raw, session.limits.maxEdges));
41
+ if (page.gap) {
42
+ missing.push(page.gap);
43
+ continue;
44
+ }
45
+ if (!page.value)
46
+ continue;
47
+ if (!page.value.complete)
48
+ missing.push((0, validation_js_1.gap)('not-collected', id));
49
+ if (depth >= session.limits.maxDepth) {
50
+ if (page.value.edges.length)
51
+ missing.push((0, validation_js_1.gap)('depth-limit', id));
52
+ continue;
53
+ }
54
+ for (const edge of page.value.edges) {
55
+ if (session.edges >= session.limits.maxEdges) {
56
+ missing.push((0, validation_js_1.gap)('edge-limit', id));
57
+ break;
58
+ }
59
+ session.edges++;
60
+ const refusal = session.admit(edge.nodeId);
61
+ if (refusal) {
62
+ missing.push(refusal);
63
+ continue;
64
+ }
65
+ edges.push(Object.freeze({
66
+ from: id,
67
+ to: edge.nodeId,
68
+ kind: edge.kind,
69
+ attribution: edge.attribution,
70
+ evidence: edge.evidence,
71
+ }));
72
+ if (edge.attribution === 'stage')
73
+ missing.push((0, validation_js_1.gap)('conservative', id));
74
+ enqueue(edge.nodeId, depth + 1);
75
+ }
76
+ }
77
+ if (hasCycle(edges))
78
+ missing.push((0, validation_js_1.gap)('cycle'));
79
+ return Object.freeze({
80
+ nodes: Object.freeze(queue.map((row) => row[0])),
81
+ marked: Object.freeze(marks),
82
+ edges: Object.freeze(edges),
83
+ gaps: (0, validation_js_1.uniqueGaps)(missing),
84
+ });
85
+ }
86
+ exports.walk = walk;
87
+ /** Iterative three-colour walk; shared nodes remain distinguishable from back edges. */
88
+ function hasCycle(edges) {
89
+ const next = new Map();
90
+ for (const edge of edges) {
91
+ if (!next.has(edge.from))
92
+ next.set(edge.from, []);
93
+ next.get(edge.from).push(edge.to);
94
+ }
95
+ const state = new Map();
96
+ for (const seed of next.keys()) {
97
+ if (state.has(seed))
98
+ continue;
99
+ const stack = [[seed, 0]];
100
+ state.set(seed, 1);
101
+ while (stack.length) {
102
+ const frame = stack[stack.length - 1], children = next.get(frame[0]) ?? [];
103
+ if (frame[1] >= children.length) {
104
+ state.set(frame[0], 2);
105
+ stack.pop();
106
+ continue;
107
+ }
108
+ const child = children[frame[1]++];
109
+ if (state.get(child) === 1)
110
+ return true;
111
+ if (!state.has(child)) {
112
+ state.set(child, 1);
113
+ stack.push([child, 0]);
114
+ }
115
+ }
116
+ }
117
+ return false;
118
+ }
119
+ function frontier(result) {
120
+ const marked = new Set(result.marked
121
+ .filter((item) => item.attention.some((mark) => mark.kind !== 'not-collected'))
122
+ .map((item) => item.nodeId));
123
+ const pending = new Map(result.nodes.map((id) => [id, 0]));
124
+ const dependents = new Map();
125
+ for (const edge of result.edges) {
126
+ pending.set(edge.from, (pending.get(edge.from) ?? 0) + 1);
127
+ if (!dependents.has(edge.to))
128
+ dependents.set(edge.to, []);
129
+ dependents.get(edge.to).push(edge.from);
130
+ }
131
+ // Process causal ancestors first, propagating one boolean through the DAG.
132
+ // This is O(nodes + edges), rather than a fresh ancestry scan per marked node.
133
+ const queue = [...pending].filter(([, count]) => count === 0).map(([id]) => id);
134
+ const earlierMark = new Set(), eligible = new Set();
135
+ for (let i = 0; i < queue.length; i++) {
136
+ const id = queue[i];
137
+ if (marked.has(id) && !earlierMark.has(id))
138
+ eligible.add(id);
139
+ for (const dependent of dependents.get(id) ?? []) {
140
+ if (marked.has(id) || earlierMark.has(id))
141
+ earlierMark.add(dependent);
142
+ const count = pending.get(dependent) - 1;
143
+ pending.set(dependent, count);
144
+ if (count === 0)
145
+ queue.push(dependent);
146
+ }
147
+ }
148
+ // Nodes left in a cycle cannot supply a validated earliest ancestor.
149
+ return result.marked.filter((item) => eligible.has(item.nodeId));
150
+ }
151
+ exports.frontier = frontier;
152
+ function pathTo(edges, start, target) {
153
+ const queue = [start], previous = new Map([[start, undefined]]);
154
+ const next = new Map();
155
+ for (const edge of edges) {
156
+ if (!next.has(edge.from))
157
+ next.set(edge.from, []);
158
+ next.get(edge.from).push(edge.to);
159
+ }
160
+ for (let i = 0; i < queue.length; i++) {
161
+ const id = queue[i];
162
+ if (id === target)
163
+ break;
164
+ for (const child of next.get(id) ?? [])
165
+ if (!previous.has(child)) {
166
+ previous.set(child, id);
167
+ queue.push(child);
168
+ }
169
+ }
170
+ if (!previous.has(target))
171
+ return Object.freeze([]);
172
+ const path = [];
173
+ let id = target;
174
+ while (id !== undefined) {
175
+ path.push(id);
176
+ id = previous.get(id);
177
+ }
178
+ return Object.freeze(path.reverse());
179
+ }
180
+ exports.pathTo = pathTo;
@@ -3,7 +3,7 @@
3
3
  * types.ts — the record's types: a bundle, its rows, its patches, its encoding, and the
4
4
  * execution tree as the record's readers read it.
5
5
  *
6
- * Imports nothing. The standalone record names nothing outside itself: the engine's frame
6
+ * Imports only the record's leaf attention type. The record names nothing outside itself: the engine's frame
7
7
  * types — the stage snapshot, flow messages and retention dials — remain in FootPrint's
8
8
  * `frameTypes.ts`. The closed record graph is enforced by scripts/check-layering.mjs.
9
9
  */
@@ -0,0 +1,8 @@
1
+ /** Optional read-only cause and impact queries over recorded dependency graphs. */
2
+ export { explain, attentionList } from './lib/cause/explain.js';
3
+ export { largestDrop, mostAffected } from './lib/cause/strategies.js';
4
+ export { createRecordGraph } from './lib/cause/recordAdapter.js';
5
+ export { createStoredRecordGraph } from './lib/cause/storedRecordAdapter.js';
6
+ export type { Adjacency, AttentionListOptions, AttentionListResult, AttentionReason, CauseGraph, CauseResult, Confidence, ExplainOptions, Gap, GapCode, GraphContext, GraphEdge, ImpactResult, ImpactStrategy, MarkedNode, MaybePromise, NodeId, RootStrategy, TraversedEdge, WalkLimits, } from './lib/cause/types.js';
7
+ export type { RecordScope, RecordGraphOptions, RecordCauseGraph, RecordNode } from './lib/cause/recordAdapter.js';
8
+ export type { StoredRecordScope, StoredRecordHost } from './lib/cause/storedRecordAdapter.js';
@@ -28,3 +28,5 @@ export type { EmitSourcePosition, LogAddress } from './lib/memory/eventPosition.
28
28
  export { EXECUTION_DELIMITER, PATH_DELIMITER, isExecutionKey, joinPath, pathSegments as idPathSegments, stageIdOf, subflowSegmentsOf, } from './lib/ids/runtimeStageId.js';
29
29
  export { deepEqual } from './lib/memory/equality.js';
30
30
  export { serveRecord } from './lib/capture/freeze.js';
31
+ /** Record attention types are shared with producers; the optional cause reader has its own door. */
32
+ export type { Attention, AttentionKind } from './lib/cause/types.js';
@@ -0,0 +1,27 @@
1
+ import type { GapCode } from './types.js';
2
+ /** One refusal representation; subclasses preserve the graph-port and record-loader boundaries. */
3
+ export declare class EvidenceRefusal extends Error {
4
+ readonly code: GapCode;
5
+ constructor(code: GapCode, message: string);
6
+ }
7
+ export type RefuseEvidence = (code?: GapCode) => Error;
8
+ interface DescriptorPolicy {
9
+ readonly translateTrapErrors: boolean;
10
+ readonly requireOwnValue: boolean;
11
+ }
12
+ /** Read data descriptors only; each boundary keeps its existing treatment of proxy traps. */
13
+ export declare function ownData(value: unknown, key: string, refuse: RefuseEvidence, policy: DescriptorPolicy): unknown;
14
+ /** Names share one grammar, with explicit limits and the record path separator exception. */
15
+ export declare function boundedName(value: unknown, maximum: number, refuse: RefuseEvidence, path?: boolean): string;
16
+ /** Bound iteration before reading entries, without invoking element accessors. */
17
+ export declare function boundedList<T>(value: unknown, maximum: number, read: (item: unknown) => T, options: {
18
+ readonly refuse: RefuseEvidence;
19
+ readonly overflow: GapCode;
20
+ readonly freeze: boolean;
21
+ readonly descriptors: DescriptorPolicy;
22
+ }): readonly T[];
23
+ /** Charge retained fields against the same mutable allowance across admission helpers. */
24
+ export declare function spendFields(budget: {
25
+ left: number;
26
+ }, count: number, refuse: RefuseEvidence): void;
27
+ export {};
@@ -0,0 +1,5 @@
1
+ import type { AttentionListOptions, AttentionListResult, CauseGraph, CauseResult, ExplainOptions, NodeId } from './types.js';
2
+ /** Read-only deterministic cause localisation and impact. No inference from timestamps or structural ancestry. */
3
+ export declare function explain(graph: CauseGraph, nodeId: NodeId, options?: ExplainOptions): Promise<CauseResult>;
4
+ /** Discover only bounded host seeds; this is not a global scan of arbitrary storage. */
5
+ export declare function attentionList(graph: CauseGraph, options?: AttentionListOptions): Promise<AttentionListResult>;
@@ -0,0 +1,6 @@
1
+ import type { NodeId } from './types.js';
2
+ /** Strategies may only select admitted nodes; impact ranking must preserve the full set. */
3
+ export declare function admitRanking(raw: unknown, allowed: ReadonlySet<NodeId>, options: {
4
+ readonly exactPermutation: boolean;
5
+ readonly maximum: number;
6
+ }): readonly NodeId[];
@@ -0,0 +1,9 @@
1
+ import type { LogAddress } from '../memory/eventPosition.js';
2
+ import type { RecordCauseGraph, RecordGraphOptions, RecordLoaderResult, RecordScope } from './recordTypes.js';
3
+ import type { GraphContext } from './types.js';
4
+ export type { RecordCauseGraph, RecordGraphOptions, RecordLoaderResult, RecordNode, RecordScope, } from './recordTypes.js';
5
+ export { recordOptions } from './recordScope.js';
6
+ /** Common engine for memory and one-scope-at-a-time host storage. Loader functions are trusted executable code. */
7
+ export declare function createLazyRecordGraph(load: (address: LogAddress, context: GraphContext) => RecordLoaderResult | Promise<RecordLoaderResult>, options?: RecordGraphOptions): RecordCauseGraph;
8
+ /** A finished record already in memory; payload admission is lazy per requested scope. */
9
+ export declare function createRecordGraph(scopes: readonly RecordScope[], options?: RecordGraphOptions): RecordCauseGraph;
@@ -0,0 +1,4 @@
1
+ import type { ResolvedRecordNode } from './recordTypes.js';
2
+ import type { Adjacency, Gap } from './types.js';
3
+ /** Share parent-side coverage checks before appending data then control children in their established order. */
4
+ export declare function childrenForRecordNode(node: ResolvedRecordNode, maximumEdges: number, extraGapCodes: readonly Gap['code'][] | undefined): Adjacency;
@@ -0,0 +1,13 @@
1
+ import type { ControlDependency } from '../memory/backtrack.js';
2
+ import type { AddRecordGap, PreparedRecordScope, RecordIdentity } from './recordTypes.js';
3
+ import type { GraphEdge } from './types.js';
4
+ /** Only an execution or captured read may govern control flow; a written key alone cannot. */
5
+ export declare function controlChildren(scope: PreparedRecordScope, identity: RecordIdentity): Iterable<readonly [string, ControlDependency]>;
6
+ /** Canonical slicing receives only control parents that existed before the dependent execution. */
7
+ export declare function controlParentLookup(scope: PreparedRecordScope): ((runtimeStageId: string) => ControlDependency | undefined) | undefined;
8
+ /** Keep missing or future deciders visible rather than silently dropping their recorded dependency. */
9
+ export declare function collectControlParentCoverage(scope: PreparedRecordScope, identity: RecordIdentity, firstCommitIndex: number, addGap: AddRecordGap): void;
10
+ /** Include control fan-out in the same immediate-neighbor cap used for data adjacency. */
11
+ export declare function collectControlChildCoverage(scope: PreparedRecordScope, identity: RecordIdentity, firstCommitIndex: number, neighbors: Set<string>, addGap: AddRecordGap): void;
12
+ /** Preserve recorded control order and coverage when a dependent execution is absent or capped. */
13
+ export declare function appendControlChildren(scope: PreparedRecordScope, identity: RecordIdentity, commitIndex: number, maximumEdges: number, edges: GraphEdge[]): boolean;
@@ -0,0 +1,24 @@
1
+ import type { CommitBundle } from '../memory/types.js';
2
+ import type { AddRecordGap, PreparedRecordScope, RecordIdentity, RecordLoadFailure, ResolvedRecordNode } from './recordTypes.js';
3
+ import type { Attention, Gap } from './types.js';
4
+ /** Preserve first-observed coverage order while collapsing repeated codes from different evidence paths. */
5
+ export declare function recordGapCollector(): {
6
+ gaps: Gap[];
7
+ addGap: AddRecordGap;
8
+ };
9
+ /** Failed loading exposes a fixed category, never the host error or an apparently complete empty graph. */
10
+ export declare function gapForRecordFailure(code: Gap['code']): readonly Gap[];
11
+ /** Return captured mark references without treating an omitted attention map as collected evidence. */
12
+ export declare function attentionForRecordNode(node: ResolvedRecordNode | RecordLoadFailure): readonly Attention[];
13
+ /** Emit host and capture gaps before query-derived gaps so existing evidence order stays stable. */
14
+ export declare function collectScopeCoverage(scope: PreparedRecordScope, bundle: CommitBundle, extraGapCodes: readonly Gap['code'][] | undefined, addGap: AddRecordGap): void;
15
+ /** A read-only identity has no new value life; unqualified executions retain written-key encounter order. */
16
+ export declare function writtenKeysForNode(bundle: CommitBundle, identity: RecordIdentity): string[];
17
+ /** Immediate adjacency caps affect graph coverage even when the caller asks only for parents. */
18
+ export declare function collectForwardCoverage(scope: PreparedRecordScope, identity: RecordIdentity, commitIndex: number, firstCommitIndex: number, maximumEdges: number, addGap: AddRecordGap): void;
19
+ /** Exact attribution requires captured per-write provenance or a validated read-only key context. */
20
+ export declare function readAttributionIsPrecise(scope: PreparedRecordScope, identity: RecordIdentity, bundle: CommitBundle): boolean;
21
+ /** Distinguish conservative attribution from missing collection without marking a proven empty leaf. */
22
+ export declare function collectReadCoverage(scope: PreparedRecordScope, identity: RecordIdentity, precise: boolean, addGap: AddRecordGap): void;
23
+ /** Forward absence is trustworthy only when every potentially consuming execution collected its reads. */
24
+ export declare function allStageReadsCollected(scope: PreparedRecordScope): boolean;
@@ -0,0 +1,9 @@
1
+ import type { LogAddress } from '../memory/eventPosition.js';
2
+ import type { GraphContext, NodeId } from './types.js';
3
+ import type { RecordIdentity, RecordLoadFailure, RecordNode, RecordScopeLoader, ResolvedRecordNode } from './recordTypes.js';
4
+ /** Parse only the bounded identity encoding; payload fields never become graph addresses. */
5
+ export declare function readNodeIdentity(nodeId: string): RecordIdentity;
6
+ /** Stable field order preserves existing address/execution/key IDs across storage and memory readers. */
7
+ export declare function nodeIdFor(addressInput: LogAddress, node: RecordNode, read?: true): NodeId;
8
+ /** An admitted ID must also match captured evidence before it can introduce a dependency. */
9
+ export declare function resolveRecordNode(nodeId: NodeId, context: GraphContext, loader: RecordScopeLoader): Promise<ResolvedRecordNode | RecordLoadFailure>;
@@ -0,0 +1,7 @@
1
+ import type { LogAddress } from '../memory/eventPosition.js';
2
+ import type { GraphContext, NodeId } from './types.js';
3
+ import type { RecordGraphOptions, RecordScopeHost, RecordScopeLoader } from './recordTypes.js';
4
+ /** Bound caller waiting independently of host cooperation without sharing cancellation between requests. */
5
+ export declare function createRecordScopeLoader(hostLoad: RecordScopeHost, limits: Required<RecordGraphOptions>): RecordScopeLoader;
6
+ /** Discovery must distinguish an unavailable scope from a successfully loaded empty record. */
7
+ export declare function openRecordScope(addressInput: LogAddress, context: GraphContext, loader: RecordScopeLoader): Promise<readonly NodeId[]>;
@@ -0,0 +1,4 @@
1
+ import type { RecordInspection, ResolvedRecordNode } from './recordTypes.js';
2
+ import type { Gap } from './types.js';
3
+ /** Derive parent adjacency and its ordered gaps together from the same bounded canonical query. */
4
+ export declare function inspectRecordNode(node: ResolvedRecordNode, maximumEdges: number, extraGapCodes: readonly Gap['code'][] | undefined): RecordInspection;
@@ -0,0 +1,8 @@
1
+ import type { LogAddress } from '../memory/eventPosition.js';
2
+ import type { PreparedRecordScope, RecordGraphOptions, RecordScope } from './recordTypes.js';
3
+ /** Read configuration as data so option accessors cannot execute during adapter construction. */
4
+ export declare function recordOptions(options: RecordGraphOptions): Required<RecordGraphOptions>;
5
+ /** Spend one shared field budget before canonical readers see any untrusted scope data. */
6
+ export declare function prepareRecordScope(value: RecordScope, address: LogAddress, maximumCommits: number, maximumFields: number): PreparedRecordScope;
7
+ /** Preserve both stage reads and stronger per-write prefixes, including their original encounter order. */
8
+ export declare function readKeysForStage(scope: PreparedRecordScope, runtimeStageId: string): string[];
@@ -0,0 +1,67 @@
1
+ import type { ControlDependency } from '../memory/backtrack.js';
2
+ import type { LogAddress } from '../memory/eventPosition.js';
3
+ import type { CommitBundle } from '../memory/types.js';
4
+ import type { Adjacency, Attention, CauseGraph, Gap, GraphContext, NodeId } from './types.js';
5
+ /** Explicit complete maps record absence too; an omitted map means it was not collected. */
6
+ export interface RecordScope {
7
+ readonly address: LogAddress;
8
+ readonly commitLog: readonly unknown[];
9
+ readonly reads?: Readonly<Record<string, readonly string[]>>;
10
+ readonly controlDeps?: Readonly<Record<string, {
11
+ readonly deciderId: string;
12
+ }>>;
13
+ readonly attention?: Readonly<Record<string, readonly Attention[]>>;
14
+ readonly complete?: boolean;
15
+ }
16
+ export interface RecordNode {
17
+ readonly runtimeStageId: string;
18
+ readonly commitIdx?: number;
19
+ readonly key?: string;
20
+ }
21
+ export interface RecordGraphOptions {
22
+ readonly maxCommits?: number;
23
+ readonly maxFields?: number;
24
+ readonly maxEdges?: number;
25
+ readonly maxScopes?: number;
26
+ readonly loadTimeoutMs?: number;
27
+ }
28
+ export interface RecordCauseGraph extends CauseGraph {
29
+ nodeId(address: LogAddress, node: RecordNode): NodeId;
30
+ /** Rejects with fixed message/code on discovery failure; never reports an unavailable scope as empty. */
31
+ openScope(address: LogAddress, context: GraphContext): Promise<readonly NodeId[]>;
32
+ }
33
+ export interface RecordLoaderResult {
34
+ readonly scope: RecordScope;
35
+ readonly gaps?: readonly Gap['code'][];
36
+ }
37
+ export interface RecordIdentity extends RecordNode {
38
+ readonly address: LogAddress;
39
+ readonly read?: true;
40
+ }
41
+ export interface PreparedRecordScope {
42
+ address: LogAddress;
43
+ log: CommitBundle[];
44
+ reads?: Map<string, string[]>;
45
+ control?: Map<string, ControlDependency>;
46
+ marks?: Map<string, readonly Attention[]>;
47
+ complete: boolean;
48
+ }
49
+ export interface RecordLoadFailure {
50
+ failure: Gap['code'];
51
+ }
52
+ export type LoadedRecordScope = PreparedRecordScope | RecordLoadFailure;
53
+ export interface ResolvedRecordNode {
54
+ scope: PreparedRecordScope;
55
+ identity: RecordIdentity;
56
+ commitIndex: number;
57
+ }
58
+ export interface RecordInspection {
59
+ adjacency: Adjacency;
60
+ gaps: readonly Gap[];
61
+ }
62
+ export type RecordScopeHost = (address: LogAddress, context: GraphContext) => RecordLoaderResult | Promise<RecordLoaderResult>;
63
+ export interface RecordScopeLoader {
64
+ loadScope(address: LogAddress, context: GraphContext): Promise<LoadedRecordScope>;
65
+ extraGapCodes(scope: PreparedRecordScope): readonly Gap['code'][] | undefined;
66
+ }
67
+ export type AddRecordGap = (code: Gap['code']) => void;
@@ -0,0 +1,33 @@
1
+ /** Admission before the existing readers touch untrusted record fields. Payloads never leave this module's graph. */
2
+ import type { LogAddress } from '../memory/eventPosition.js';
3
+ import type { CommitBundle } from '../memory/types.js';
4
+ import type { GapCode } from './types.js';
5
+ import { attention as marksOf } from './validation.js';
6
+ import { EvidenceRefusal } from './admission.js';
7
+ export { marksOf };
8
+ /** Loader refusals stay distinct from graph-port refusals so a failed host load keeps its original classification. */
9
+ export declare class RecordRefusal extends EvidenceRefusal {
10
+ readonly code: GapCode;
11
+ constructor(code?: GapCode);
12
+ }
13
+ export declare function own(value: unknown, key: string): unknown;
14
+ export declare function names(value: unknown, maximum?: number, path?: boolean): string[];
15
+ export declare function name(value: unknown, path?: boolean): string;
16
+ export declare function list(value: unknown, maximum: number): readonly unknown[];
17
+ export declare function addressOf(value: unknown): LogAddress;
18
+ export declare function spend(budget: {
19
+ left: number;
20
+ }, count: number): void;
21
+ export declare const addressKey: (a: LogAddress) => string;
22
+ /** Iterative bounded copy: data descriptors only, no getters/toJSON/prototypes or opaque class instances. */
23
+ export declare function dataCopy(value: unknown, budget: {
24
+ left: number;
25
+ }): unknown;
26
+ export declare function bundleOf(value: unknown, idx: number, budget: {
27
+ left: number;
28
+ }): CommitBundle;
29
+ export declare function mapOf<T>(value: unknown, limit: number, read: (v: unknown) => T, budget?: {
30
+ left: number;
31
+ }): Map<string, T> | undefined;
32
+ /** Observe native promises by intrinsic without assimilating a data object's then accessor. */
33
+ export declare function projectLoaded<T, R>(work: () => T | Promise<T>, project: (value: T) => R): Promise<R>;
@@ -0,0 +1,30 @@
1
+ import type { CauseGraph, ExplainOptions, Gap, GraphContext, NodeId, WalkLimits } from './types.js';
2
+ export interface Read<T> {
3
+ readonly value?: T;
4
+ readonly gap?: Gap;
5
+ }
6
+ type Method = 'parents' | 'children' | 'attention' | 'gaps' | 'roots';
7
+ /** One deadline, memo table and work budget per public query, never per graph or store. */
8
+ export declare class Session {
9
+ readonly limits: Required<WalkLimits>;
10
+ readonly context: GraphContext;
11
+ readonly nodes: Set<string>;
12
+ readonly controller: AbortController;
13
+ private readonly cache;
14
+ private calls;
15
+ edges: number;
16
+ private timedOut;
17
+ private readonly deadline;
18
+ private readonly timer;
19
+ private readonly external?;
20
+ private readonly onAbort;
21
+ private readonly methods;
22
+ constructor(graph: CauseGraph, options?: ExplainOptions);
23
+ dispose(): void;
24
+ stopGap(id?: NodeId): Gap | undefined;
25
+ admit(id: NodeId): Gap | undefined;
26
+ hasRoots(): boolean;
27
+ read<T>(method: Method, id: NodeId | undefined, project: (raw: unknown) => T): Promise<Read<T>>;
28
+ invoke<T>(id: NodeId | undefined, work: () => unknown, project: (raw: unknown) => T): Promise<Read<T>>;
29
+ }
30
+ export {};
@@ -0,0 +1,11 @@
1
+ import type { LogAddress } from '../memory/eventPosition.js';
2
+ import { type RecordCauseGraph, type RecordGraphOptions, type RecordScope } from './recordAdapter.js';
3
+ import type { GraphContext, MaybePromise } from './types.js';
4
+ export interface StoredRecordScope extends Omit<RecordScope, 'address' | 'commitLog' | 'complete'> {
5
+ readonly entries: readonly unknown[];
6
+ }
7
+ export interface StoredRecordHost {
8
+ loadLog(address: LogAddress, context: GraphContext): MaybePromise<StoredRecordScope>;
9
+ }
10
+ /** Load one address only. The host owns auth/storage; cancellation is forwarded and waiting is independently bounded. */
11
+ export declare function createStoredRecordGraph(host: StoredRecordHost, options?: RecordGraphOptions): RecordCauseGraph;
@@ -0,0 +1,3 @@
1
+ import type { NodeId, RootStrategy } from './types.js';
2
+ export declare const largestDrop: (scores: ReadonlyMap<NodeId, number>) => RootStrategy;
3
+ export declare const mostAffected: (counts: ReadonlyMap<NodeId, number>) => RootStrategy;
@@ -0,0 +1,90 @@
1
+ /** Names and evidence references only. No state values, messages, credentials or business payloads. */
2
+ export type NodeId = string;
3
+ export type AttentionKind = 'error' | 'unknown-effect' | 'gave-up' | 'timeout' | 'retried' | 'quality-drop' | 'not-collected' | 'outlier' | (string & {});
4
+ export type AttentionReason = 'ERROR_RECORDED' | 'UNKNOWN_EFFECT_RECORDED' | 'RETRY_STOP_RECORDED' | 'TIMEOUT_RECORDED' | 'RETRY_RECORDED' | 'QUALITY_DROP_RECORDED' | 'EVIDENCE_NOT_COLLECTED' | 'OUTLIER_RECORDED' | 'BUSINESS_RULE_MATCHED';
5
+ export interface Attention {
6
+ readonly kind: AttentionKind;
7
+ readonly reason: AttentionReason;
8
+ readonly evidence: readonly string[];
9
+ }
10
+ export type Confidence = 'complete' | 'incomplete';
11
+ export type GapCode = 'not-collected' | 'untracked-input' | 'redacted' | 'conservative' | 'scope-boundary' | 'invalid-data' | 'unavailable' | 'timeout' | 'cancelled' | 'cycle' | 'node-limit' | 'depth-limit' | 'edge-limit' | 'call-limit' | 'truncated' | 'strategy-failed';
12
+ export interface Gap {
13
+ readonly code: GapCode;
14
+ readonly nodeId?: NodeId;
15
+ readonly evidence: readonly string[];
16
+ }
17
+ export interface GraphEdge {
18
+ readonly nodeId: NodeId;
19
+ readonly kind: 'data' | 'control';
20
+ readonly attribution: 'per-write' | 'stage' | 'recorded-control';
21
+ readonly evidence: readonly string[];
22
+ }
23
+ export interface Adjacency {
24
+ readonly edges: readonly GraphEdge[];
25
+ readonly complete: boolean;
26
+ }
27
+ export interface GraphContext {
28
+ readonly signal: AbortSignal;
29
+ }
30
+ export type MaybePromise<T> = T | Promise<T>;
31
+ /** A host owns storage, authentication and service resolution. A node may represent a key-qualified write. */
32
+ export interface CauseGraph {
33
+ parents(id: NodeId, context: GraphContext): MaybePromise<Adjacency>;
34
+ children(id: NodeId, context: GraphContext): MaybePromise<Adjacency>;
35
+ attention(id: NodeId, context: GraphContext): MaybePromise<readonly Attention[]>;
36
+ gaps(id: NodeId, context: GraphContext): MaybePromise<readonly Gap[]>;
37
+ /** Optional, bounded discovery seeds; attentionList can instead receive explicit nodeIds. */
38
+ roots?(context: GraphContext): MaybePromise<readonly NodeId[]>;
39
+ }
40
+ export interface TraversedEdge extends Omit<GraphEdge, 'nodeId'> {
41
+ readonly from: NodeId;
42
+ readonly to: NodeId;
43
+ }
44
+ export interface MarkedNode {
45
+ readonly nodeId: NodeId;
46
+ readonly attention: readonly Attention[];
47
+ }
48
+ export interface ImpactResult {
49
+ readonly nodes: readonly NodeId[];
50
+ readonly edges: readonly TraversedEdge[];
51
+ readonly confidence: Confidence;
52
+ readonly gaps: readonly Gap[];
53
+ }
54
+ export interface CauseResult {
55
+ readonly nodeId: NodeId;
56
+ readonly attention: readonly Attention[];
57
+ /** Present only for one supported candidate and complete backward coverage. Not provider-internal proof. */
58
+ readonly rootCause?: NodeId;
59
+ /** Selected node → candidate, following recorded dependency edges. */
60
+ readonly path: readonly NodeId[];
61
+ readonly alternatives: readonly NodeId[];
62
+ readonly impact: ImpactResult;
63
+ readonly confidence: Confidence;
64
+ readonly gaps: readonly Gap[];
65
+ }
66
+ export interface AttentionListResult {
67
+ readonly nodes: readonly MarkedNode[];
68
+ readonly confidence: Confidence;
69
+ readonly gaps: readonly Gap[];
70
+ }
71
+ export interface WalkLimits {
72
+ readonly maxNodes?: number;
73
+ readonly maxDepth?: number;
74
+ readonly maxEdges?: number;
75
+ readonly maxCalls?: number;
76
+ readonly timeoutMs?: number;
77
+ }
78
+ /** A strategy selects a tied best tier of already recorded marked nodes; it cannot introduce identities. */
79
+ export type RootStrategy = (candidates: readonly MarkedNode[], context: GraphContext) => MaybePromise<readonly NodeId[]>;
80
+ /** Reorder all already discovered impact references; never inject or drop evidence. */
81
+ export type ImpactStrategy = (nodes: readonly NodeId[], context: GraphContext) => MaybePromise<readonly NodeId[]>;
82
+ export interface ExplainOptions extends WalkLimits {
83
+ readonly signal?: AbortSignal;
84
+ readonly rankRoots?: RootStrategy;
85
+ readonly rankImpact?: ImpactStrategy;
86
+ }
87
+ export interface AttentionListOptions extends WalkLimits {
88
+ readonly signal?: AbortSignal;
89
+ readonly nodeIds?: readonly NodeId[];
90
+ }
@@ -0,0 +1,24 @@
1
+ import type { Adjacency, Attention, Gap, GapCode, NodeId } from './types.js';
2
+ import { EvidenceRefusal } from './admission.js';
3
+ /** Graph-port refusals carry their code through Session; loader refusals are handled at the record boundary. */
4
+ export declare class AdmissionError extends EvidenceRefusal {
5
+ readonly code: GapCode;
6
+ constructor(code?: GapCode);
7
+ }
8
+ /**
9
+ * Only ordinary native promise wrappers belong to the async host contract.
10
+ * An own constructor could run an accessor/species during intrinsic then().
11
+ * Exotic wrappers remain the trusted host's responsibility, including rejection handling.
12
+ */
13
+ export declare function observePromise(raw: unknown, accept: (raw: unknown) => void, reject: (reason: unknown) => void): boolean;
14
+ export declare function own(value: unknown, key: string): unknown;
15
+ export declare function plain(value: unknown): void;
16
+ export declare function name(value: unknown, maximum?: number): string;
17
+ export declare function list<T>(value: unknown, maximum: number, read: (item: unknown) => T, overflow?: GapCode): readonly T[];
18
+ export declare const refs: (value: unknown) => readonly string[];
19
+ export declare function attention(value: unknown): readonly Attention[];
20
+ export declare function gaps(value: unknown): readonly Gap[];
21
+ export declare function adjacency(value: unknown, maximum: number): Adjacency;
22
+ export declare const nodeIds: (value: unknown, max: number) => readonly NodeId[];
23
+ export declare const gap: (code: GapCode, nodeId?: NodeId) => Gap;
24
+ export declare function uniqueGaps(items: readonly Gap[]): readonly Gap[];
@@ -0,0 +1,12 @@
1
+ import type { Gap, MarkedNode, NodeId, TraversedEdge } from './types.js';
2
+ import { Session } from './session.js';
3
+ export interface Walk {
4
+ readonly nodes: readonly NodeId[];
5
+ readonly marked: readonly MarkedNode[];
6
+ readonly edges: readonly TraversedEdge[];
7
+ readonly gaps: readonly Gap[];
8
+ }
9
+ /** BFS discovers the shallowest arrival; a repeated diamond node is not a cycle. */
10
+ export declare function walk(session: Session, seeds: readonly NodeId[], direction: 'parents' | 'children'): Promise<Walk>;
11
+ export declare function frontier(result: Walk): readonly MarkedNode[];
12
+ export declare function pathTo(edges: readonly TraversedEdge[], start: NodeId, target: NodeId): readonly NodeId[];