foottrace 1.1.0 → 1.3.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 (110) hide show
  1. package/README.md +121 -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 +6 -0
  6. package/dist/esm/index.js +1 -0
  7. package/dist/esm/lib/cause/explain.d.ts +5 -0
  8. package/dist/esm/lib/cause/explain.js +114 -0
  9. package/dist/esm/lib/cause/queryMemo.d.ts +18 -0
  10. package/dist/esm/lib/cause/queryMemo.js +46 -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 +42 -0
  15. package/dist/esm/lib/cause/recordChildren.d.ts +4 -0
  16. package/dist/esm/lib/cause/recordChildren.js +78 -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/recordInspection.d.ts +4 -0
  24. package/dist/esm/lib/cause/recordInspection.js +86 -0
  25. package/dist/esm/lib/cause/recordLoading.d.ts +7 -0
  26. package/dist/esm/lib/cause/recordLoading.js +116 -0
  27. package/dist/esm/lib/cause/recordQuery.d.ts +9 -0
  28. package/dist/esm/lib/cause/recordQuery.js +22 -0
  29. package/dist/esm/lib/cause/recordScope.d.ts +8 -0
  30. package/dist/esm/lib/cause/recordScope.js +60 -0
  31. package/dist/esm/lib/cause/recordTypes.d.ts +67 -0
  32. package/dist/esm/lib/cause/recordTypes.js +1 -0
  33. package/dist/esm/lib/cause/recordValidation.d.ts +33 -0
  34. package/dist/esm/lib/cause/recordValidation.js +198 -0
  35. package/dist/esm/lib/cause/session.d.ts +30 -0
  36. package/dist/esm/lib/cause/session.js +164 -0
  37. package/dist/esm/lib/cause/storedRecordAdapter.d.ts +11 -0
  38. package/dist/esm/lib/cause/storedRecordAdapter.js +63 -0
  39. package/dist/esm/lib/cause/strategies.d.ts +3 -0
  40. package/dist/esm/lib/cause/strategies.js +21 -0
  41. package/dist/esm/lib/cause/types.d.ts +91 -0
  42. package/dist/esm/lib/cause/types.js +1 -0
  43. package/dist/esm/lib/cause/validation.d.ts +24 -0
  44. package/dist/esm/lib/cause/validation.js +117 -0
  45. package/dist/esm/lib/cause/walk.d.ts +12 -0
  46. package/dist/esm/lib/cause/walk.js +174 -0
  47. package/dist/esm/lib/evidence/admission.d.ts +31 -0
  48. package/dist/esm/lib/evidence/admission.js +72 -0
  49. package/dist/esm/lib/evidence/codes.d.ts +33 -0
  50. package/dist/esm/lib/evidence/codes.js +28 -0
  51. package/dist/esm/lib/evidence/requirementTypes.d.ts +46 -0
  52. package/dist/esm/lib/evidence/requirementTypes.js +1 -0
  53. package/dist/esm/lib/evidence/requirements.d.ts +24 -0
  54. package/dist/esm/lib/evidence/requirements.js +185 -0
  55. package/dist/esm/lib/memory/types.d.ts +4 -1
  56. package/dist/esm/lib/memory/types.js +1 -1
  57. package/dist/index.js +5 -1
  58. package/dist/lib/cause/explain.js +119 -0
  59. package/dist/lib/cause/queryMemo.js +52 -0
  60. package/dist/lib/cause/ranking.js +14 -0
  61. package/dist/lib/cause/recordAdapter.js +48 -0
  62. package/dist/lib/cause/recordChildren.js +82 -0
  63. package/dist/lib/cause/recordControl.js +74 -0
  64. package/dist/lib/cause/recordCoverage.js +99 -0
  65. package/dist/lib/cause/recordIdentity.js +73 -0
  66. package/dist/lib/cause/recordInspection.js +90 -0
  67. package/dist/lib/cause/recordLoading.js +121 -0
  68. package/dist/lib/cause/recordQuery.js +27 -0
  69. package/dist/lib/cause/recordScope.js +66 -0
  70. package/dist/lib/cause/recordTypes.js +2 -0
  71. package/dist/lib/cause/recordValidation.js +213 -0
  72. package/dist/lib/cause/session.js +168 -0
  73. package/dist/lib/cause/storedRecordAdapter.js +67 -0
  74. package/dist/lib/cause/strategies.js +26 -0
  75. package/dist/lib/cause/types.js +2 -0
  76. package/dist/lib/cause/validation.js +133 -0
  77. package/dist/lib/cause/walk.js +180 -0
  78. package/dist/lib/evidence/admission.js +80 -0
  79. package/dist/lib/evidence/codes.js +33 -0
  80. package/dist/lib/evidence/requirementTypes.js +2 -0
  81. package/dist/lib/evidence/requirements.js +190 -0
  82. package/dist/lib/memory/types.js +1 -1
  83. package/dist/types/cause.d.ts +8 -0
  84. package/dist/types/index.d.ts +6 -0
  85. package/dist/types/lib/cause/explain.d.ts +5 -0
  86. package/dist/types/lib/cause/queryMemo.d.ts +18 -0
  87. package/dist/types/lib/cause/ranking.d.ts +6 -0
  88. package/dist/types/lib/cause/recordAdapter.d.ts +9 -0
  89. package/dist/types/lib/cause/recordChildren.d.ts +4 -0
  90. package/dist/types/lib/cause/recordControl.d.ts +13 -0
  91. package/dist/types/lib/cause/recordCoverage.d.ts +24 -0
  92. package/dist/types/lib/cause/recordIdentity.d.ts +9 -0
  93. package/dist/types/lib/cause/recordInspection.d.ts +4 -0
  94. package/dist/types/lib/cause/recordLoading.d.ts +7 -0
  95. package/dist/types/lib/cause/recordQuery.d.ts +9 -0
  96. package/dist/types/lib/cause/recordScope.d.ts +8 -0
  97. package/dist/types/lib/cause/recordTypes.d.ts +67 -0
  98. package/dist/types/lib/cause/recordValidation.d.ts +33 -0
  99. package/dist/types/lib/cause/session.d.ts +30 -0
  100. package/dist/types/lib/cause/storedRecordAdapter.d.ts +11 -0
  101. package/dist/types/lib/cause/strategies.d.ts +3 -0
  102. package/dist/types/lib/cause/types.d.ts +91 -0
  103. package/dist/types/lib/cause/validation.d.ts +24 -0
  104. package/dist/types/lib/cause/walk.d.ts +12 -0
  105. package/dist/types/lib/evidence/admission.d.ts +31 -0
  106. package/dist/types/lib/evidence/codes.d.ts +33 -0
  107. package/dist/types/lib/evidence/requirementTypes.d.ts +46 -0
  108. package/dist/types/lib/evidence/requirements.d.ts +24 -0
  109. package/dist/types/lib/memory/types.d.ts +4 -1
  110. package/package.json +67 -13
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.allStageReadsCollected = exports.collectReadCoverage = exports.readAttributionIsPrecise = exports.collectForwardCoverage = exports.writtenKeysForNode = exports.collectScopeCoverage = exports.attentionForRecordNode = exports.gapForRecordFailure = exports.recordGapCollector = void 0;
4
+ const keyPaths_js_1 = require("../memory/keyPaths.js");
5
+ const forwardSliceForKey_js_1 = require("../slice/forwardSliceForKey.js");
6
+ const recordControl_js_1 = require("./recordControl.js");
7
+ const recordScope_js_1 = require("./recordScope.js");
8
+ /** Preserve first-observed coverage order while collapsing repeated codes from different evidence paths. */
9
+ function recordGapCollector() {
10
+ const gaps = [];
11
+ return {
12
+ gaps,
13
+ addGap: (code) => {
14
+ if (!gaps.some((gap) => gap.code === code))
15
+ gaps.push({ code, evidence: [] });
16
+ },
17
+ };
18
+ }
19
+ exports.recordGapCollector = recordGapCollector;
20
+ /** Failed loading exposes a fixed category, never the host error or an apparently complete empty graph. */
21
+ function gapForRecordFailure(code) {
22
+ return [{ code, evidence: [] }];
23
+ }
24
+ exports.gapForRecordFailure = gapForRecordFailure;
25
+ /** Return captured mark references without treating an omitted attention map as collected evidence. */
26
+ function attentionForRecordNode(node) {
27
+ // An omitted map stays unknown coverage; it is never replaced by an affirmative empty map.
28
+ return node.scope.marks?.get(node.identity.runtimeStageId) ?? [];
29
+ }
30
+ exports.attentionForRecordNode = attentionForRecordNode;
31
+ /** Emit host and capture gaps before query-derived gaps so existing evidence order stays stable. */
32
+ function collectScopeCoverage(scope, bundle, extraGapCodes, addGap) {
33
+ for (const code of extraGapCodes ?? [])
34
+ addGap(code);
35
+ if (!scope.complete || scope.control === undefined || scope.marks === undefined)
36
+ addGap('not-collected');
37
+ if (bundle.untrackedSources?.length)
38
+ addGap('untracked-input');
39
+ if (bundle.redactedPaths.length)
40
+ addGap('redacted');
41
+ }
42
+ exports.collectScopeCoverage = collectScopeCoverage;
43
+ /** A read-only identity has no new value life; unqualified executions retain written-key encounter order. */
44
+ function writtenKeysForNode(bundle, identity) {
45
+ return identity.read
46
+ ? []
47
+ : identity.key === undefined
48
+ ? [...new Set(bundle.trace.map((entry) => entry.path))]
49
+ : [identity.key];
50
+ }
51
+ exports.writtenKeysForNode = writtenKeysForNode;
52
+ /** Immediate adjacency caps affect graph coverage even when the caller asks only for parents. */
53
+ function collectForwardCoverage(scope, identity, commitIndex, firstCommitIndex, maximumEdges, addGap) {
54
+ const neighbors = new Set();
55
+ const lookup = (runtimeStageId) => (0, recordScope_js_1.readKeysForStage)(scope, runtimeStageId);
56
+ for (const key of writtenKeysForNode(scope.log[commitIndex], identity)) {
57
+ const forward = (0, forwardSliceForKey_js_1.forwardSliceForKey)(scope.log, key, lookup, {
58
+ before: commitIndex + 1,
59
+ maxDepth: 1,
60
+ maxNodes: maximumEdges + 1,
61
+ });
62
+ // A one-hop query intentionally stops at depth one; only a node cap hides immediate adjacency.
63
+ if (forward.root?.truncated?.byNodes)
64
+ addGap('edge-limit');
65
+ for (const edge of forward.root?.fedEdges ?? [])
66
+ neighbors.add(JSON.stringify(['data', edge.child.commitIdx, edge.child.key]));
67
+ for (const read of forward.root?.reads ?? [])
68
+ if (!scope.log[read.commitIdx]?.trace.length)
69
+ neighbors.add(JSON.stringify(['read', read.commitIdx, key]));
70
+ }
71
+ (0, recordControl_js_1.collectControlChildCoverage)(scope, identity, firstCommitIndex, neighbors, addGap);
72
+ if (neighbors.size > maximumEdges)
73
+ addGap('edge-limit');
74
+ }
75
+ exports.collectForwardCoverage = collectForwardCoverage;
76
+ /** Exact attribution requires captured per-write provenance or a validated read-only key context. */
77
+ function readAttributionIsPrecise(scope, identity, bundle) {
78
+ const relevant = identity.key === undefined
79
+ ? bundle.trace
80
+ : bundle.trace.filter((entry) => (0, keyPaths_js_1.relation)(entry.path, identity.key) !== undefined);
81
+ return identity.read === true
82
+ ? identity.key !== undefined && (0, recordScope_js_1.readKeysForStage)(scope, identity.runtimeStageId).includes(identity.key)
83
+ : identity.key !== undefined && relevant.length > 0 && relevant.every((entry) => entry.readKeys !== undefined);
84
+ }
85
+ exports.readAttributionIsPrecise = readAttributionIsPrecise;
86
+ /** Distinguish conservative attribution from missing collection without marking a proven empty leaf. */
87
+ function collectReadCoverage(scope, identity, precise, addGap) {
88
+ if (!precise && (identity.key !== undefined || (0, recordScope_js_1.readKeysForStage)(scope, identity.runtimeStageId).length > 0))
89
+ addGap('conservative');
90
+ if (!scope.reads?.has(identity.runtimeStageId) && !precise)
91
+ addGap('not-collected');
92
+ }
93
+ exports.collectReadCoverage = collectReadCoverage;
94
+ /** Forward absence is trustworthy only when every potentially consuming execution collected its reads. */
95
+ function allStageReadsCollected(scope) {
96
+ return !scope.log.some((bundle) => !scope.reads?.has(bundle.runtimeStageId) &&
97
+ (bundle.trace.length === 0 || bundle.trace.some((entry) => entry.readKeys === undefined)));
98
+ }
99
+ exports.allStageReadsCollected = allStageReadsCollected;
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveRecordNode = exports.nodeIdFor = exports.readNodeIdentity = void 0;
4
+ const recordScope_js_1 = require("./recordScope.js");
5
+ const recordValidation_js_1 = require("./recordValidation.js");
6
+ /** Parse only the bounded identity encoding; payload fields never become graph addresses. */
7
+ function readNodeIdentity(nodeId) {
8
+ if (typeof nodeId !== 'string' || nodeId.length > 2048 || !nodeId.startsWith('record:'))
9
+ throw new recordValidation_js_1.RecordRefusal();
10
+ let fields;
11
+ try {
12
+ fields = JSON.parse(nodeId.slice(7));
13
+ }
14
+ catch {
15
+ throw new recordValidation_js_1.RecordRefusal();
16
+ }
17
+ const address = (0, recordValidation_js_1.addressOf)((0, recordValidation_js_1.recordOwn)(fields, 'address')), runtimeStageId = (0, recordValidation_js_1.recordName)((0, recordValidation_js_1.recordOwn)(fields, 'runtimeStageId')), commitIndex = (0, recordValidation_js_1.recordOwn)(fields, 'commitIdx'), key = (0, recordValidation_js_1.recordOwn)(fields, 'key'), read = (0, recordValidation_js_1.recordOwn)(fields, 'read');
18
+ if (commitIndex !== undefined && (!Number.isSafeInteger(commitIndex) || commitIndex < 0))
19
+ throw new recordValidation_js_1.RecordRefusal();
20
+ if ((read !== undefined && read !== true) || (read === true && key === undefined))
21
+ throw new recordValidation_js_1.RecordRefusal();
22
+ return {
23
+ address,
24
+ runtimeStageId,
25
+ ...(commitIndex === undefined ? {} : { commitIdx: commitIndex }),
26
+ ...(key === undefined ? {} : { key: (0, recordValidation_js_1.recordName)(key, true) }),
27
+ ...(read === true ? { read: true } : {}),
28
+ };
29
+ }
30
+ exports.readNodeIdentity = readNodeIdentity;
31
+ /** Stable field order preserves existing address/execution/key IDs across storage and memory readers. */
32
+ function nodeIdFor(addressInput, node, read) {
33
+ try {
34
+ const address = (0, recordValidation_js_1.addressOf)(addressInput), runtimeStageId = (0, recordValidation_js_1.recordName)((0, recordValidation_js_1.recordOwn)(node, 'runtimeStageId')), commitIndex = (0, recordValidation_js_1.recordOwn)(node, 'commitIdx'), key = (0, recordValidation_js_1.recordOwn)(node, 'key');
35
+ if (commitIndex !== undefined && (!Number.isSafeInteger(commitIndex) || commitIndex < 0))
36
+ throw new recordValidation_js_1.RecordRefusal();
37
+ const nodeId = 'record:' +
38
+ JSON.stringify({
39
+ address,
40
+ runtimeStageId,
41
+ ...(commitIndex === undefined ? {} : { commitIdx: commitIndex }),
42
+ ...(key === undefined ? {} : { key: (0, recordValidation_js_1.recordName)(key, true) }),
43
+ ...(read ? { read: true } : {}),
44
+ });
45
+ return nodeId.length <= 2048 ? nodeId : 'record:invalid';
46
+ }
47
+ catch {
48
+ return 'record:invalid';
49
+ }
50
+ }
51
+ exports.nodeIdFor = nodeIdFor;
52
+ /** An admitted ID must also match captured evidence before it can introduce a dependency. */
53
+ async function resolveRecordNode(nodeId, context, loader) {
54
+ try {
55
+ const identity = readNodeIdentity(nodeId), scope = await loader.loadScope(identity.address, context);
56
+ if ('failure' in scope)
57
+ return scope;
58
+ const commitIndex = identity.commitIdx ?? scope.log.findIndex((bundle) => bundle.runtimeStageId === identity.runtimeStageId);
59
+ if (commitIndex < 0 ||
60
+ commitIndex >= scope.log.length ||
61
+ scope.log[commitIndex].runtimeStageId !== identity.runtimeStageId)
62
+ throw new recordValidation_js_1.RecordRefusal();
63
+ // A qualified read is a captured fact, never a caller-supplied request to invent a dependency.
64
+ if (identity.read &&
65
+ (identity.key === undefined || !(0, recordScope_js_1.readKeysForStage)(scope, identity.runtimeStageId).includes(identity.key)))
66
+ throw new recordValidation_js_1.RecordRefusal();
67
+ return { scope, identity, commitIndex };
68
+ }
69
+ catch (error) {
70
+ return { failure: error instanceof recordValidation_js_1.RecordRefusal ? error.code : 'invalid-data' };
71
+ }
72
+ }
73
+ exports.resolveRecordNode = resolveRecordNode;
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.inspectRecordNode = void 0;
4
+ const backtrack_js_1 = require("../memory/backtrack.js");
5
+ const commitLogUtils_js_1 = require("../memory/commitLogUtils.js");
6
+ const sliceForKey_js_1 = require("../slice/sliceForKey.js");
7
+ const recordControl_js_1 = require("./recordControl.js");
8
+ const recordCoverage_js_1 = require("./recordCoverage.js");
9
+ const recordIdentity_js_1 = require("./recordIdentity.js");
10
+ const recordScope_js_1 = require("./recordScope.js");
11
+ /** Anchor writers before the execution so later commits cannot rewrite the recorded causal edge. */
12
+ function projectParentEdges(scope, parentEdges, firstCommitIndex, precise, maximumEdges, addGap) {
13
+ const edges = [];
14
+ for (const edge of parentEdges) {
15
+ if (edges.length >= maximumEdges) {
16
+ addGap('edge-limit');
17
+ break;
18
+ }
19
+ const writer = edge.kind === 'data' && edge.key
20
+ ? (0, commitLogUtils_js_1.findLastWriter)(scope.log, edge.key, firstCommitIndex)
21
+ : scope.log.find((bundle) => bundle.runtimeStageId === edge.parent.runtimeStageId);
22
+ if (!writer) {
23
+ addGap('untracked-input');
24
+ continue;
25
+ }
26
+ const writerIndex = scope.log.indexOf(writer);
27
+ edges.push({
28
+ nodeId: (0, recordIdentity_js_1.nodeIdFor)(scope.address, {
29
+ runtimeStageId: writer.runtimeStageId,
30
+ commitIdx: writerIndex,
31
+ ...(edge.kind === 'data' && edge.key ? { key: edge.key } : {}),
32
+ }),
33
+ kind: edge.kind,
34
+ attribution: edge.kind === 'control' ? 'recorded-control' : precise ? 'per-write' : 'stage',
35
+ evidence: ['commit:' + writerIndex],
36
+ });
37
+ if (edge.basis)
38
+ addGap('conservative');
39
+ }
40
+ return edges;
41
+ }
42
+ /** Derive parent adjacency and its ordered gaps together from the same bounded canonical query. */
43
+ function inspectRecordNode(node, maximumEdges, extraGapCodes) {
44
+ const { scope, identity, commitIndex } = node;
45
+ const bundle = scope.log[commitIndex], firstCommitIndex = scope.log.findIndex((entry) => entry.runtimeStageId === identity.runtimeStageId), coverage = (0, recordCoverage_js_1.recordGapCollector)();
46
+ (0, recordCoverage_js_1.collectScopeCoverage)(scope, bundle, extraGapCodes, coverage.addGap);
47
+ const lookup = (runtimeStageId) => (0, recordScope_js_1.readKeysForStage)(scope, runtimeStageId);
48
+ (0, recordCoverage_js_1.collectForwardCoverage)(scope, identity, commitIndex, firstCommitIndex, maximumEdges, coverage.addGap);
49
+ const precise = (0, recordCoverage_js_1.readAttributionIsPrecise)(scope, identity, bundle);
50
+ (0, recordCoverage_js_1.collectReadCoverage)(scope, identity, precise, coverage.addGap);
51
+ (0, recordControl_js_1.collectControlParentCoverage)(scope, identity, firstCommitIndex, coverage.addGap);
52
+ const control = (0, recordControl_js_1.controlParentLookup)(scope);
53
+ const slice = identity.key === undefined || identity.read
54
+ ? undefined
55
+ : (0, sliceForKey_js_1.sliceForKey)(scope.log, identity.key, lookup, {
56
+ before: commitIndex + 1,
57
+ maxDepth: 1,
58
+ maxNodes: maximumEdges + 1,
59
+ controlDeps: control,
60
+ });
61
+ if (identity.key !== undefined &&
62
+ !identity.read &&
63
+ (!slice?.writer || scope.log.indexOf(slice.writer) !== commitIndex)) {
64
+ coverage.addGap('invalid-data');
65
+ return { adjacency: { edges: [], complete: false }, gaps: coverage.gaps };
66
+ }
67
+ // Read-only identities retain both the read's data writer and the execution's recorded control parent.
68
+ const root = identity.read && identity.key !== undefined
69
+ ? (0, backtrack_js_1.causalChain)(scope.log, identity.runtimeStageId, (runtimeStageId) => (runtimeStageId === identity.runtimeStageId ? [identity.key] : lookup(runtimeStageId)), { maxDepth: 1, maxNodes: maximumEdges + 1, controlDeps: control })
70
+ : slice?.root ??
71
+ (identity.key === undefined
72
+ ? (0, backtrack_js_1.causalChain)(scope.log, identity.runtimeStageId, lookup, {
73
+ maxDepth: 1,
74
+ maxNodes: maximumEdges + 1,
75
+ controlDeps: control,
76
+ })
77
+ : undefined);
78
+ if (root?.preRunReads?.keys.length)
79
+ coverage.addGap('untracked-input');
80
+ if (root?.truncated?.byNodes)
81
+ coverage.addGap('edge-limit');
82
+ for (const note of slice?.notes ?? [])
83
+ if (note.code === 'redacted')
84
+ coverage.addGap('redacted');
85
+ else if (note.code === 'nested-rows' || note.code === 'from-initial-state')
86
+ coverage.addGap('conservative');
87
+ const edges = projectParentEdges(scope, root?.parentEdges ?? [], firstCommitIndex, precise, maximumEdges, coverage.addGap);
88
+ return { adjacency: { edges, complete: coverage.gaps.length === 0 }, gaps: coverage.gaps };
89
+ }
90
+ exports.inspectRecordNode = inspectRecordNode;
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.openRecordScope = exports.createRecordScopeLoader = void 0;
4
+ const validation_js_1 = require("./validation.js");
5
+ const recordIdentity_js_1 = require("./recordIdentity.js");
6
+ const recordScope_js_1 = require("./recordScope.js");
7
+ const recordValidation_js_1 = require("./recordValidation.js");
8
+ /** Admit before caching so a cache hit cannot bypass payload limits or lose scope-owned coverage. */
9
+ function admitLoadedScope(result, request, limits, state) {
10
+ if (request.controller.signal.aborted || performance.now() >= request.deadline) {
11
+ request.controller.abort();
12
+ return { failure: request.context.signal.aborted ? 'cancelled' : 'timeout' };
13
+ }
14
+ let prepared;
15
+ try {
16
+ prepared = (0, recordScope_js_1.prepareRecordScope)((0, recordValidation_js_1.recordOwn)(result, 'scope'), request.address, limits.maxCommits, limits.maxFields);
17
+ const extra = (0, recordValidation_js_1.recordList)((0, recordValidation_js_1.recordOwn)(result, 'gaps') ?? [], 32);
18
+ // Coverage belongs to this admitted object, so cached scopes retain the same gap ordering and ownership.
19
+ state.extraGaps.set(prepared, (0, validation_js_1.gaps)(extra.map((code) => ({ code, evidence: [] }))).map((gap) => gap.code));
20
+ }
21
+ catch (error) {
22
+ throw error instanceof recordValidation_js_1.RecordRefusal ? error : new recordValidation_js_1.RecordRefusal();
23
+ }
24
+ if (performance.now() >= request.deadline) {
25
+ request.controller.abort();
26
+ return { failure: 'timeout' };
27
+ }
28
+ if (state.cache.size >= limits.maxScopes && !state.cache.has(request.cacheKey))
29
+ return { failure: 'node-limit' };
30
+ state.cache.set(request.cacheKey, prepared);
31
+ return prepared;
32
+ }
33
+ /** Host failures become fixed gaps while unfinished host work continues to occupy its bounded slot. */
34
+ function startScopeWork(hostLoad, request, limits, state) {
35
+ const work = (0, recordValidation_js_1.projectLoaded)(() => {
36
+ if (request.controller.signal.aborted)
37
+ throw new recordValidation_js_1.RecordRefusal(request.context.signal.aborted ? 'cancelled' : 'timeout');
38
+ return hostLoad(request.address, { signal: request.controller.signal });
39
+ }, (result) => admitLoadedScope(result, request, limits, state)).catch((error) => {
40
+ if (request.context.signal.aborted)
41
+ return { failure: 'cancelled' };
42
+ if (performance.now() >= request.deadline) {
43
+ request.controller.abort();
44
+ return { failure: 'timeout' };
45
+ }
46
+ return { failure: error instanceof recordValidation_js_1.RecordRefusal ? error.code : 'unavailable' };
47
+ });
48
+ // A timed-out host operation retains its slot until it settles; repeated timeouts cannot grow an unbounded I/O pool.
49
+ void work.then(() => {
50
+ state.activeLoads--;
51
+ }, () => {
52
+ state.activeLoads--;
53
+ });
54
+ return work;
55
+ }
56
+ /** Bound caller waiting independently of host cooperation without sharing cancellation between requests. */
57
+ function createRecordScopeLoader(hostLoad, limits) {
58
+ const state = { activeLoads: 0, cache: new Map(), extraGaps: new WeakMap() };
59
+ return {
60
+ async loadScope(address, context) {
61
+ if (context.signal.aborted)
62
+ return { failure: 'cancelled' };
63
+ const cacheKey = (0, recordValidation_js_1.addressKey)(address);
64
+ const cached = state.cache.get(cacheKey);
65
+ if (cached)
66
+ return cached;
67
+ if (state.cache.size + state.activeLoads >= limits.maxScopes)
68
+ return { failure: 'node-limit' };
69
+ state.activeLoads++;
70
+ const deadline = performance.now() + limits.loadTimeoutMs;
71
+ const controller = new AbortController();
72
+ let timer;
73
+ let finish;
74
+ const interrupted = new Promise((resolve) => {
75
+ finish = resolve;
76
+ });
77
+ const cancel = () => {
78
+ controller.abort();
79
+ finish?.({ failure: 'cancelled' });
80
+ };
81
+ context.signal.addEventListener('abort', cancel, { once: true });
82
+ timer = setTimeout(() => {
83
+ controller.abort();
84
+ finish?.({ failure: 'timeout' });
85
+ }, limits.loadTimeoutMs);
86
+ try {
87
+ const work = startScopeWork(hostLoad, { address, context, controller, deadline, cacheKey }, limits, state);
88
+ return await Promise.race([work, interrupted]);
89
+ }
90
+ finally {
91
+ if (timer !== undefined)
92
+ clearTimeout(timer);
93
+ context.signal.removeEventListener('abort', cancel);
94
+ }
95
+ },
96
+ extraGapCodes: (scope) => state.extraGaps.get(scope),
97
+ };
98
+ }
99
+ exports.createRecordScopeLoader = createRecordScopeLoader;
100
+ /** Discovery must distinguish an unavailable scope from a successfully loaded empty record. */
101
+ async function openRecordScope(addressInput, context, loader) {
102
+ let address;
103
+ try {
104
+ address = (0, recordValidation_js_1.addressOf)(addressInput);
105
+ }
106
+ catch {
107
+ throw new recordValidation_js_1.RecordRefusal();
108
+ }
109
+ const scope = await loader.loadScope(address, context);
110
+ if ('failure' in scope)
111
+ throw new recordValidation_js_1.RecordRefusal(scope.failure);
112
+ // Empty unfinished evidence has no node on which to report its gap; discovery must reject instead.
113
+ if (!scope.log.length &&
114
+ (!scope.complete ||
115
+ scope.control === undefined ||
116
+ scope.marks === undefined ||
117
+ (loader.extraGapCodes(scope)?.length ?? 0) > 0))
118
+ throw new recordValidation_js_1.RecordRefusal('not-collected');
119
+ return scope.log.map((bundle, commitIndex) => (0, recordIdentity_js_1.nodeIdFor)(address, { runtimeStageId: bundle.runtimeStageId, commitIdx: commitIndex }));
120
+ }
121
+ exports.openRecordScope = openRecordScope;
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.whenResolved = exports.createRecordQuery = void 0;
4
+ const queryMemo_js_1 = require("./queryMemo.js");
5
+ const recordIdentity_js_1 = require("./recordIdentity.js");
6
+ const recordInspection_js_1 = require("./recordInspection.js");
7
+ /** Both dependency directions share the same admitted node and canonical inspection within a query. */
8
+ function createRecordQuery(loader, maximumEdges) {
9
+ const resolutionOwner = {}, inspectionOwner = {};
10
+ return {
11
+ resolve(nodeId, context) {
12
+ const resolve = () => (0, recordIdentity_js_1.resolveRecordNode)(nodeId, context, loader), memo = (0, queryMemo_js_1.queryMemo)(context);
13
+ // A retained success must never bypass the loader's existing cancellation check.
14
+ return memo && !context.signal.aborted ? memo.remember(resolutionOwner, nodeId, resolve) : resolve();
15
+ },
16
+ inspect(node, context) {
17
+ const inspect = () => (0, recordInspection_js_1.inspectRecordNode)(node, maximumEdges, loader.extraGapCodes(node.scope));
18
+ return (0, queryMemo_js_1.queryMemo)(context)?.remember(inspectionOwner, node, inspect) ?? inspect();
19
+ },
20
+ };
21
+ }
22
+ exports.createRecordQuery = createRecordQuery;
23
+ /** Every port maps the same fixed loading failure while projecting only admitted nodes. */
24
+ function whenResolved(node, onNode, onFailure) {
25
+ return 'failure' in node ? onFailure(node) : onNode(node);
26
+ }
27
+ exports.whenResolved = whenResolved;
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.readKeysForStage = exports.prepareRecordScope = exports.recordOptions = void 0;
4
+ const recordValidation_js_1 = require("./recordValidation.js");
5
+ /** Refuse an invalid limit before loading a scope or allocating its preparation budget. */
6
+ function boundedOption(value, defaultValue, maximum) {
7
+ const configuredValue = value ?? defaultValue;
8
+ if (typeof configuredValue !== 'number' ||
9
+ !Number.isSafeInteger(configuredValue) ||
10
+ configuredValue < 1 ||
11
+ configuredValue > maximum)
12
+ throw new TypeError('cause: invalid record configuration');
13
+ return configuredValue;
14
+ }
15
+ /** Read configuration as data so option accessors cannot execute during adapter construction. */
16
+ function recordOptions(options) {
17
+ try {
18
+ const prototype = Object.getPrototypeOf(options);
19
+ if (prototype !== Object.prototype && prototype !== null)
20
+ throw new TypeError();
21
+ return Object.freeze({
22
+ maxCommits: boundedOption((0, recordValidation_js_1.recordOwn)(options, 'maxCommits'), 256, 4096),
23
+ maxFields: boundedOption((0, recordValidation_js_1.recordOwn)(options, 'maxFields'), 20000, 1000000),
24
+ maxEdges: boundedOption((0, recordValidation_js_1.recordOwn)(options, 'maxEdges'), 64, 256),
25
+ maxScopes: boundedOption((0, recordValidation_js_1.recordOwn)(options, 'maxScopes'), 32, 256),
26
+ loadTimeoutMs: boundedOption((0, recordValidation_js_1.recordOwn)(options, 'loadTimeoutMs'), 1000, 30000),
27
+ });
28
+ }
29
+ catch {
30
+ throw new TypeError('cause: invalid record configuration');
31
+ }
32
+ }
33
+ exports.recordOptions = recordOptions;
34
+ /** Spend one shared field budget before canonical readers see any untrusted scope data. */
35
+ function prepareRecordScope(value, address, maximumCommits, maximumFields) {
36
+ if ((0, recordValidation_js_1.addressKey)((0, recordValidation_js_1.addressOf)((0, recordValidation_js_1.recordOwn)(value, 'address'))) !== (0, recordValidation_js_1.addressKey)(address))
37
+ throw new recordValidation_js_1.RecordRefusal();
38
+ const rawLog = (0, recordValidation_js_1.recordList)((0, recordValidation_js_1.recordOwn)(value, 'commitLog'), maximumCommits), budget = { left: maximumFields };
39
+ const log = rawLog.map((_, commitIndex) => (0, recordValidation_js_1.bundleOf)((0, recordValidation_js_1.recordOwn)(rawLog, String(commitIndex)), commitIndex, budget));
40
+ const reads = (0, recordValidation_js_1.mapOf)((0, recordValidation_js_1.recordOwn)(value, 'reads'), maximumCommits, (value) => {
41
+ const readKeys = (0, recordValidation_js_1.names)(value, 128, true);
42
+ (0, recordValidation_js_1.spend)(budget, readKeys.length);
43
+ return readKeys;
44
+ }, budget);
45
+ const control = (0, recordValidation_js_1.mapOf)((0, recordValidation_js_1.recordOwn)(value, 'controlDeps'), maximumCommits, (value) => ({ deciderId: (0, recordValidation_js_1.recordName)((0, recordValidation_js_1.recordOwn)(value, 'deciderId')) }), budget);
46
+ const marks = (0, recordValidation_js_1.mapOf)((0, recordValidation_js_1.recordOwn)(value, 'attention'), maximumCommits, (value) => {
47
+ const attention = (0, recordValidation_js_1.marksOf)(value);
48
+ (0, recordValidation_js_1.spend)(budget, attention.length + attention.reduce((count, mark) => count + mark.evidence.length, 0));
49
+ return attention;
50
+ }, budget);
51
+ return { address, log, reads, control, marks, complete: (0, recordValidation_js_1.recordOwn)(value, 'complete') === true };
52
+ }
53
+ exports.prepareRecordScope = prepareRecordScope;
54
+ /** Preserve both stage reads and stronger per-write prefixes, including their original encounter order. */
55
+ function readKeysForStage(scope, runtimeStageId) {
56
+ // Captured per-write prefixes remain evidence even when a supplied stage-read map omits them.
57
+ return [
58
+ ...new Set([
59
+ ...(scope.reads?.get(runtimeStageId) ?? []),
60
+ ...scope.log
61
+ .filter((bundle) => bundle.runtimeStageId === runtimeStageId)
62
+ .flatMap((bundle) => bundle.trace.flatMap((entry) => entry.readKeys ?? [])),
63
+ ]),
64
+ ];
65
+ }
66
+ exports.readKeysForStage = readKeysForStage;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });