foottrace 1.0.3 → 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 (128) hide show
  1. package/README.md +137 -4
  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 +7 -2
  6. package/dist/esm/index.js +2 -1
  7. package/dist/esm/lib/cause/admission.d.ts +27 -0
  8. package/dist/esm/lib/cause/admission.js +59 -0
  9. package/dist/esm/lib/cause/explain.d.ts +5 -0
  10. package/dist/esm/lib/cause/explain.js +114 -0
  11. package/dist/esm/lib/cause/ranking.d.ts +6 -0
  12. package/dist/esm/lib/cause/ranking.js +10 -0
  13. package/dist/esm/lib/cause/recordAdapter.d.ts +9 -0
  14. package/dist/esm/lib/cause/recordAdapter.js +49 -0
  15. package/dist/esm/lib/cause/recordChildren.d.ts +4 -0
  16. package/dist/esm/lib/cause/recordChildren.js +79 -0
  17. package/dist/esm/lib/cause/recordControl.d.ts +13 -0
  18. package/dist/esm/lib/cause/recordControl.js +66 -0
  19. package/dist/esm/lib/cause/recordCoverage.d.ts +24 -0
  20. package/dist/esm/lib/cause/recordCoverage.js +87 -0
  21. package/dist/esm/lib/cause/recordIdentity.d.ts +9 -0
  22. package/dist/esm/lib/cause/recordIdentity.js +67 -0
  23. package/dist/esm/lib/cause/recordLoading.d.ts +7 -0
  24. package/dist/esm/lib/cause/recordLoading.js +116 -0
  25. package/dist/esm/lib/cause/recordParents.d.ts +4 -0
  26. package/dist/esm/lib/cause/recordParents.js +86 -0
  27. package/dist/esm/lib/cause/recordScope.d.ts +8 -0
  28. package/dist/esm/lib/cause/recordScope.js +60 -0
  29. package/dist/esm/lib/cause/recordTypes.d.ts +67 -0
  30. package/dist/esm/lib/cause/recordTypes.js +1 -0
  31. package/dist/esm/lib/cause/recordValidation.d.ts +33 -0
  32. package/dist/esm/lib/cause/recordValidation.js +198 -0
  33. package/dist/esm/lib/cause/session.d.ts +30 -0
  34. package/dist/esm/lib/cause/session.js +161 -0
  35. package/dist/esm/lib/cause/storedRecordAdapter.d.ts +11 -0
  36. package/dist/esm/lib/cause/storedRecordAdapter.js +57 -0
  37. package/dist/esm/lib/cause/strategies.d.ts +3 -0
  38. package/dist/esm/lib/cause/strategies.js +21 -0
  39. package/dist/esm/lib/cause/types.d.ts +90 -0
  40. package/dist/esm/lib/cause/types.js +1 -0
  41. package/dist/esm/lib/cause/validation.d.ts +24 -0
  42. package/dist/esm/lib/cause/validation.js +134 -0
  43. package/dist/esm/lib/cause/walk.d.ts +12 -0
  44. package/dist/esm/lib/cause/walk.js +174 -0
  45. package/dist/esm/lib/ids/attemptId.d.ts +63 -0
  46. package/dist/esm/lib/ids/attemptId.js +91 -0
  47. package/dist/esm/lib/memory/EventLog.d.ts +89 -3
  48. package/dist/esm/lib/memory/EventLog.js +242 -3
  49. package/dist/esm/lib/memory/honesty.d.ts +6 -4
  50. package/dist/esm/lib/memory/honesty.js +7 -4
  51. package/dist/esm/lib/memory/sink.d.ts +148 -0
  52. package/dist/esm/lib/memory/sink.js +92 -0
  53. package/dist/esm/lib/memory/types.d.ts +4 -1
  54. package/dist/esm/lib/memory/types.js +1 -1
  55. package/dist/esm/lib/time-travel/bundles.d.ts +2 -0
  56. package/dist/esm/lib/time-travel/bundles.js +16 -0
  57. package/dist/esm/lib/time-travel/commitStops.js +21 -2
  58. package/dist/esm/lib/time-travel/index.d.ts +2 -0
  59. package/dist/esm/lib/time-travel/index.js +1 -0
  60. package/dist/esm/lib/time-travel/storedRecord.d.ts +195 -0
  61. package/dist/esm/lib/time-travel/storedRecord.js +494 -0
  62. package/dist/esm/lib/time-travel/timeTravel.js +11 -0
  63. package/dist/esm/lib/time-travel/types.d.ts +4 -1
  64. package/dist/esm/write.d.ts +7 -0
  65. package/dist/esm/write.js +6 -0
  66. package/dist/index.js +6 -2
  67. package/dist/lib/cause/admission.js +67 -0
  68. package/dist/lib/cause/explain.js +119 -0
  69. package/dist/lib/cause/ranking.js +14 -0
  70. package/dist/lib/cause/recordAdapter.js +55 -0
  71. package/dist/lib/cause/recordChildren.js +83 -0
  72. package/dist/lib/cause/recordControl.js +74 -0
  73. package/dist/lib/cause/recordCoverage.js +99 -0
  74. package/dist/lib/cause/recordIdentity.js +73 -0
  75. package/dist/lib/cause/recordLoading.js +121 -0
  76. package/dist/lib/cause/recordParents.js +90 -0
  77. package/dist/lib/cause/recordScope.js +66 -0
  78. package/dist/lib/cause/recordTypes.js +2 -0
  79. package/dist/lib/cause/recordValidation.js +213 -0
  80. package/dist/lib/cause/session.js +165 -0
  81. package/dist/lib/cause/storedRecordAdapter.js +61 -0
  82. package/dist/lib/cause/strategies.js +26 -0
  83. package/dist/lib/cause/types.js +2 -0
  84. package/dist/lib/cause/validation.js +150 -0
  85. package/dist/lib/cause/walk.js +180 -0
  86. package/dist/lib/ids/attemptId.js +96 -0
  87. package/dist/lib/memory/EventLog.js +242 -3
  88. package/dist/lib/memory/honesty.js +7 -4
  89. package/dist/lib/memory/sink.js +98 -0
  90. package/dist/lib/memory/types.js +1 -1
  91. package/dist/lib/time-travel/bundles.js +18 -1
  92. package/dist/lib/time-travel/commitStops.js +21 -2
  93. package/dist/lib/time-travel/index.js +3 -1
  94. package/dist/lib/time-travel/storedRecord.js +498 -0
  95. package/dist/lib/time-travel/timeTravel.js +11 -0
  96. package/dist/types/cause.d.ts +8 -0
  97. package/dist/types/index.d.ts +7 -2
  98. package/dist/types/lib/cause/admission.d.ts +27 -0
  99. package/dist/types/lib/cause/explain.d.ts +5 -0
  100. package/dist/types/lib/cause/ranking.d.ts +6 -0
  101. package/dist/types/lib/cause/recordAdapter.d.ts +9 -0
  102. package/dist/types/lib/cause/recordChildren.d.ts +4 -0
  103. package/dist/types/lib/cause/recordControl.d.ts +13 -0
  104. package/dist/types/lib/cause/recordCoverage.d.ts +24 -0
  105. package/dist/types/lib/cause/recordIdentity.d.ts +9 -0
  106. package/dist/types/lib/cause/recordLoading.d.ts +7 -0
  107. package/dist/types/lib/cause/recordParents.d.ts +4 -0
  108. package/dist/types/lib/cause/recordScope.d.ts +8 -0
  109. package/dist/types/lib/cause/recordTypes.d.ts +67 -0
  110. package/dist/types/lib/cause/recordValidation.d.ts +33 -0
  111. package/dist/types/lib/cause/session.d.ts +30 -0
  112. package/dist/types/lib/cause/storedRecordAdapter.d.ts +11 -0
  113. package/dist/types/lib/cause/strategies.d.ts +3 -0
  114. package/dist/types/lib/cause/types.d.ts +90 -0
  115. package/dist/types/lib/cause/validation.d.ts +24 -0
  116. package/dist/types/lib/cause/walk.d.ts +12 -0
  117. package/dist/types/lib/ids/attemptId.d.ts +63 -0
  118. package/dist/types/lib/memory/EventLog.d.ts +89 -3
  119. package/dist/types/lib/memory/honesty.d.ts +6 -4
  120. package/dist/types/lib/memory/sink.d.ts +148 -0
  121. package/dist/types/lib/memory/types.d.ts +4 -1
  122. package/dist/types/lib/time-travel/bundles.d.ts +2 -0
  123. package/dist/types/lib/time-travel/index.d.ts +2 -0
  124. package/dist/types/lib/time-travel/storedRecord.d.ts +195 -0
  125. package/dist/types/lib/time-travel/types.d.ts +4 -1
  126. package/dist/types/write.d.ts +7 -0
  127. package/dist/write.js +8 -1
  128. package/package.json +67 -13
@@ -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[];
@@ -0,0 +1,63 @@
1
+ /**
2
+ * attemptId — the address of one attempt under a stage execution.
3
+ *
4
+ * Format:
5
+ * <runtimeStageId>@<n> attempt n of the stage execution 'charge#4@2'
6
+ * <runtimeStageId>@<n>.<k>@<m> attempt m of the k-th call made 'charge#4@2.1@3'
7
+ * during stage attempt n
8
+ *
9
+ * Every number after the runtimeStageId is a positive integer, counted from 1: attempt 1 is the first
10
+ * try, call 1 the first call the stage attempt made. Written without leading zeros, so one attempt has
11
+ * one spelling and an id is a safe join key.
12
+ *
13
+ * PARSED FROM THE RIGHT. A runtimeStageId always ends in `#<execution index>`, and the suffix grammar
14
+ * holds no `#`, so the LAST `#` of an attempt id is its execution delimiter whatever the stage id
15
+ * contains — `@`, `.` and `~` stay legal in stage ids, and no byte is newly reserved (the id doors'
16
+ * refusal, FootPrint's `refuseReservedId`, is untouched). The delimiter is read from the grammar's one
17
+ * owner, `runtimeStageId.ts`.
18
+ *
19
+ * Imports nothing but the runtime-ID grammar: a leaf of the record, checked by the package fence.
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * buildAttemptId('charge#4', 2); // 'charge#4@2'
24
+ * buildAttemptId('charge#4', 2, { ordinal: 1, attempt: 3 }); // 'charge#4@2.1@3'
25
+ * parseAttemptId('sf/v1.2@x#7@3'); // { runtimeStageId: 'sf/v1.2@x#7', attempt: 3 }
26
+ * parseAttemptId('charge#4'); // undefined — a stage execution, not an attempt
27
+ * ```
28
+ */
29
+ import { type RuntimeStageId } from './runtimeStageId.js';
30
+ declare const ATTEMPT_ID: unique symbol;
31
+ /** A `<runtimeStageId>@<n>[.<k>@<m>]` string the grammar produced. Assignable to `string`. */
32
+ export type AttemptId = string & {
33
+ readonly [ATTEMPT_ID]: true;
34
+ };
35
+ /**
36
+ * Build the id of one attempt.
37
+ *
38
+ * @param runtimeStageId The stage execution the attempt belongs to (`[subflowPath/]stageId#index`).
39
+ * @param attempt The stage attempt, from 1.
40
+ * @param call For an attempt of a call made inside the stage attempt: the call's `ordinal` (1 for the
41
+ * first call that stage attempt made) and its own `attempt` (from 1).
42
+ * @throws TypeError when `runtimeStageId` does not end in `#<execution index>`, and RangeError when a
43
+ * count is not a positive integer — what `parseAttemptId` could not give back is never written.
44
+ */
45
+ export declare function buildAttemptId(runtimeStageId: string, attempt: number, call?: {
46
+ readonly ordinal: number;
47
+ readonly attempt: number;
48
+ }): AttemptId;
49
+ /**
50
+ * Parse an attempt id into its parts, or `undefined` when `id` is not one (a bare runtimeStageId, a
51
+ * count of 0, a leading zero, a missing part). Never throws.
52
+ *
53
+ * `parseAttemptId(buildAttemptId(...parts))` gives the parts back, whatever the stage id contains.
54
+ */
55
+ export declare function parseAttemptId(id: string): {
56
+ runtimeStageId: RuntimeStageId;
57
+ attempt: number;
58
+ call?: {
59
+ ordinal: number;
60
+ attempt: number;
61
+ };
62
+ } | undefined;
63
+ export {};
@@ -24,8 +24,20 @@
24
24
  * contract for state) and a `SharedArrayBuffer` (every copy shares its memory). A frozen RegExp's
25
25
  * `lastIndex` is read-only, so a `/g` or `/y` regex read from a bundle throws when `exec` or
26
26
  * `replace` advance it.
27
+ *
28
+ * A SINK (1.1.0, optional). `new EventLog(base, { sink, address, meta? })` also hands every entry to a
29
+ * `RecordSink` AS IT LANDS (`sink.ts`): `opened` from the constructor (the base, as the sink's own
30
+ * copy), `commit` from `record` after the bundle is frozen and appended (the bundle as served),
31
+ * `note` from `note`, `closed` from `close`. The log's own record never depends on the sink: the
32
+ * bundle is in `steps` before the sink sees it, and nothing the sink does reaches the writer — a
33
+ * throw, a rejected promise or an entry the log refuses to send (a `note` whose data is not a plain
34
+ * object that survives `structuredClone`) is counted (`sinkFailures`) and reported to
35
+ * `sink.onFailure`, and the writer goes on; a `meta` that will not copy is lost on its own and its
36
+ * entry still goes. Without a sink, nothing here runs: the one-argument log writes the same bytes,
37
+ * and has the same own fields, it always did.
27
38
  */
28
39
  import type { EmitSourcePosition, LogAddress } from './eventPosition.js';
40
+ import { type LogOutcome, type Meta, type RecordSink } from './sink.js';
29
41
  import type { CommitBundle } from './types.js';
30
42
  export declare class EventLog {
31
43
  /** Base snapshot BEFORE the first stage mutates anything. */
@@ -34,7 +46,26 @@ export declare class EventLog {
34
46
  private steps;
35
47
  private sourceAddress?;
36
48
  private sourceAddressRetired;
37
- constructor(initialMemory: any);
49
+ /** Where entries go as they land; absent for the one-argument log. */
50
+ private readonly sink?;
51
+ /** Entries the sink did not take; set with the sink. */
52
+ private lostEntries?;
53
+ /** True while `onFailure` runs (until its promise settles): a loss it causes is counted, not reported again. */
54
+ private reporting?;
55
+ /**
56
+ * @param initialMemory The fold base — the state before the first commit (copied).
57
+ * @param options With a `sink`: every entry goes to it as it lands, under `address` — which also
58
+ * becomes this log's {@link address} — and `meta` rides the `opened` entry. The sink is told
59
+ * `opened` before the constructor returns.
60
+ * @throws TypeError when `options` names no sink, a malformed address, or a base that is not a plain
61
+ * object (the `opened` entry carries it) — a writer's mistake, refused at the door. Nothing the sink
62
+ * itself does is ever thrown.
63
+ */
64
+ constructor(initialMemory: any, options?: {
65
+ readonly sink: RecordSink;
66
+ readonly address: LogAddress;
67
+ readonly meta?: Meta;
68
+ });
38
69
  /**
39
70
  * The fold BASE — the state as it stood before the first commit.
40
71
  *
@@ -78,6 +109,53 @@ export declare class EventLog {
78
109
  * will no longer edit — its own object is the one frozen.
79
110
  */
80
111
  record(bundle: CommitBundle): void;
112
+ /**
113
+ * Note something beside the log, in the sink, as it happens — for evidence that must survive a run
114
+ * that never closes (an attempt that ended, before the next one starts). `kind` and `data` are the
115
+ * writer's: opaque to the record. Notes never enter the log's bundles, so the log's bytes are the
116
+ * same with or without them. Without a sink, nothing happens.
117
+ *
118
+ * Never throws: a `kind` that is not a string, `data` that is not a plain object surviving
119
+ * `structuredClone`, or a sink that fails is counted in {@link sinkFailures} and reported to
120
+ * `sink.onFailure`. The sink keeps its own copy of `data`.
121
+ */
122
+ note(kind: string, data: Meta): void;
123
+ /**
124
+ * Tell the sink this log ended — `outcome` and, for the reader's check that the stored log is
125
+ * whole, how many commits it holds now. `meta` rides the entry, copied. Without a sink, nothing
126
+ * happens. The log itself stays as it is: a later `record` still lands, and still reaches the sink
127
+ * (a reader then reads the log as not complete until it is closed again).
128
+ *
129
+ * Never throws: an unknown outcome, a `meta` that is not a plain object surviving `structuredClone`,
130
+ * or a sink that fails is counted in {@link sinkFailures} and reported to `sink.onFailure`.
131
+ */
132
+ close(outcome: LogOutcome, meta?: Meta): void;
133
+ /**
134
+ * How many entries the sink did not take: a sink method that threw or whose promise rejected, and an
135
+ * entry the log refused to send — plus each `meta` the log could not copy (that entry went without
136
+ * it). Counted as each happens (a rejection, when it settles); 0 without a sink. The stored record
137
+ * says the same from its side: a missing commit is a gap.
138
+ */
139
+ get sinkFailures(): number;
140
+ /**
141
+ * One entry to the sink. Nothing here reaches the writer: a refusal, a throw or a rejection is a lost
142
+ * entry; a `meta` that cannot be copied is lost on its own — counted and reported — and the entry goes
143
+ * without it.
144
+ */
145
+ private send;
146
+ /**
147
+ * Count the rejection of an adapter's promise once. A native promise is watched through the real
148
+ * `Promise.prototype.then` (an own `then` on it is not trusted); any other thenable is adopted into a
149
+ * fresh promise first, whose settling functions run once. Either way the rejection is handled.
150
+ */
151
+ private watch;
152
+ /**
153
+ * Count a lost entry and tell the sink's `onFailure`, if it listens; a report that fails is ignored.
154
+ * A loss caused while `onFailure` runs — until the promise it returns settles, when it returns one —
155
+ * (it wrote to this log again, through the same failing sink) is counted and not reported again: one
156
+ * report deep, never a runaway.
157
+ */
158
+ private lost;
81
159
  /**
82
160
  * Every recorded commit bundle, AS SERVED (9.44.2): a new array each call; a bundle freezing sealed
83
161
  * whole is the log's own (frozen) object, one holding a Date, Map, buffer … is a copy of its open
@@ -94,12 +172,20 @@ export declare class EventLog {
94
172
  recorded(): CommitBundle[];
95
173
  /** Number of recorded commits. */
96
174
  get length(): number;
97
- /** Immutable identity of this log, absent until the engine binds it. */
175
+ /**
176
+ * Immutable identity of this log: the sink's address when the log was built with one, otherwise
177
+ * absent until the engine binds it.
178
+ */
98
179
  get address(): LogAddress | undefined;
99
180
  /** @internal Bind once; resuming the same log must not rename its earlier positions. */
100
181
  bindAddress(logRunId: string, drillPath: readonly string[]): void;
101
182
  /** O(1) source-time capture. No counter, observer bookkeeping, or future commit prediction. */
102
183
  capturePosition(runId: string): EmitSourcePosition | undefined;
103
- /** Wipes history (useful for test resets). */
184
+ /**
185
+ * Wipes history (useful for test resets).
186
+ *
187
+ * @throws Error on a log built with a sink: what the sink stored cannot be wiped, and a second
188
+ * history under the same address would read back as one corrupted log. Start a fresh EventLog.
189
+ */
104
190
  clear(): void;
105
191
  }
@@ -23,10 +23,11 @@
23
23
  * alias of `foottrace` that is a union of string literals: each one is inside the
24
24
  * registry or is named, with its reason, as not an honesty vocabulary.
25
25
  *
26
- * NO OBJECT GAINS A FIELD from being registered. Three signals carry no code of their own — a
27
- * `LogGap` (`'log-gap'`), `CausalNode.incompleteSources` (`'incomplete-sources'`) and
28
- * `FoldedState.redacted` / `redactedPaths` (`'redacted'`) — and a `truncated: { byDepth, byNodes }`
29
- * field means `'truncated'`; they are registered so a reader explains them from the same place.
26
+ * NO OBJECT GAINS A FIELD from being registered. Four signals carry no code of their own — a
27
+ * `LogGap` (`'log-gap'`), `CausalNode.incompleteSources` (`'incomplete-sources'`),
28
+ * `FoldedState.redacted` / `redactedPaths` (`'redacted'`) and an entry of `StoredRecord.unread`
29
+ * (`'unread-entry'`, 1.1.0) — and a `truncated: { byDepth, byNodes }` field means `'truncated'`; they
30
+ * are registered so a reader explains them from the same place.
30
31
  *
31
32
  * NOT HERE: the two strings a redaction leaves where a value was. The engine writes those on every
32
33
  * redacted run; the log's lives in its own leaf, `memory/placeholders.ts` (the scope channel's with
@@ -75,6 +76,7 @@ export declare const HONESTY_CODES: Readonly<{
75
76
  'initial+log': string;
76
77
  'log-only': string;
77
78
  'log-gap': string;
79
+ 'unread-entry': string;
78
80
  'incomplete-sources': string;
79
81
  redacted: string;
80
82
  deleted: string;