foottrace 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +96 -2
- package/dist/cause.js +14 -0
- package/dist/esm/cause.d.ts +8 -0
- package/dist/esm/cause.js +5 -0
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/lib/cause/admission.d.ts +27 -0
- package/dist/esm/lib/cause/admission.js +59 -0
- package/dist/esm/lib/cause/explain.d.ts +5 -0
- package/dist/esm/lib/cause/explain.js +114 -0
- package/dist/esm/lib/cause/ranking.d.ts +6 -0
- package/dist/esm/lib/cause/ranking.js +10 -0
- package/dist/esm/lib/cause/recordAdapter.d.ts +9 -0
- package/dist/esm/lib/cause/recordAdapter.js +49 -0
- package/dist/esm/lib/cause/recordChildren.d.ts +4 -0
- package/dist/esm/lib/cause/recordChildren.js +79 -0
- package/dist/esm/lib/cause/recordControl.d.ts +13 -0
- package/dist/esm/lib/cause/recordControl.js +66 -0
- package/dist/esm/lib/cause/recordCoverage.d.ts +24 -0
- package/dist/esm/lib/cause/recordCoverage.js +87 -0
- package/dist/esm/lib/cause/recordIdentity.d.ts +9 -0
- package/dist/esm/lib/cause/recordIdentity.js +67 -0
- package/dist/esm/lib/cause/recordLoading.d.ts +7 -0
- package/dist/esm/lib/cause/recordLoading.js +116 -0
- package/dist/esm/lib/cause/recordParents.d.ts +4 -0
- package/dist/esm/lib/cause/recordParents.js +86 -0
- package/dist/esm/lib/cause/recordScope.d.ts +8 -0
- package/dist/esm/lib/cause/recordScope.js +60 -0
- package/dist/esm/lib/cause/recordTypes.d.ts +67 -0
- package/dist/esm/lib/cause/recordTypes.js +1 -0
- package/dist/esm/lib/cause/recordValidation.d.ts +33 -0
- package/dist/esm/lib/cause/recordValidation.js +198 -0
- package/dist/esm/lib/cause/session.d.ts +30 -0
- package/dist/esm/lib/cause/session.js +161 -0
- package/dist/esm/lib/cause/storedRecordAdapter.d.ts +11 -0
- package/dist/esm/lib/cause/storedRecordAdapter.js +57 -0
- package/dist/esm/lib/cause/strategies.d.ts +3 -0
- package/dist/esm/lib/cause/strategies.js +21 -0
- package/dist/esm/lib/cause/types.d.ts +90 -0
- package/dist/esm/lib/cause/types.js +1 -0
- package/dist/esm/lib/cause/validation.d.ts +24 -0
- package/dist/esm/lib/cause/validation.js +134 -0
- package/dist/esm/lib/cause/walk.d.ts +12 -0
- package/dist/esm/lib/cause/walk.js +174 -0
- package/dist/esm/lib/memory/types.d.ts +4 -1
- package/dist/esm/lib/memory/types.js +1 -1
- package/dist/lib/cause/admission.js +67 -0
- package/dist/lib/cause/explain.js +119 -0
- package/dist/lib/cause/ranking.js +14 -0
- package/dist/lib/cause/recordAdapter.js +55 -0
- package/dist/lib/cause/recordChildren.js +83 -0
- package/dist/lib/cause/recordControl.js +74 -0
- package/dist/lib/cause/recordCoverage.js +99 -0
- package/dist/lib/cause/recordIdentity.js +73 -0
- package/dist/lib/cause/recordLoading.js +121 -0
- package/dist/lib/cause/recordParents.js +90 -0
- package/dist/lib/cause/recordScope.js +66 -0
- package/dist/lib/cause/recordTypes.js +2 -0
- package/dist/lib/cause/recordValidation.js +213 -0
- package/dist/lib/cause/session.js +165 -0
- package/dist/lib/cause/storedRecordAdapter.js +61 -0
- package/dist/lib/cause/strategies.js +26 -0
- package/dist/lib/cause/types.js +2 -0
- package/dist/lib/cause/validation.js +150 -0
- package/dist/lib/cause/walk.js +180 -0
- package/dist/lib/memory/types.js +1 -1
- package/dist/types/cause.d.ts +8 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/lib/cause/admission.d.ts +27 -0
- package/dist/types/lib/cause/explain.d.ts +5 -0
- package/dist/types/lib/cause/ranking.d.ts +6 -0
- package/dist/types/lib/cause/recordAdapter.d.ts +9 -0
- package/dist/types/lib/cause/recordChildren.d.ts +4 -0
- package/dist/types/lib/cause/recordControl.d.ts +13 -0
- package/dist/types/lib/cause/recordCoverage.d.ts +24 -0
- package/dist/types/lib/cause/recordIdentity.d.ts +9 -0
- package/dist/types/lib/cause/recordLoading.d.ts +7 -0
- package/dist/types/lib/cause/recordParents.d.ts +4 -0
- package/dist/types/lib/cause/recordScope.d.ts +8 -0
- package/dist/types/lib/cause/recordTypes.d.ts +67 -0
- package/dist/types/lib/cause/recordValidation.d.ts +33 -0
- package/dist/types/lib/cause/session.d.ts +30 -0
- package/dist/types/lib/cause/storedRecordAdapter.d.ts +11 -0
- package/dist/types/lib/cause/strategies.d.ts +3 -0
- package/dist/types/lib/cause/types.d.ts +90 -0
- package/dist/types/lib/cause/validation.d.ts +24 -0
- package/dist/types/lib/cause/walk.d.ts +12 -0
- package/dist/types/lib/memory/types.d.ts +4 -1
- package/package.json +67 -13
|
@@ -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,150 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.uniqueGaps = exports.gap = exports.nodeIds = exports.adjacency = exports.gaps = exports.attention = exports.refs = exports.list = exports.name = exports.plain = exports.own = exports.observePromise = exports.AdmissionError = void 0;
|
|
4
|
+
const admission_js_1 = require("./admission.js");
|
|
5
|
+
/** Graph-port refusals carry their code through Session; loader refusals are handled at the record boundary. */
|
|
6
|
+
class AdmissionError extends admission_js_1.EvidenceRefusal {
|
|
7
|
+
code;
|
|
8
|
+
constructor(code = 'invalid-data') {
|
|
9
|
+
super(code, 'Invalid cause evidence');
|
|
10
|
+
this.code = code;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
exports.AdmissionError = AdmissionError;
|
|
14
|
+
/**
|
|
15
|
+
* Only ordinary native promise wrappers belong to the async host contract.
|
|
16
|
+
* An own constructor could run an accessor/species during intrinsic then().
|
|
17
|
+
* Exotic wrappers remain the trusted host's responsibility, including rejection handling.
|
|
18
|
+
*/
|
|
19
|
+
function observePromise(raw, accept, reject) {
|
|
20
|
+
if (!raw || typeof raw !== 'object' || Object.getPrototypeOf(raw) !== Promise.prototype)
|
|
21
|
+
return false;
|
|
22
|
+
if (Object.getOwnPropertyDescriptor(raw, 'constructor'))
|
|
23
|
+
throw new AdmissionError();
|
|
24
|
+
try {
|
|
25
|
+
Promise.prototype.then.call(raw, accept, reject);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
throw new AdmissionError();
|
|
29
|
+
}
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
exports.observePromise = observePromise;
|
|
33
|
+
const refuseGraph = (code) => new AdmissionError(code);
|
|
34
|
+
const graphDescriptors = { translateTrapErrors: false, requireOwnValue: false };
|
|
35
|
+
function own(value, key) {
|
|
36
|
+
return (0, admission_js_1.ownData)(value, key, refuseGraph, graphDescriptors);
|
|
37
|
+
}
|
|
38
|
+
exports.own = own;
|
|
39
|
+
function plain(value) {
|
|
40
|
+
if (!value || typeof value !== 'object' || ![Object.prototype, null].includes(Object.getPrototypeOf(value)))
|
|
41
|
+
throw new AdmissionError();
|
|
42
|
+
}
|
|
43
|
+
exports.plain = plain;
|
|
44
|
+
function name(value, maximum = 2048) {
|
|
45
|
+
return (0, admission_js_1.boundedName)(value, maximum, refuseGraph);
|
|
46
|
+
}
|
|
47
|
+
exports.name = name;
|
|
48
|
+
function list(value, maximum, read, overflow = 'invalid-data') {
|
|
49
|
+
return (0, admission_js_1.boundedList)(value, maximum, read, {
|
|
50
|
+
refuse: refuseGraph,
|
|
51
|
+
overflow,
|
|
52
|
+
freeze: true,
|
|
53
|
+
descriptors: graphDescriptors,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
exports.list = list;
|
|
57
|
+
const reasons = new Set([
|
|
58
|
+
'ERROR_RECORDED',
|
|
59
|
+
'UNKNOWN_EFFECT_RECORDED',
|
|
60
|
+
'RETRY_STOP_RECORDED',
|
|
61
|
+
'TIMEOUT_RECORDED',
|
|
62
|
+
'RETRY_RECORDED',
|
|
63
|
+
'QUALITY_DROP_RECORDED',
|
|
64
|
+
'EVIDENCE_NOT_COLLECTED',
|
|
65
|
+
'OUTLIER_RECORDED',
|
|
66
|
+
'BUSINESS_RULE_MATCHED',
|
|
67
|
+
]);
|
|
68
|
+
const codes = new Set([
|
|
69
|
+
'not-collected',
|
|
70
|
+
'untracked-input',
|
|
71
|
+
'redacted',
|
|
72
|
+
'conservative',
|
|
73
|
+
'scope-boundary',
|
|
74
|
+
'invalid-data',
|
|
75
|
+
'unavailable',
|
|
76
|
+
'timeout',
|
|
77
|
+
'cancelled',
|
|
78
|
+
'cycle',
|
|
79
|
+
'node-limit',
|
|
80
|
+
'depth-limit',
|
|
81
|
+
'edge-limit',
|
|
82
|
+
'call-limit',
|
|
83
|
+
'truncated',
|
|
84
|
+
'strategy-failed',
|
|
85
|
+
]);
|
|
86
|
+
const refs = (value) => list(value, 16, (item) => name(item, 512));
|
|
87
|
+
exports.refs = refs;
|
|
88
|
+
function attention(value) {
|
|
89
|
+
return list(value, 32, (item) => {
|
|
90
|
+
plain(item);
|
|
91
|
+
const kind = own(item, 'kind'), reason = own(item, 'reason');
|
|
92
|
+
if (typeof kind !== 'string' ||
|
|
93
|
+
!/^[A-Za-z][A-Za-z0-9_-]{0,63}$/.test(kind) ||
|
|
94
|
+
typeof reason !== 'string' ||
|
|
95
|
+
!reasons.has(reason))
|
|
96
|
+
throw new AdmissionError();
|
|
97
|
+
const evidence = (0, exports.refs)(own(item, 'evidence'));
|
|
98
|
+
if (kind !== 'not-collected' && evidence.length === 0)
|
|
99
|
+
throw new AdmissionError();
|
|
100
|
+
return Object.freeze({ kind, reason: reason, evidence });
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
exports.attention = attention;
|
|
104
|
+
function gaps(value) {
|
|
105
|
+
return list(value, 32, (item) => {
|
|
106
|
+
plain(item);
|
|
107
|
+
const code = own(item, 'code'), nodeId = own(item, 'nodeId');
|
|
108
|
+
if (typeof code !== 'string' || !codes.has(code))
|
|
109
|
+
throw new AdmissionError();
|
|
110
|
+
return Object.freeze({
|
|
111
|
+
code: code,
|
|
112
|
+
...(nodeId === undefined ? {} : { nodeId: name(nodeId) }),
|
|
113
|
+
evidence: (0, exports.refs)(own(item, 'evidence')),
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
exports.gaps = gaps;
|
|
118
|
+
function adjacency(value, maximum) {
|
|
119
|
+
plain(value);
|
|
120
|
+
const complete = own(value, 'complete');
|
|
121
|
+
if (typeof complete !== 'boolean')
|
|
122
|
+
throw new AdmissionError();
|
|
123
|
+
const edges = list(own(value, 'edges'), maximum, (item) => {
|
|
124
|
+
plain(item);
|
|
125
|
+
const nodeId = name(own(item, 'nodeId')), kind = own(item, 'kind'), attribution = own(item, 'attribution');
|
|
126
|
+
if ((kind !== 'data' && kind !== 'control') ||
|
|
127
|
+
(attribution !== 'per-write' && attribution !== 'stage' && attribution !== 'recorded-control') ||
|
|
128
|
+
(kind === 'control' && attribution !== 'recorded-control') ||
|
|
129
|
+
(kind === 'data' && attribution === 'recorded-control'))
|
|
130
|
+
throw new AdmissionError();
|
|
131
|
+
return Object.freeze({ nodeId, kind, attribution, evidence: (0, exports.refs)(own(item, 'evidence')) });
|
|
132
|
+
}, 'edge-limit');
|
|
133
|
+
return Object.freeze({ edges, complete });
|
|
134
|
+
}
|
|
135
|
+
exports.adjacency = adjacency;
|
|
136
|
+
const nodeIds = (value, max) => list(value, max, (item) => name(item), 'node-limit');
|
|
137
|
+
exports.nodeIds = nodeIds;
|
|
138
|
+
const gap = (code, nodeId) => Object.freeze({ code, ...(nodeId === undefined ? {} : { nodeId }), evidence: Object.freeze([]) });
|
|
139
|
+
exports.gap = gap;
|
|
140
|
+
function uniqueGaps(items) {
|
|
141
|
+
const seen = new Set();
|
|
142
|
+
return Object.freeze(items.filter((item) => {
|
|
143
|
+
const key = JSON.stringify(item);
|
|
144
|
+
if (seen.has(key))
|
|
145
|
+
return false;
|
|
146
|
+
seen.add(key);
|
|
147
|
+
return true;
|
|
148
|
+
}));
|
|
149
|
+
}
|
|
150
|
+
exports.uniqueGaps = uniqueGaps;
|