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,185 @@
1
+ import { boundedList, boundedName, EvidenceRefusal, ownData, spendFields } from './admission.js';
2
+ import { isRequirementReason } from './codes.js';
3
+ /** Fixed per-call ceilings; characters are UTF-16 code units, not serialized transport bytes. */
4
+ export const REQUIREMENT_RESULT_LIMITS = Object.freeze({
5
+ results: 512,
6
+ checks: 16,
7
+ affects: 32,
8
+ refs: 16,
9
+ name: 512,
10
+ fields: 32768,
11
+ characters: 1048576,
12
+ });
13
+ /** No input, field value or originating exception is retained on a refusal. */
14
+ export class RequirementValidationError extends EvidenceRefusal {
15
+ constructor(code = 'invalid-data') {
16
+ super(code, 'REQUIREMENT_RESULTS_INVALID');
17
+ }
18
+ }
19
+ const refuse = (code) => new RequirementValidationError(code);
20
+ const descriptors = { translateTrapErrors: true, requireOwnValue: true };
21
+ const own = (value, key) => ownData(value, key, refuse, descriptors);
22
+ const resultKeys = new Set([
23
+ 'id',
24
+ 'requirementRef',
25
+ 'findingRef',
26
+ 'windowRef',
27
+ 'affects',
28
+ 'state',
29
+ 'reason',
30
+ 'detail',
31
+ 'evidenceRefs',
32
+ 'checks',
33
+ 'actionRefs',
34
+ 'validTimeRef',
35
+ 'learnedTimeRef',
36
+ ]);
37
+ const checkKeys = new Set(['checkRef', 'disposition', 'reason', 'detail', 'evidenceRefs', 'attemptRefs']);
38
+ const dispositions = new Set([
39
+ 'checked-pass',
40
+ 'checked-fail',
41
+ 'unreachable',
42
+ 'not-applicable',
43
+ ]);
44
+ /** Closed objects expose no unknown payloads. Never invoke data accessors, iterators or toJSON. */
45
+ function object(value, keys, budget) {
46
+ if (!value || typeof value !== 'object')
47
+ throw refuse();
48
+ let prototype;
49
+ let fields;
50
+ try {
51
+ prototype = Object.getPrototypeOf(value);
52
+ fields = Reflect.ownKeys(value);
53
+ }
54
+ catch {
55
+ throw refuse();
56
+ }
57
+ if (prototype !== Object.prototype && prototype !== null)
58
+ throw refuse();
59
+ if (fields.length > keys.size || fields.some((field) => typeof field !== 'string' || !keys.has(field)))
60
+ throw refuse();
61
+ spendFields(budget, fields.length, refuse);
62
+ // Including optional own fields: an own accessor is never treated as an absent field.
63
+ for (const field of fields)
64
+ own(value, field);
65
+ }
66
+ function list(value, maximum, read, budget) {
67
+ try {
68
+ if (!Array.isArray(value))
69
+ throw refuse();
70
+ }
71
+ catch {
72
+ throw refuse();
73
+ }
74
+ const length = own(value, 'length');
75
+ if (typeof length !== 'number' || !Number.isSafeInteger(length) || length < 0)
76
+ throw refuse();
77
+ if (length > maximum)
78
+ throw refuse('node-limit');
79
+ let prototype;
80
+ let keys;
81
+ try {
82
+ prototype = Object.getPrototypeOf(value);
83
+ keys = Reflect.ownKeys(value);
84
+ }
85
+ catch {
86
+ throw refuse();
87
+ }
88
+ if (prototype !== Array.prototype)
89
+ throw refuse();
90
+ if (keys.length !== length + 1 ||
91
+ keys.some((key) => key !== 'length' && (typeof key !== 'string' || !/^(0|[1-9]\d*)$/.test(key) || Number(key) >= length)))
92
+ throw refuse();
93
+ spendFields(budget, length + 1, refuse);
94
+ return boundedList(value, maximum, read, {
95
+ refuse,
96
+ overflow: 'node-limit',
97
+ freeze: true,
98
+ descriptors,
99
+ expectedLength: length,
100
+ translateArrayErrors: true,
101
+ });
102
+ }
103
+ function name(value, budget) {
104
+ const result = boundedName(value, REQUIREMENT_RESULT_LIMITS.name, refuse);
105
+ if (/\s/u.test(result))
106
+ throw refuse();
107
+ budget.characters -= result.length;
108
+ if (budget.characters < 0)
109
+ throw refuse('node-limit');
110
+ return result;
111
+ }
112
+ function unique(items, key) {
113
+ if (new Set(items.map(key)).size !== items.length)
114
+ throw refuse();
115
+ return items;
116
+ }
117
+ function refs(value, budget, maximum = REQUIREMENT_RESULT_LIMITS.refs) {
118
+ return unique(list(value, maximum, (item) => name(item, budget), budget), (item) => item);
119
+ }
120
+ function reason(value) {
121
+ if (!isRequirementReason(value))
122
+ throw refuse();
123
+ return value;
124
+ }
125
+ function optionalName(value, key, budget) {
126
+ const raw = own(value, key);
127
+ return raw === undefined ? undefined : name(raw, budget);
128
+ }
129
+ function detail(value, budget) {
130
+ const id = optionalName(value, 'detail', budget);
131
+ if (id !== undefined && !/^[A-Za-z][A-Za-z0-9_.-]*:.+$/.test(id))
132
+ throw refuse();
133
+ return id;
134
+ }
135
+ function check(value, budget) {
136
+ object(value, checkKeys, budget);
137
+ const checkRef = name(own(value, 'checkRef'), budget), disposition = own(value, 'disposition');
138
+ if (typeof disposition !== 'string' || !dispositions.has(disposition))
139
+ throw refuse();
140
+ const rawReason = own(value, 'reason'), detailId = detail(value, budget);
141
+ return Object.freeze({
142
+ checkRef,
143
+ disposition: disposition,
144
+ ...(rawReason === undefined ? {} : { reason: reason(rawReason) }),
145
+ ...(detailId === undefined ? {} : { detail: detailId }),
146
+ evidenceRefs: refs(own(value, 'evidenceRefs'), budget),
147
+ attemptRefs: refs(own(value, 'attemptRefs'), budget),
148
+ });
149
+ }
150
+ function result(value, budget) {
151
+ object(value, resultKeys, budget);
152
+ const id = name(own(value, 'id'), budget), requirementRef = name(own(value, 'requirementRef'), budget), findingRef = name(own(value, 'findingRef'), budget), windowRef = name(own(value, 'windowRef'), budget), state = own(value, 'state'), rawReason = own(value, 'reason');
153
+ if (state !== 'satisfied' && state !== 'unresolved')
154
+ throw refuse();
155
+ if (state === 'satisfied' && rawReason !== undefined)
156
+ throw refuse();
157
+ const affects = refs(own(value, 'affects'), budget, REQUIREMENT_RESULT_LIMITS.affects);
158
+ if (!affects.length)
159
+ throw refuse();
160
+ const evidenceRefs = refs(own(value, 'evidenceRefs'), budget), checks = unique(list(own(value, 'checks'), REQUIREMENT_RESULT_LIMITS.checks, (item) => check(item, budget), budget), (item) => item.checkRef), actionRefs = refs(own(value, 'actionRefs'), budget), detailId = detail(value, budget), validTimeRef = optionalName(value, 'validTimeRef', budget), learnedTimeRef = optionalName(value, 'learnedTimeRef', budget);
161
+ const binding = {
162
+ id,
163
+ requirementRef,
164
+ findingRef,
165
+ windowRef,
166
+ affects,
167
+ evidenceRefs,
168
+ checks,
169
+ actionRefs,
170
+ ...(detailId === undefined ? {} : { detail: detailId }),
171
+ ...(validTimeRef === undefined ? {} : { validTimeRef }),
172
+ ...(learnedTimeRef === undefined ? {} : { learnedTimeRef }),
173
+ };
174
+ return Object.freeze(state === 'unresolved' ? { ...binding, state, reason: reason(rawReason) } : { ...binding, state });
175
+ }
176
+ /**
177
+ * Atomically project detached, frozen reference-only records. This is structural admission only.
178
+ * The host must authenticate producers, resolve authorized artifacts, verify current query/run/scope and
179
+ * domain validity, and enforce every unresolved affects reference before rendering any factual claim.
180
+ * Identifier grammar does not classify secrets; producers must supply non-secret identifiers only.
181
+ */
182
+ export function readRequirementResults(value) {
183
+ const budget = { left: REQUIREMENT_RESULT_LIMITS.fields, characters: REQUIREMENT_RESULT_LIMITS.characters };
184
+ return unique(list(value, REQUIREMENT_RESULT_LIMITS.results, (item) => result(item, budget), budget), (item) => item.id);
185
+ }
@@ -2,10 +2,11 @@
2
2
  * types.ts — the record's types: a bundle, its rows, its patches, its encoding, and the
3
3
  * execution tree as the record's readers read it.
4
4
  *
5
- * Imports nothing. The standalone record names nothing outside itself: the engine's frame
5
+ * Imports only the record's leaf attention type. The record names nothing outside itself: the engine's frame
6
6
  * types — the stage snapshot, flow messages and retention dials — remain in FootPrint's
7
7
  * `frameTypes.ts`. The closed record graph is enforced by scripts/check-layering.mjs.
8
8
  */
9
+ import type { Attention } from '../cause/types.js';
9
10
  /** A flat key-value bag representing a state patch (overwrite or merge). */
10
11
  export interface MemoryPatch {
11
12
  [key: string]: any;
@@ -194,6 +195,8 @@ export type CommitValuesMode = 'full' | 'delta';
194
195
  * ```
195
196
  */
196
197
  export interface ExecutionTree {
198
+ /** Optional recorded business attention; absence does not assert health. */
199
+ readonly attention?: readonly Attention[];
197
200
  /** The stage's id (prefixed inside a subflow). `commitStops` reads the root's to place a subflow's seed. */
198
201
  readonly id?: string;
199
202
  /** `[subflowPath/]stageId#executionIndex` — what a reader keys on. A node without one is not a stage. */
@@ -2,7 +2,7 @@
2
2
  * types.ts — the record's types: a bundle, its rows, its patches, its encoding, and the
3
3
  * execution tree as the record's readers read it.
4
4
  *
5
- * Imports nothing. The standalone record names nothing outside itself: the engine's frame
5
+ * Imports only the record's leaf attention type. The record names nothing outside itself: the engine's frame
6
6
  * types — the stage snapshot, flow messages and retention dials — remain in FootPrint's
7
7
  * `frameTypes.ts`. The closed record graph is enforced by scripts/check-layering.mjs.
8
8
  */
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.idPathSegments = exports.joinPath = exports.isExecutionKey = exports.PATH_DELIMITER = exports.EXECUTION_DELIMITER = exports.CommitRangeIndex = exports.HONESTY_CODES = exports.sliceToJSON = exports.sliceForKey = exports.resolveKeysReadSource = exports.keyTimeline = exports.keysReadFromMap = exports.keysReadFromExecutionTree = exports.forwardSliceToJSON = exports.forwardSliceForKey = exports.formatTimeline = exports.formatSlice = exports.formatForwardSlice = exports.elementProvenance = exports.arrayProvenance = exports.formatCausalChain = exports.flattenCausalDAG = exports.causalChain = exports.timeTravel = exports.tagStops = exports.stateAt = exports.splitAxis = exports.readStoredRecord = exports.isCommitBundle = exports.filterStops = exports.commitStopsStrategy = exports.commitStops = exports.recordsPhases = exports.inferLegacyPhases = exports.findLastWriterWithBasis = exports.findLastWriter = exports.findCommits = exports.findCommit = exports.commitValueAtWithBasis = exports.commitValueAt = exports.commitIndexOf = exports.buildCommitIndex = exports.applySmartMerge = exports.UnknownVerbError = exports.parseAttemptId = exports.buildAttemptId = exports.splitStageId = exports.parseRuntimeStageId = exports.createExecutionCounter = exports.buildRuntimeStageId = void 0;
4
- exports.serveRecord = exports.deepEqual = exports.subflowSegmentsOf = exports.stageIdOf = void 0;
4
+ exports.REQUIREMENT_RESULT_LIMITS = exports.RequirementValidationError = exports.readRequirementResults = exports.serveRecord = exports.deepEqual = exports.subflowSegmentsOf = exports.stageIdOf = void 0;
5
5
  var runtimeStageId_js_1 = require("./lib/ids/runtimeStageId.js");
6
6
  Object.defineProperty(exports, "buildRuntimeStageId", { enumerable: true, get: function () { return runtimeStageId_js_1.buildRuntimeStageId; } });
7
7
  Object.defineProperty(exports, "createExecutionCounter", { enumerable: true, get: function () { return runtimeStageId_js_1.createExecutionCounter; } });
@@ -69,3 +69,7 @@ var equality_js_1 = require("./lib/memory/equality.js");
69
69
  Object.defineProperty(exports, "deepEqual", { enumerable: true, get: function () { return equality_js_1.deepEqual; } });
70
70
  var freeze_js_1 = require("./lib/capture/freeze.js");
71
71
  Object.defineProperty(exports, "serveRecord", { enumerable: true, get: function () { return freeze_js_1.serveRecord; } });
72
+ var requirements_js_1 = require("./lib/evidence/requirements.js");
73
+ Object.defineProperty(exports, "readRequirementResults", { enumerable: true, get: function () { return requirements_js_1.readRequirementResults; } });
74
+ Object.defineProperty(exports, "RequirementValidationError", { enumerable: true, get: function () { return requirements_js_1.RequirementValidationError; } });
75
+ Object.defineProperty(exports, "REQUIREMENT_RESULT_LIMITS", { enumerable: true, get: function () { return requirements_js_1.REQUIREMENT_RESULT_LIMITS; } });
@@ -0,0 +1,119 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.attentionList = exports.explain = void 0;
4
+ const session_js_1 = require("./session.js");
5
+ const walk_js_1 = require("./walk.js");
6
+ const validation_js_1 = require("./validation.js");
7
+ const ranking_js_1 = require("./ranking.js");
8
+ /** Read-only deterministic cause localisation and impact. No inference from timestamps or structural ancestry. */
9
+ async function explain(graph, nodeId, options = {}) {
10
+ (0, validation_js_1.graphName)(nodeId);
11
+ const session = new session_js_1.Session(graph, options);
12
+ try {
13
+ const backward = await (0, walk_js_1.walk)(session, [nodeId], 'parents');
14
+ const missing = [...backward.gaps];
15
+ let candidates = (0, walk_js_1.frontier)(backward).map((item) => item.nodeId);
16
+ const rank = (0, validation_js_1.graphOwn)(options, 'rankRoots');
17
+ if (rank !== undefined) {
18
+ if (typeof rank !== 'function')
19
+ throw new TypeError('CAUSE_CONFIGURATION_INVALID');
20
+ const eligible = backward.marked.filter((item) => item.attention.some((mark) => mark.kind !== 'not-collected'));
21
+ const selection = await session.invoke(undefined, () => rank(Object.freeze(eligible), session.context), (raw) => (0, ranking_js_1.admitRanking)(raw, new Set(eligible.map((item) => item.nodeId)), {
22
+ exactPermutation: false,
23
+ maximum: session.limits.maxNodes,
24
+ }));
25
+ if (selection.gap)
26
+ missing.push(selection.gap, (0, validation_js_1.gap)('strategy-failed'));
27
+ else
28
+ candidates = [...(selection.value ?? [])];
29
+ }
30
+ const stopped = session.stopGap(nodeId);
31
+ if (stopped)
32
+ missing.push(stopped);
33
+ const causeComplete = missing.length === 0;
34
+ const rootCause = causeComplete && candidates.length === 1 ? candidates[0] : undefined;
35
+ const forward = await (0, walk_js_1.walk)(session, [nodeId], 'children');
36
+ let impactNodes = Object.freeze(forward.nodes.filter((id) => id !== nodeId));
37
+ const impactGaps = [...forward.gaps], rankImpact = (0, validation_js_1.graphOwn)(options, 'rankImpact');
38
+ if (typeof rankImpact === 'function') {
39
+ const selection = await session.invoke(undefined, () => rankImpact(impactNodes, session.context), (raw) => (0, ranking_js_1.admitRanking)(raw, new Set(impactNodes), {
40
+ exactPermutation: true,
41
+ maximum: session.limits.maxNodes,
42
+ }));
43
+ if (selection.gap)
44
+ impactGaps.push(selection.gap, (0, validation_js_1.gap)('strategy-failed'));
45
+ else
46
+ impactNodes = selection.value ?? impactNodes;
47
+ }
48
+ const impact = Object.freeze({
49
+ nodes: impactNodes,
50
+ edges: forward.edges,
51
+ confidence: impactGaps.length ? 'incomplete' : 'complete',
52
+ gaps: (0, validation_js_1.uniqueGaps)(impactGaps),
53
+ });
54
+ const allGaps = (0, validation_js_1.uniqueGaps)([...missing, ...impactGaps]);
55
+ return Object.freeze({
56
+ nodeId,
57
+ attention: backward.marked.find((item) => item.nodeId === nodeId)?.attention ?? Object.freeze([]),
58
+ ...(rootCause ? { rootCause } : {}),
59
+ path: rootCause ? (0, walk_js_1.pathTo)(backward.edges, nodeId, rootCause) : Object.freeze([]),
60
+ alternatives: Object.freeze(rootCause ? [] : candidates),
61
+ impact,
62
+ confidence: allGaps.length ? 'incomplete' : 'complete',
63
+ gaps: allGaps,
64
+ });
65
+ }
66
+ finally {
67
+ session.dispose();
68
+ }
69
+ }
70
+ exports.explain = explain;
71
+ /** Discover only bounded host seeds; this is not a global scan of arbitrary storage. */
72
+ async function attentionList(graph, options = {}) {
73
+ const session = new session_js_1.Session(graph, options);
74
+ try {
75
+ let seeds = [], missing = [];
76
+ const requested = (0, validation_js_1.graphOwn)(options, 'nodeIds');
77
+ const admitSeeds = (raw) => {
78
+ // Only inspect the bounded prefix; the unread tail is explicitly incomplete.
79
+ if (!Array.isArray(raw))
80
+ return (0, validation_js_1.nodeIds)(raw, session.limits.maxNodes);
81
+ const length = (0, validation_js_1.graphOwn)(raw, 'length');
82
+ if (typeof length !== 'number' || !Number.isSafeInteger(length) || length < 0)
83
+ return (0, validation_js_1.nodeIds)(raw, session.limits.maxNodes);
84
+ if (length > session.limits.maxNodes)
85
+ missing.push((0, validation_js_1.gap)('node-limit'));
86
+ const prefix = [];
87
+ for (let i = 0; i < Math.min(length, session.limits.maxNodes); i++)
88
+ prefix.push((0, validation_js_1.graphOwn)(raw, String(i)));
89
+ return (0, validation_js_1.nodeIds)(prefix, session.limits.maxNodes);
90
+ };
91
+ if (requested !== undefined) {
92
+ const read = await session.invoke(undefined, () => requested, admitSeeds);
93
+ if (read.gap)
94
+ missing.push(read.gap);
95
+ else
96
+ seeds = read.value ?? [];
97
+ }
98
+ else if (session.hasRoots()) {
99
+ const read = await session.read('roots', undefined, admitSeeds);
100
+ if (read.gap)
101
+ missing.push(read.gap);
102
+ else
103
+ seeds = read.value ?? [];
104
+ }
105
+ else
106
+ missing.push((0, validation_js_1.gap)('not-collected'));
107
+ const result = await (0, walk_js_1.walk)(session, seeds, 'children');
108
+ missing = [...missing, ...result.gaps];
109
+ return Object.freeze({
110
+ nodes: result.marked,
111
+ confidence: missing.length ? 'incomplete' : 'complete',
112
+ gaps: (0, validation_js_1.uniqueGaps)(missing),
113
+ });
114
+ }
115
+ finally {
116
+ session.dispose();
117
+ }
118
+ }
119
+ exports.attentionList = attentionList;
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.endQueryMemo = exports.queryMemo = exports.beginQueryMemo = void 0;
4
+ /** Share bounded derived work within one query without retaining it in the graph's storage cache. */
5
+ class QueryMemo {
6
+ maximumEntries;
7
+ owners = new Map();
8
+ entries = 0;
9
+ closed = false;
10
+ constructor(maximumEntries) {
11
+ this.maximumEntries = maximumEntries;
12
+ }
13
+ remember(owner, key, create) {
14
+ const previous = this.owners.get(owner);
15
+ if (previous?.has(key))
16
+ return previous.get(key);
17
+ if (this.closed || this.entries >= this.maximumEntries)
18
+ return create();
19
+ const value = create();
20
+ // Creation can finish after disposal or re-enter other readers; neither may refill a closed/full memo.
21
+ if (!this.closed && this.entries < this.maximumEntries) {
22
+ const bucket = this.owners.get(owner) ?? new Map();
23
+ if (!bucket.has(key))
24
+ this.entries++;
25
+ bucket.set(key, value);
26
+ this.owners.set(owner, bucket);
27
+ }
28
+ return value;
29
+ }
30
+ close() {
31
+ this.closed = true;
32
+ this.owners.clear();
33
+ this.entries = 0;
34
+ }
35
+ }
36
+ const queries = new WeakMap();
37
+ /** Reserve one resolution and one inspection per admitted node, shared across all adapter owners. */
38
+ function beginQueryMemo(context, maximumNodes) {
39
+ queries.set(context, new QueryMemo(maximumNodes * 2));
40
+ }
41
+ exports.beginQueryMemo = beginQueryMemo;
42
+ /** Direct graph calls have no managed query lifetime and deliberately remain uncached. */
43
+ function queryMemo(context) {
44
+ return queries.get(context);
45
+ }
46
+ exports.queryMemo = queryMemo;
47
+ /** Clear synchronously without aborting an otherwise successful query's public signal. */
48
+ function endQueryMemo(context) {
49
+ queries.get(context)?.close();
50
+ queries.delete(context);
51
+ }
52
+ exports.endQueryMemo = endQueryMemo;
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.admitRanking = void 0;
4
+ const validation_js_1 = require("./validation.js");
5
+ /** Strategies may only select admitted nodes; impact ranking must preserve the full set. */
6
+ function admitRanking(raw, allowed, options) {
7
+ const ids = (0, validation_js_1.nodeIds)(raw, options.maximum);
8
+ if ((options.exactPermutation && ids.length !== allowed.size) ||
9
+ new Set(ids).size !== ids.length ||
10
+ ids.some((id) => !allowed.has(id)))
11
+ throw new TypeError();
12
+ return ids;
13
+ }
14
+ exports.admitRanking = admitRanking;
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createRecordGraph = exports.createLazyRecordGraph = exports.recordOptions = void 0;
4
+ const recordChildren_js_1 = require("./recordChildren.js");
5
+ const recordCoverage_js_1 = require("./recordCoverage.js");
6
+ const recordIdentity_js_1 = require("./recordIdentity.js");
7
+ const recordLoading_js_1 = require("./recordLoading.js");
8
+ const recordQuery_js_1 = require("./recordQuery.js");
9
+ const recordScope_js_1 = require("./recordScope.js");
10
+ const recordValidation_js_1 = require("./recordValidation.js");
11
+ /** Give each failed port its own incomplete page rather than sharing a mutable edge array. */
12
+ const unavailableAdjacency = () => ({ edges: [], complete: false });
13
+ var recordScope_js_2 = require("./recordScope.js");
14
+ Object.defineProperty(exports, "recordOptions", { enumerable: true, get: function () { return recordScope_js_2.recordOptions; } });
15
+ /** Common engine for memory and one-scope-at-a-time host storage. Loader functions are trusted executable code. */
16
+ function createLazyRecordGraph(load, options = {}) {
17
+ const limits = (0, recordScope_js_1.recordOptions)(options), loader = (0, recordLoading_js_1.createRecordScopeLoader)(load, limits);
18
+ const query = (0, recordQuery_js_1.createRecordQuery)(loader, limits.maxEdges);
19
+ return Object.freeze({
20
+ nodeId: (address, node) => (0, recordIdentity_js_1.nodeIdFor)(address, node),
21
+ openScope: (address, context) => (0, recordLoading_js_1.openRecordScope)(address, context, loader),
22
+ async parents(nodeId, context) {
23
+ return (0, recordQuery_js_1.whenResolved)(await query.resolve(nodeId, context), (node) => query.inspect(node, context).adjacency, unavailableAdjacency);
24
+ },
25
+ async gaps(nodeId, context) {
26
+ return (0, recordQuery_js_1.whenResolved)(await query.resolve(nodeId, context), (node) => query.inspect(node, context).gaps, (failure) => (0, recordCoverage_js_1.gapForRecordFailure)(failure.failure));
27
+ },
28
+ async attention(nodeId, context) {
29
+ return (0, recordQuery_js_1.whenResolved)(await query.resolve(nodeId, context), recordCoverage_js_1.attentionForRecordNode, () => []);
30
+ },
31
+ async children(nodeId, context) {
32
+ return (0, recordQuery_js_1.whenResolved)(await query.resolve(nodeId, context), (node) => (0, recordChildren_js_1.childrenForRecordNode)(node, limits.maxEdges, query.inspect(node, context)), unavailableAdjacency);
33
+ },
34
+ });
35
+ }
36
+ exports.createLazyRecordGraph = createLazyRecordGraph;
37
+ /** A finished record already in memory; payload admission is lazy per requested scope. */
38
+ function createRecordGraph(scopes, options = {}) {
39
+ const limits = (0, recordScope_js_1.recordOptions)(options);
40
+ return createLazyRecordGraph((address) => {
41
+ for (const raw of (0, recordValidation_js_1.recordList)(scopes, limits.maxScopes)) {
42
+ if ((0, recordValidation_js_1.addressKey)((0, recordValidation_js_1.addressOf)((0, recordValidation_js_1.recordOwn)(raw, 'address'))) === (0, recordValidation_js_1.addressKey)(address))
43
+ return { scope: raw };
44
+ }
45
+ throw new recordValidation_js_1.RecordRefusal('unavailable');
46
+ }, limits);
47
+ }
48
+ exports.createRecordGraph = createRecordGraph;
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.childrenForRecordNode = void 0;
4
+ const forwardSliceForKey_js_1 = require("../slice/forwardSliceForKey.js");
5
+ const recordControl_js_1 = require("./recordControl.js");
6
+ const recordCoverage_js_1 = require("./recordCoverage.js");
7
+ const recordIdentity_js_1 = require("./recordIdentity.js");
8
+ const recordScope_js_1 = require("./recordScope.js");
9
+ /** Keep per-write and conservative fed edges distinct while retaining existing cap and deduplication order. */
10
+ function appendFedChildren(scope, root, maximumEdges, edges) {
11
+ let complete = true;
12
+ for (const edge of root.fedEdges) {
13
+ const child = edge.child;
14
+ if (child.commitIdx === undefined || !child.runtimeStageId) {
15
+ complete = false;
16
+ continue;
17
+ }
18
+ if (edges.length >= maximumEdges) {
19
+ complete = false;
20
+ break;
21
+ }
22
+ const nodeId = (0, recordIdentity_js_1.nodeIdFor)(scope.address, {
23
+ runtimeStageId: child.runtimeStageId,
24
+ commitIdx: child.commitIdx,
25
+ key: child.key,
26
+ });
27
+ if (!edges.some((existing) => existing.nodeId === nodeId))
28
+ edges.push({ nodeId, kind: 'data', attribution: edge.basis, evidence: ['commit:' + child.commitIdx] });
29
+ if (edge.basis === 'stage')
30
+ complete = false;
31
+ }
32
+ return complete;
33
+ }
34
+ /** A consumer with no writes still needs a qualified node so its recorded control flow remains reachable. */
35
+ function appendReadOnlyChildren(scope, root, key, maximumEdges, edges) {
36
+ let complete = true;
37
+ for (const read of root.reads) {
38
+ if (scope.log[read.commitIdx]?.trace.length)
39
+ continue;
40
+ if (edges.length >= maximumEdges) {
41
+ complete = false;
42
+ break;
43
+ }
44
+ const nodeId = (0, recordIdentity_js_1.nodeIdFor)(scope.address, { runtimeStageId: read.runtimeStageId, commitIdx: read.commitIdx, key }, true);
45
+ if (!edges.some((existing) => existing.nodeId === nodeId))
46
+ edges.push({ nodeId, kind: 'data', attribution: 'per-write', evidence: ['commit:' + read.commitIdx] });
47
+ }
48
+ return complete;
49
+ }
50
+ /** Query each written value at its original commit boundary instead of the value's latest writer. */
51
+ function appendDataChildren(scope, identity, commitIndex, maximumEdges, edges) {
52
+ let complete = true;
53
+ const lookup = (runtimeStageId) => (0, recordScope_js_1.readKeysForStage)(scope, runtimeStageId);
54
+ for (const key of (0, recordCoverage_js_1.writtenKeysForNode)(scope.log[commitIndex], identity)) {
55
+ const forward = (0, forwardSliceForKey_js_1.forwardSliceForKey)(scope.log, key, lookup, {
56
+ before: commitIndex + 1,
57
+ maxDepth: 1,
58
+ maxNodes: maximumEdges + 1,
59
+ });
60
+ if (forward.root?.truncated?.byNodes)
61
+ complete = false;
62
+ if (forward.root?.commitIdx !== commitIndex) {
63
+ complete = false;
64
+ continue;
65
+ }
66
+ complete = appendFedChildren(scope, forward.root, maximumEdges, edges) && complete;
67
+ complete = appendReadOnlyChildren(scope, forward.root, key, maximumEdges, edges) && complete;
68
+ }
69
+ return complete;
70
+ }
71
+ /** Share parent-side coverage checks before appending data then control children in their established order. */
72
+ function childrenForRecordNode(node, maximumEdges, inspection) {
73
+ const { scope, identity, commitIndex } = node;
74
+ const edges = [];
75
+ let complete = inspection.gaps.length === 0;
76
+ if (!(0, recordCoverage_js_1.allStageReadsCollected)(scope))
77
+ complete = false;
78
+ complete = appendDataChildren(scope, identity, commitIndex, maximumEdges, edges) && complete;
79
+ complete = (0, recordControl_js_1.appendControlChildren)(scope, identity, commitIndex, maximumEdges, edges) && complete;
80
+ return { edges, complete };
81
+ }
82
+ exports.childrenForRecordNode = childrenForRecordNode;
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.appendControlChildren = exports.collectControlChildCoverage = exports.collectControlParentCoverage = exports.controlParentLookup = exports.controlChildren = void 0;
4
+ const recordIdentity_js_1 = require("./recordIdentity.js");
5
+ /** Only an execution or captured read may govern control flow; a written key alone cannot. */
6
+ function controlChildren(scope, identity) {
7
+ // A read-only execution may govern later work; a particular written key is not itself a decision.
8
+ return identity.key === undefined || identity.read ? scope.control ?? [] : [];
9
+ }
10
+ exports.controlChildren = controlChildren;
11
+ /** Canonical slicing receives only control parents that existed before the dependent execution. */
12
+ function controlParentLookup(scope) {
13
+ return scope.control === undefined
14
+ ? undefined
15
+ : (runtimeStageId) => {
16
+ const dependency = scope.control.get(runtimeStageId);
17
+ if (!dependency)
18
+ return undefined;
19
+ const parentIndex = scope.log.findIndex((bundle) => bundle.runtimeStageId === dependency.deciderId), childIndex = scope.log.findIndex((bundle) => bundle.runtimeStageId === runtimeStageId);
20
+ return parentIndex >= 0 && parentIndex < childIndex ? dependency : undefined;
21
+ };
22
+ }
23
+ exports.controlParentLookup = controlParentLookup;
24
+ /** Keep missing or future deciders visible rather than silently dropping their recorded dependency. */
25
+ function collectControlParentCoverage(scope, identity, firstCommitIndex, addGap) {
26
+ const dependency = scope.control?.get(identity.runtimeStageId);
27
+ if (!dependency)
28
+ return;
29
+ const parentIndex = scope.log.findIndex((bundle) => bundle.runtimeStageId === dependency.deciderId);
30
+ if (parentIndex < 0)
31
+ addGap('scope-boundary');
32
+ else if (parentIndex >= firstCommitIndex)
33
+ addGap('invalid-data');
34
+ }
35
+ exports.collectControlParentCoverage = collectControlParentCoverage;
36
+ /** Include control fan-out in the same immediate-neighbor cap used for data adjacency. */
37
+ function collectControlChildCoverage(scope, identity, firstCommitIndex, neighbors, addGap) {
38
+ for (const [childStageId, dependency] of controlChildren(scope, identity))
39
+ if (dependency.deciderId === identity.runtimeStageId) {
40
+ const childIndex = scope.log.findIndex((bundle) => bundle.runtimeStageId === childStageId);
41
+ if (childIndex < 0)
42
+ addGap('scope-boundary');
43
+ else if (childIndex <= firstCommitIndex)
44
+ addGap('invalid-data');
45
+ else
46
+ neighbors.add(JSON.stringify(['control', childStageId]));
47
+ }
48
+ }
49
+ exports.collectControlChildCoverage = collectControlChildCoverage;
50
+ /** Preserve recorded control order and coverage when a dependent execution is absent or capped. */
51
+ function appendControlChildren(scope, identity, commitIndex, maximumEdges, edges) {
52
+ let complete = true;
53
+ for (const [childStageId, dependency] of controlChildren(scope, identity)) {
54
+ if (dependency.deciderId !== identity.runtimeStageId)
55
+ continue;
56
+ const childIndex = scope.log.findIndex((bundle) => bundle.runtimeStageId === childStageId);
57
+ if (childIndex <= commitIndex) {
58
+ complete = false;
59
+ continue;
60
+ }
61
+ if (edges.length >= maximumEdges) {
62
+ complete = false;
63
+ break;
64
+ }
65
+ edges.push({
66
+ nodeId: (0, recordIdentity_js_1.nodeIdFor)(scope.address, { runtimeStageId: childStageId, commitIdx: childIndex }),
67
+ kind: 'control',
68
+ attribution: 'recorded-control',
69
+ evidence: ['commit:' + childIndex],
70
+ });
71
+ }
72
+ return complete;
73
+ }
74
+ exports.appendControlChildren = appendControlChildren;