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,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,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.own)(options, 'maxCommits'), 256, 4096),
23
+ maxFields: boundedOption((0, recordValidation_js_1.own)(options, 'maxFields'), 20000, 1000000),
24
+ maxEdges: boundedOption((0, recordValidation_js_1.own)(options, 'maxEdges'), 64, 256),
25
+ maxScopes: boundedOption((0, recordValidation_js_1.own)(options, 'maxScopes'), 32, 256),
26
+ loadTimeoutMs: boundedOption((0, recordValidation_js_1.own)(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.own)(value, 'address'))) !== (0, recordValidation_js_1.addressKey)(address))
37
+ throw new recordValidation_js_1.RecordRefusal();
38
+ const rawLog = (0, recordValidation_js_1.list)((0, recordValidation_js_1.own)(value, 'commitLog'), maximumCommits), budget = { left: maximumFields };
39
+ const log = rawLog.map((_, commitIndex) => (0, recordValidation_js_1.bundleOf)((0, recordValidation_js_1.own)(rawLog, String(commitIndex)), commitIndex, budget));
40
+ const reads = (0, recordValidation_js_1.mapOf)((0, recordValidation_js_1.own)(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.own)(value, 'controlDeps'), maximumCommits, (value) => ({ deciderId: (0, recordValidation_js_1.name)((0, recordValidation_js_1.own)(value, 'deciderId')) }), budget);
46
+ const marks = (0, recordValidation_js_1.mapOf)((0, recordValidation_js_1.own)(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.own)(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 });
@@ -0,0 +1,213 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.projectLoaded = exports.mapOf = exports.bundleOf = exports.dataCopy = exports.addressKey = exports.spend = exports.addressOf = exports.list = exports.name = exports.names = exports.own = exports.RecordRefusal = exports.marksOf = void 0;
4
+ const validation_js_1 = require("./validation.js");
5
+ Object.defineProperty(exports, "marksOf", { enumerable: true, get: function () { return validation_js_1.attention; } });
6
+ const admission_js_1 = require("./admission.js");
7
+ /** Loader refusals stay distinct from graph-port refusals so a failed host load keeps its original classification. */
8
+ class RecordRefusal extends admission_js_1.EvidenceRefusal {
9
+ code;
10
+ constructor(code = 'invalid-data') {
11
+ super(code, 'cause: record unavailable');
12
+ this.code = code;
13
+ }
14
+ }
15
+ exports.RecordRefusal = RecordRefusal;
16
+ const refuseRecord = (code) => new RecordRefusal(code);
17
+ const recordDescriptors = { translateTrapErrors: true, requireOwnValue: true };
18
+ function own(value, key) {
19
+ return (0, admission_js_1.ownData)(value, key, refuseRecord, recordDescriptors);
20
+ }
21
+ exports.own = own;
22
+ function names(value, maximum = 128, path = false) {
23
+ return list(value, maximum).map((item) => name(item, path));
24
+ }
25
+ exports.names = names;
26
+ function name(value, path = false) {
27
+ return (0, admission_js_1.boundedName)(value, 1024, refuseRecord, path);
28
+ }
29
+ exports.name = name;
30
+ function list(value, maximum) {
31
+ return (0, admission_js_1.boundedList)(value, maximum, (item) => item, {
32
+ refuse: refuseRecord,
33
+ overflow: 'node-limit',
34
+ freeze: false,
35
+ descriptors: recordDescriptors,
36
+ });
37
+ }
38
+ exports.list = list;
39
+ function addressOf(value) {
40
+ const logRunId = name(own(value, 'logRunId')), drillPath = names(own(value, 'drillPath'), 16);
41
+ if (logRunId.length + drillPath.reduce((n, s) => n + s.length, 0) > 1536)
42
+ throw new RecordRefusal();
43
+ return Object.freeze({ logRunId, drillPath: Object.freeze(drillPath) });
44
+ }
45
+ exports.addressOf = addressOf;
46
+ function spend(budget, count) {
47
+ (0, admission_js_1.spendFields)(budget, count, refuseRecord);
48
+ }
49
+ exports.spend = spend;
50
+ const addressKey = (a) => JSON.stringify([a.logRunId, ...a.drillPath]);
51
+ exports.addressKey = addressKey;
52
+ /** Iterative bounded copy: data descriptors only, no getters/toJSON/prototypes or opaque class instances. */
53
+ function dataCopy(value, budget) {
54
+ const leaf = (v) => v === null || typeof v !== 'object';
55
+ const check = (v) => {
56
+ spend(budget, 1);
57
+ if (typeof v === 'function' || typeof v === 'symbol' || (typeof v === 'string' && v.length > 65536))
58
+ throw new RecordRefusal();
59
+ };
60
+ check(value);
61
+ if (leaf(value))
62
+ return value;
63
+ const make = (v) => {
64
+ let p;
65
+ try {
66
+ p = Object.getPrototypeOf(v);
67
+ }
68
+ catch {
69
+ throw new RecordRefusal();
70
+ }
71
+ if (!Array.isArray(v) && p !== Object.prototype && p !== null)
72
+ throw new RecordRefusal();
73
+ if (Array.isArray(v)) {
74
+ const length = own(v, 'length');
75
+ if (typeof length !== 'number' || !Number.isSafeInteger(length) || length < 0)
76
+ throw new RecordRefusal();
77
+ spend(budget, length);
78
+ const array = [];
79
+ array.length = length;
80
+ return array;
81
+ }
82
+ return Object.create(null);
83
+ };
84
+ const root = make(value), seen = new Set([value]), work = [{ from: value, to: root }];
85
+ while (work.length) {
86
+ const { from, to } = work.pop();
87
+ let keys;
88
+ try {
89
+ keys = Object.keys(from);
90
+ }
91
+ catch {
92
+ throw new RecordRefusal();
93
+ }
94
+ if (keys.length > budget.left)
95
+ throw new RecordRefusal('node-limit');
96
+ for (const key of keys) {
97
+ if (key.length > 1024)
98
+ throw new RecordRefusal('node-limit');
99
+ const v = own(from, key);
100
+ check(v);
101
+ let copy = v;
102
+ if (!leaf(v)) {
103
+ if (seen.has(v))
104
+ throw new RecordRefusal();
105
+ seen.add(v);
106
+ copy = make(v);
107
+ work.push({ from: v, to: copy });
108
+ }
109
+ Object.defineProperty(to, key, { value: copy, enumerable: true, writable: true, configurable: true });
110
+ }
111
+ }
112
+ return root;
113
+ }
114
+ exports.dataCopy = dataCopy;
115
+ function bundleOf(value, idx, budget) {
116
+ const runtimeStageId = name(own(value, 'runtimeStageId')), stageId = name(own(value, 'stageId'));
117
+ const trace = list(own(value, 'trace'), 128).map((_, i) => {
118
+ const t = own(own(value, 'trace'), String(i)), path = name(own(t, 'path'), true), verb = own(t, 'verb');
119
+ if (!['set', 'merge', 'append', 'delete'].includes(verb))
120
+ throw new RecordRefusal();
121
+ const readKeys = own(t, 'readKeys');
122
+ return { path, verb: verb, ...(readKeys === undefined ? {} : { readKeys: names(readKeys, 128, true) }) };
123
+ });
124
+ spend(budget, trace.length + trace.reduce((n, t) => n + (t.readKeys?.length ?? 0), 0) + 1);
125
+ const overwrite = dataCopy(own(value, 'overwrite'), budget), updates = dataCopy(own(value, 'updates'), budget);
126
+ if (!overwrite ||
127
+ typeof overwrite !== 'object' ||
128
+ Array.isArray(overwrite) ||
129
+ !updates ||
130
+ typeof updates !== 'object' ||
131
+ Array.isArray(updates))
132
+ throw new RecordRefusal();
133
+ const redactedPaths = names(own(value, 'redactedPaths') ?? [], 128, true);
134
+ const untracked = own(value, 'untrackedSources'), phase = own(value, 'phase');
135
+ spend(budget, redactedPaths.length + (untracked === undefined ? 0 : list(untracked, 128).length));
136
+ return {
137
+ idx,
138
+ stage: stageId,
139
+ stageId,
140
+ runtimeStageId,
141
+ trace,
142
+ overwrite,
143
+ updates,
144
+ redactedPaths,
145
+ ...(untracked === undefined
146
+ ? {}
147
+ : {
148
+ untrackedSources: names(untracked).map((s) => {
149
+ if (!['args', 'env', 'silent'].includes(s))
150
+ throw new RecordRefusal();
151
+ return s;
152
+ }),
153
+ }),
154
+ ...(phase === undefined
155
+ ? {}
156
+ : {
157
+ phase: (() => {
158
+ if (phase !== 'exit' && phase !== 'repeat')
159
+ throw new RecordRefusal();
160
+ return phase;
161
+ })(),
162
+ }),
163
+ };
164
+ }
165
+ exports.bundleOf = bundleOf;
166
+ function mapOf(value, limit, read, budget) {
167
+ if (value === undefined)
168
+ return undefined;
169
+ if (!value || typeof value !== 'object' || Array.isArray(value))
170
+ throw new RecordRefusal();
171
+ let keys;
172
+ try {
173
+ keys = Object.keys(value);
174
+ }
175
+ catch {
176
+ throw new RecordRefusal();
177
+ }
178
+ if (keys.length > limit)
179
+ throw new RecordRefusal('node-limit');
180
+ if (budget)
181
+ spend(budget, keys.length);
182
+ return new Map(keys.map((k) => [name(k), read(own(value, k))]));
183
+ }
184
+ exports.mapOf = mapOf;
185
+ /** Observe native promises by intrinsic without assimilating a data object's then accessor. */
186
+ function projectLoaded(work, project) {
187
+ return new Promise((resolve, reject) => {
188
+ const accept = (value) => {
189
+ try {
190
+ resolve(project(value));
191
+ }
192
+ catch (error) {
193
+ reject(error);
194
+ }
195
+ };
196
+ let returned;
197
+ try {
198
+ returned = work();
199
+ }
200
+ catch (error) {
201
+ reject(error);
202
+ return;
203
+ }
204
+ try {
205
+ if (!(0, validation_js_1.observePromise)(returned, (value) => accept(value), reject))
206
+ accept(returned);
207
+ }
208
+ catch (error) {
209
+ reject(error instanceof validation_js_1.AdmissionError ? new RecordRefusal(error.code) : error);
210
+ }
211
+ });
212
+ }
213
+ exports.projectLoaded = projectLoaded;
@@ -0,0 +1,165 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Session = void 0;
4
+ const validation_js_1 = require("./validation.js");
5
+ const defaults = { maxNodes: 100, maxDepth: 20, maxEdges: 1000, maxCalls: 1000, timeoutMs: 1000 };
6
+ const ceilings = { maxNodes: 10000, maxDepth: 1000, maxEdges: 100000, maxCalls: 100000, timeoutMs: 30000 };
7
+ /** One deadline, memo table and work budget per public query, never per graph or store. */
8
+ class Session {
9
+ limits;
10
+ context;
11
+ nodes = new Set();
12
+ controller = new AbortController();
13
+ cache = new Map();
14
+ calls = 0;
15
+ edges = 0;
16
+ timedOut = false;
17
+ deadline;
18
+ timer;
19
+ external;
20
+ onAbort = () => this.controller.abort();
21
+ methods = {};
22
+ constructor(graph, options = {}) {
23
+ try {
24
+ (0, validation_js_1.plain)(options);
25
+ const limits = { ...defaults };
26
+ for (const key of Object.keys(defaults)) {
27
+ const value = (0, validation_js_1.own)(options, key);
28
+ if (value !== undefined) {
29
+ if (typeof value !== 'number' ||
30
+ !Number.isSafeInteger(value) ||
31
+ value < (key === 'maxDepth' ? 0 : 1) ||
32
+ value > ceilings[key])
33
+ throw new TypeError();
34
+ limits[key] = value;
35
+ }
36
+ }
37
+ this.limits = Object.freeze(limits);
38
+ const rank = (0, validation_js_1.own)(options, 'rankRoots');
39
+ if (rank !== undefined && typeof rank !== 'function')
40
+ throw new TypeError();
41
+ const impactRank = (0, validation_js_1.own)(options, 'rankImpact');
42
+ if (impactRank !== undefined && typeof impactRank !== 'function')
43
+ throw new TypeError();
44
+ const signal = (0, validation_js_1.own)(options, 'signal');
45
+ if (signal !== undefined && !(signal instanceof AbortSignal))
46
+ throw new TypeError();
47
+ this.external = signal;
48
+ for (const key of ['parents', 'children', 'attention', 'gaps', 'roots']) {
49
+ let cursor = graph, method;
50
+ for (let depth = 0; cursor && cursor !== Object.prototype && depth < 8; depth++, cursor = Object.getPrototypeOf(cursor)) {
51
+ const field = Object.getOwnPropertyDescriptor(cursor, key);
52
+ if (field) {
53
+ if (!('value' in field))
54
+ throw new TypeError();
55
+ method = field.value;
56
+ break;
57
+ }
58
+ }
59
+ if (typeof method !== 'function' && key !== 'roots')
60
+ throw new TypeError();
61
+ if (typeof method === 'function')
62
+ this.methods[key] = method.bind(graph);
63
+ }
64
+ }
65
+ catch {
66
+ throw new TypeError('CAUSE_CONFIGURATION_INVALID');
67
+ }
68
+ this.context = Object.freeze({ signal: this.controller.signal });
69
+ this.deadline = performance.now() + this.limits.timeoutMs;
70
+ this.external?.addEventListener('abort', this.onAbort, { once: true });
71
+ if (this.external?.aborted)
72
+ this.onAbort();
73
+ this.timer = setTimeout(() => {
74
+ this.timedOut = true;
75
+ this.controller.abort();
76
+ }, this.limits.timeoutMs);
77
+ }
78
+ dispose() {
79
+ clearTimeout(this.timer);
80
+ this.external?.removeEventListener('abort', this.onAbort);
81
+ }
82
+ stopGap(id) {
83
+ // Resolved Promise chains can starve timers. Check the monotonic deadline too.
84
+ if (!this.controller.signal.aborted && performance.now() >= this.deadline) {
85
+ this.timedOut = true;
86
+ this.controller.abort();
87
+ }
88
+ return this.controller.signal.aborted ? (0, validation_js_1.gap)(this.timedOut ? 'timeout' : 'cancelled', id) : undefined;
89
+ }
90
+ admit(id) {
91
+ (0, validation_js_1.name)(id);
92
+ if (this.nodes.has(id))
93
+ return undefined;
94
+ if (this.nodes.size >= this.limits.maxNodes)
95
+ return (0, validation_js_1.gap)('node-limit', id);
96
+ this.nodes.add(id);
97
+ return undefined;
98
+ }
99
+ hasRoots() {
100
+ return !!this.methods.roots;
101
+ }
102
+ read(method, id, project) {
103
+ const key = JSON.stringify([method, id]);
104
+ const cached = this.cache.get(key);
105
+ if (cached)
106
+ return cached;
107
+ const pending = this.invoke(id, () => {
108
+ const fn = this.methods[method];
109
+ if (!fn)
110
+ throw new validation_js_1.AdmissionError('not-collected');
111
+ return id === undefined ? fn(this.context) : fn(id, this.context);
112
+ }, project);
113
+ this.cache.set(key, pending);
114
+ return pending;
115
+ }
116
+ invoke(id, work, project) {
117
+ const stopped = this.stopGap(id);
118
+ if (stopped)
119
+ return Promise.resolve({ gap: stopped });
120
+ if (++this.calls > this.limits.maxCalls)
121
+ return Promise.resolve({ gap: (0, validation_js_1.gap)('call-limit', id) });
122
+ return new Promise((resolve) => {
123
+ let finished = false;
124
+ const finish = (result) => {
125
+ if (finished)
126
+ return;
127
+ finished = true;
128
+ this.controller.signal.removeEventListener('abort', abort);
129
+ resolve(result);
130
+ };
131
+ const abort = () => finish({ gap: this.stopGap(id) ?? (0, validation_js_1.gap)('cancelled', id) });
132
+ const accept = (raw) => {
133
+ if (finished)
134
+ return;
135
+ const stopped = this.stopGap(id);
136
+ if (stopped) {
137
+ finish({ gap: stopped });
138
+ return;
139
+ }
140
+ try {
141
+ const value = project(raw), expired = this.stopGap(id);
142
+ finish(expired ? { gap: expired } : { value });
143
+ }
144
+ catch (error) {
145
+ finish({ gap: (0, validation_js_1.gap)(error instanceof validation_js_1.AdmissionError ? error.code : 'invalid-data', id) });
146
+ }
147
+ };
148
+ this.controller.signal.addEventListener('abort', abort, { once: true });
149
+ if (this.controller.signal.aborted) {
150
+ abort();
151
+ return;
152
+ }
153
+ try {
154
+ const returned = work();
155
+ // Observe native Promises by intrinsic, never assimilate a payload's `then` getter.
156
+ if (!(0, validation_js_1.observePromise)(returned, accept, () => finish({ gap: (0, validation_js_1.gap)('unavailable', id) })))
157
+ accept(returned);
158
+ }
159
+ catch (error) {
160
+ finish({ gap: (0, validation_js_1.gap)(error instanceof validation_js_1.AdmissionError ? error.code : 'unavailable', id) });
161
+ }
162
+ });
163
+ }
164
+ }
165
+ exports.Session = Session;
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createStoredRecordGraph = void 0;
4
+ const storedRecord_js_1 = require("../time-travel/storedRecord.js");
5
+ const recordAdapter_js_1 = require("./recordAdapter.js");
6
+ const recordValidation_js_1 = require("./recordValidation.js");
7
+ /** Load one address only. The host owns auth/storage; cancellation is forwarded and waiting is independently bounded. */
8
+ function createStoredRecordGraph(host, options = {}) {
9
+ const limits = (0, recordAdapter_js_1.recordOptions)(options);
10
+ return (0, recordAdapter_js_1.createLazyRecordGraph)((address, context) => (0, recordValidation_js_1.projectLoaded)(() => host.loadLog(address, context), (loaded) => {
11
+ const max = limits.maxCommits, budget = { left: limits.maxFields };
12
+ const raw = (0, recordValidation_js_1.list)((0, recordValidation_js_1.own)(loaded, 'entries'), max * 4 + 16), entries = [];
13
+ for (let i = 0; i < raw.length; i++) {
14
+ if (context.signal.aborted)
15
+ throw new recordValidation_js_1.RecordRefusal('cancelled');
16
+ const entry = (0, recordValidation_js_1.own)(raw, String(i)), a = (0, recordValidation_js_1.addressOf)((0, recordValidation_js_1.own)(entry, 'address'));
17
+ if ((0, recordValidation_js_1.addressKey)(a) !== (0, recordValidation_js_1.addressKey)(address))
18
+ throw new recordValidation_js_1.RecordRefusal();
19
+ const kind = (0, recordValidation_js_1.own)(entry, 'kind'), at = (0, recordValidation_js_1.own)(entry, 'at');
20
+ if (typeof at !== 'number' || !Number.isFinite(at))
21
+ throw new recordValidation_js_1.RecordRefusal();
22
+ if (kind === 'commit') {
23
+ const b = (0, recordValidation_js_1.own)(entry, 'bundle'), idx = (0, recordValidation_js_1.own)(b, 'idx');
24
+ if (!Number.isSafeInteger(idx) || idx < 0 || idx >= max)
25
+ throw new recordValidation_js_1.RecordRefusal('node-limit');
26
+ entries.push({ kind, address: a, at, bundle: (0, recordValidation_js_1.bundleOf)(b, idx, budget) });
27
+ }
28
+ else if (kind === 'opened')
29
+ entries.push({ kind, address: a, at, initialState: (0, recordValidation_js_1.dataCopy)((0, recordValidation_js_1.own)(entry, 'initialState'), budget) });
30
+ else if (kind === 'closed')
31
+ entries.push({ kind, address: a, at, outcome: (0, recordValidation_js_1.own)(entry, 'outcome'), commits: (0, recordValidation_js_1.own)(entry, 'commits') });
32
+ else if (kind === 'note') {
33
+ /* opaque note bodies never enter dependency projection */
34
+ }
35
+ else
36
+ throw new recordValidation_js_1.RecordRefusal();
37
+ }
38
+ const record = (0, storedRecord_js_1.readStoredRecord)(entries, {
39
+ entries: max * 4 + 16,
40
+ missingCommits: max,
41
+ drillDepth: 16,
42
+ addressLength: 1536,
43
+ }), log = record.log(address);
44
+ if (!log)
45
+ throw new recordValidation_js_1.RecordRefusal();
46
+ if (log.gaps.length)
47
+ throw new recordValidation_js_1.RecordRefusal('not-collected');
48
+ return {
49
+ scope: {
50
+ address,
51
+ commitLog: log.commitLog,
52
+ reads: (0, recordValidation_js_1.own)(loaded, 'reads'),
53
+ controlDeps: (0, recordValidation_js_1.own)(loaded, 'controlDeps'),
54
+ attention: (0, recordValidation_js_1.own)(loaded, 'attention'),
55
+ complete: log.complete,
56
+ },
57
+ gaps: record.unread.length || log.gaps.length ? ['not-collected'] : [],
58
+ };
59
+ }), limits);
60
+ }
61
+ exports.createStoredRecordGraph = createStoredRecordGraph;
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.mostAffected = exports.largestDrop = void 0;
4
+ /** Host-computed quality drops or impact counts: scores rank recorded candidates, never create evidence. */
5
+ function maximum(scores) {
6
+ return (candidates) => {
7
+ let best = -Infinity;
8
+ const tied = [];
9
+ for (const candidate of candidates) {
10
+ const score = scores.get(candidate.nodeId);
11
+ if (score === undefined || !Number.isFinite(score))
12
+ throw new TypeError('CAUSE_STRATEGY_EVIDENCE_MISSING');
13
+ if (score > best) {
14
+ best = score;
15
+ tied.length = 0;
16
+ }
17
+ if (score === best)
18
+ tied.push(candidate.nodeId);
19
+ }
20
+ return Object.freeze(tied);
21
+ };
22
+ }
23
+ const largestDrop = (scores) => maximum(scores);
24
+ exports.largestDrop = largestDrop;
25
+ const mostAffected = (counts) => maximum(counts);
26
+ exports.mostAffected = mostAffected;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });