foottrace 1.2.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.
- package/README.md +27 -2
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/lib/cause/explain.js +7 -7
- package/dist/esm/lib/cause/queryMemo.d.ts +18 -0
- package/dist/esm/lib/cause/queryMemo.js +46 -0
- package/dist/esm/lib/cause/recordAdapter.js +12 -19
- package/dist/esm/lib/cause/recordChildren.d.ts +3 -3
- package/dist/esm/lib/cause/recordChildren.js +3 -4
- package/dist/esm/lib/cause/recordCoverage.d.ts +2 -2
- package/dist/esm/lib/cause/recordCoverage.js +1 -1
- package/dist/esm/lib/cause/recordIdentity.js +5 -5
- package/dist/esm/lib/cause/recordLoading.js +3 -3
- package/dist/esm/lib/cause/recordQuery.d.ts +9 -0
- package/dist/esm/lib/cause/recordQuery.js +22 -0
- package/dist/esm/lib/cause/recordScope.js +13 -13
- package/dist/esm/lib/cause/recordValidation.d.ts +4 -4
- package/dist/esm/lib/cause/recordValidation.js +17 -17
- package/dist/esm/lib/cause/session.js +9 -6
- package/dist/esm/lib/cause/storedRecordAdapter.js +16 -10
- package/dist/esm/lib/cause/types.d.ts +2 -1
- package/dist/esm/lib/cause/validation.d.ts +4 -4
- package/dist/esm/lib/cause/validation.js +19 -36
- package/dist/esm/lib/{cause → evidence}/admission.d.ts +5 -1
- package/dist/esm/lib/{cause → evidence}/admission.js +14 -1
- package/dist/esm/lib/evidence/codes.d.ts +33 -0
- package/dist/esm/lib/evidence/codes.js +28 -0
- package/dist/esm/lib/evidence/requirementTypes.d.ts +46 -0
- package/dist/esm/lib/evidence/requirementTypes.js +1 -0
- package/dist/esm/lib/evidence/requirements.d.ts +24 -0
- package/dist/esm/lib/evidence/requirements.js +185 -0
- package/dist/index.js +5 -1
- package/dist/lib/cause/explain.js +6 -6
- package/dist/lib/cause/queryMemo.js +52 -0
- package/dist/lib/cause/recordAdapter.js +10 -17
- package/dist/lib/cause/recordChildren.js +3 -4
- package/dist/lib/cause/recordCoverage.js +1 -1
- package/dist/lib/cause/recordIdentity.js +4 -4
- package/dist/lib/cause/recordLoading.js +2 -2
- package/dist/lib/cause/recordQuery.js +27 -0
- package/dist/lib/cause/recordScope.js +12 -12
- package/dist/lib/cause/recordValidation.js +21 -21
- package/dist/lib/cause/session.js +8 -5
- package/dist/lib/cause/storedRecordAdapter.js +15 -9
- package/dist/lib/cause/validation.js +23 -40
- package/dist/lib/{cause → evidence}/admission.js +14 -1
- package/dist/lib/evidence/codes.js +33 -0
- package/dist/lib/evidence/requirementTypes.js +2 -0
- package/dist/lib/evidence/requirements.js +190 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/lib/cause/queryMemo.d.ts +18 -0
- package/dist/types/lib/cause/recordChildren.d.ts +3 -3
- package/dist/types/lib/cause/recordCoverage.d.ts +2 -2
- package/dist/types/lib/cause/recordQuery.d.ts +9 -0
- package/dist/types/lib/cause/recordValidation.d.ts +4 -4
- package/dist/types/lib/cause/types.d.ts +2 -1
- package/dist/types/lib/cause/validation.d.ts +4 -4
- package/dist/types/lib/{cause → evidence}/admission.d.ts +5 -1
- package/dist/types/lib/evidence/codes.d.ts +33 -0
- package/dist/types/lib/evidence/requirementTypes.d.ts +46 -0
- package/dist/types/lib/evidence/requirements.d.ts +24 -0
- package/package.json +1 -1
- /package/dist/esm/lib/cause/{recordParents.d.ts → recordInspection.d.ts} +0 -0
- /package/dist/esm/lib/cause/{recordParents.js → recordInspection.js} +0 -0
- /package/dist/lib/cause/{recordParents.js → recordInspection.js} +0 -0
- /package/dist/types/lib/cause/{recordParents.d.ts → recordInspection.d.ts} +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { AdmissionError, gap,
|
|
1
|
+
import { AdmissionError, gap, graphName, observePromise, graphOwn, plain } from './validation.js';
|
|
2
|
+
import { beginQueryMemo, endQueryMemo } from './queryMemo.js';
|
|
2
3
|
const defaults = { maxNodes: 100, maxDepth: 20, maxEdges: 1000, maxCalls: 1000, timeoutMs: 1000 };
|
|
3
4
|
const ceilings = { maxNodes: 10000, maxDepth: 1000, maxEdges: 100000, maxCalls: 100000, timeoutMs: 30000 };
|
|
4
5
|
/** One deadline, memo table and work budget per public query, never per graph or store. */
|
|
@@ -21,7 +22,7 @@ export class Session {
|
|
|
21
22
|
plain(options);
|
|
22
23
|
const limits = { ...defaults };
|
|
23
24
|
for (const key of Object.keys(defaults)) {
|
|
24
|
-
const value =
|
|
25
|
+
const value = graphOwn(options, key);
|
|
25
26
|
if (value !== undefined) {
|
|
26
27
|
if (typeof value !== 'number' ||
|
|
27
28
|
!Number.isSafeInteger(value) ||
|
|
@@ -32,13 +33,13 @@ export class Session {
|
|
|
32
33
|
}
|
|
33
34
|
}
|
|
34
35
|
this.limits = Object.freeze(limits);
|
|
35
|
-
const rank =
|
|
36
|
+
const rank = graphOwn(options, 'rankRoots');
|
|
36
37
|
if (rank !== undefined && typeof rank !== 'function')
|
|
37
38
|
throw new TypeError();
|
|
38
|
-
const impactRank =
|
|
39
|
+
const impactRank = graphOwn(options, 'rankImpact');
|
|
39
40
|
if (impactRank !== undefined && typeof impactRank !== 'function')
|
|
40
41
|
throw new TypeError();
|
|
41
|
-
const signal =
|
|
42
|
+
const signal = graphOwn(options, 'signal');
|
|
42
43
|
if (signal !== undefined && !(signal instanceof AbortSignal))
|
|
43
44
|
throw new TypeError();
|
|
44
45
|
this.external = signal;
|
|
@@ -71,8 +72,10 @@ export class Session {
|
|
|
71
72
|
this.timedOut = true;
|
|
72
73
|
this.controller.abort();
|
|
73
74
|
}, this.limits.timeoutMs);
|
|
75
|
+
beginQueryMemo(this.context, this.limits.maxNodes);
|
|
74
76
|
}
|
|
75
77
|
dispose() {
|
|
78
|
+
endQueryMemo(this.context);
|
|
76
79
|
clearTimeout(this.timer);
|
|
77
80
|
this.external?.removeEventListener('abort', this.onAbort);
|
|
78
81
|
}
|
|
@@ -85,7 +88,7 @@ export class Session {
|
|
|
85
88
|
return this.controller.signal.aborted ? gap(this.timedOut ? 'timeout' : 'cancelled', id) : undefined;
|
|
86
89
|
}
|
|
87
90
|
admit(id) {
|
|
88
|
-
|
|
91
|
+
graphName(id);
|
|
89
92
|
if (this.nodes.has(id))
|
|
90
93
|
return undefined;
|
|
91
94
|
if (this.nodes.size >= this.limits.maxNodes)
|
|
@@ -1,31 +1,37 @@
|
|
|
1
1
|
import { readStoredRecord } from '../time-travel/storedRecord.js';
|
|
2
2
|
import { createLazyRecordGraph, recordOptions, } from './recordAdapter.js';
|
|
3
|
-
import { addressKey, addressOf, bundleOf, dataCopy,
|
|
3
|
+
import { addressKey, addressOf, bundleOf, dataCopy, recordList, recordOwn, projectLoaded, RecordRefusal, } from './recordValidation.js';
|
|
4
4
|
/** Load one address only. The host owns auth/storage; cancellation is forwarded and waiting is independently bounded. */
|
|
5
5
|
export function createStoredRecordGraph(host, options = {}) {
|
|
6
6
|
const limits = recordOptions(options);
|
|
7
7
|
return createLazyRecordGraph((address, context) => projectLoaded(() => host.loadLog(address, context), (loaded) => {
|
|
8
8
|
const max = limits.maxCommits, budget = { left: limits.maxFields };
|
|
9
|
-
const raw =
|
|
9
|
+
const raw = recordList(recordOwn(loaded, 'entries'), max * 4 + 16), entries = [];
|
|
10
10
|
for (let i = 0; i < raw.length; i++) {
|
|
11
11
|
if (context.signal.aborted)
|
|
12
12
|
throw new RecordRefusal('cancelled');
|
|
13
|
-
const entry =
|
|
13
|
+
const entry = recordOwn(raw, String(i)), a = addressOf(recordOwn(entry, 'address'));
|
|
14
14
|
if (addressKey(a) !== addressKey(address))
|
|
15
15
|
throw new RecordRefusal();
|
|
16
|
-
const kind =
|
|
16
|
+
const kind = recordOwn(entry, 'kind'), at = recordOwn(entry, 'at');
|
|
17
17
|
if (typeof at !== 'number' || !Number.isFinite(at))
|
|
18
18
|
throw new RecordRefusal();
|
|
19
19
|
if (kind === 'commit') {
|
|
20
|
-
const b =
|
|
20
|
+
const b = recordOwn(entry, 'bundle'), idx = recordOwn(b, 'idx');
|
|
21
21
|
if (!Number.isSafeInteger(idx) || idx < 0 || idx >= max)
|
|
22
22
|
throw new RecordRefusal('node-limit');
|
|
23
23
|
entries.push({ kind, address: a, at, bundle: bundleOf(b, idx, budget) });
|
|
24
24
|
}
|
|
25
25
|
else if (kind === 'opened')
|
|
26
|
-
entries.push({ kind, address: a, at, initialState: dataCopy(
|
|
26
|
+
entries.push({ kind, address: a, at, initialState: dataCopy(recordOwn(entry, 'initialState'), budget) });
|
|
27
27
|
else if (kind === 'closed')
|
|
28
|
-
entries.push({
|
|
28
|
+
entries.push({
|
|
29
|
+
kind,
|
|
30
|
+
address: a,
|
|
31
|
+
at,
|
|
32
|
+
outcome: recordOwn(entry, 'outcome'),
|
|
33
|
+
commits: recordOwn(entry, 'commits'),
|
|
34
|
+
});
|
|
29
35
|
else if (kind === 'note') {
|
|
30
36
|
/* opaque note bodies never enter dependency projection */
|
|
31
37
|
}
|
|
@@ -46,9 +52,9 @@ export function createStoredRecordGraph(host, options = {}) {
|
|
|
46
52
|
scope: {
|
|
47
53
|
address,
|
|
48
54
|
commitLog: log.commitLog,
|
|
49
|
-
reads:
|
|
50
|
-
controlDeps:
|
|
51
|
-
attention:
|
|
55
|
+
reads: recordOwn(loaded, 'reads'),
|
|
56
|
+
controlDeps: recordOwn(loaded, 'controlDeps'),
|
|
57
|
+
attention: recordOwn(loaded, 'attention'),
|
|
52
58
|
complete: log.complete,
|
|
53
59
|
},
|
|
54
60
|
gaps: record.unread.length || log.gaps.length ? ['not-collected'] : [],
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { GapCode } from '../evidence/codes.js';
|
|
2
|
+
export type { GapCode } from '../evidence/codes.js';
|
|
1
3
|
/** Names and evidence references only. No state values, messages, credentials or business payloads. */
|
|
2
4
|
export type NodeId = string;
|
|
3
5
|
export type AttentionKind = 'error' | 'unknown-effect' | 'gave-up' | 'timeout' | 'retried' | 'quality-drop' | 'not-collected' | 'outlier' | (string & {});
|
|
@@ -8,7 +10,6 @@ export interface Attention {
|
|
|
8
10
|
readonly evidence: readonly string[];
|
|
9
11
|
}
|
|
10
12
|
export type Confidence = 'complete' | 'incomplete';
|
|
11
|
-
export type GapCode = 'not-collected' | 'untracked-input' | 'redacted' | 'conservative' | 'scope-boundary' | 'invalid-data' | 'unavailable' | 'timeout' | 'cancelled' | 'cycle' | 'node-limit' | 'depth-limit' | 'edge-limit' | 'call-limit' | 'truncated' | 'strategy-failed';
|
|
12
13
|
export interface Gap {
|
|
13
14
|
readonly code: GapCode;
|
|
14
15
|
readonly nodeId?: NodeId;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Adjacency, Attention, Gap, GapCode, NodeId } from './types.js';
|
|
2
|
-
import { EvidenceRefusal } from '
|
|
2
|
+
import { EvidenceRefusal } from '../evidence/admission.js';
|
|
3
3
|
/** Graph-port refusals carry their code through Session; loader refusals are handled at the record boundary. */
|
|
4
4
|
export declare class AdmissionError extends EvidenceRefusal {
|
|
5
5
|
readonly code: GapCode;
|
|
@@ -11,10 +11,10 @@ export declare class AdmissionError extends EvidenceRefusal {
|
|
|
11
11
|
* Exotic wrappers remain the trusted host's responsibility, including rejection handling.
|
|
12
12
|
*/
|
|
13
13
|
export declare function observePromise(raw: unknown, accept: (raw: unknown) => void, reject: (reason: unknown) => void): boolean;
|
|
14
|
-
export declare function
|
|
14
|
+
export declare function graphOwn(value: unknown, key: string): unknown;
|
|
15
15
|
export declare function plain(value: unknown): void;
|
|
16
|
-
export declare function
|
|
17
|
-
export declare function
|
|
16
|
+
export declare function graphName(value: unknown, maximum?: number): string;
|
|
17
|
+
export declare function graphList<T>(value: unknown, maximum: number, read: (item: unknown) => T, overflow?: GapCode): readonly T[];
|
|
18
18
|
export declare const refs: (value: unknown) => readonly string[];
|
|
19
19
|
export declare function attention(value: unknown): readonly Attention[];
|
|
20
20
|
export declare function gaps(value: unknown): readonly Gap[];
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { boundedList, boundedName, EvidenceRefusal, ownData } from '
|
|
1
|
+
import { boundedList, boundedName, EvidenceRefusal, ownData } from '../evidence/admission.js';
|
|
2
|
+
import { isGapCode } from '../evidence/codes.js';
|
|
2
3
|
/** Graph-port refusals carry their code through Session; loader refusals are handled at the record boundary. */
|
|
3
4
|
export class AdmissionError extends EvidenceRefusal {
|
|
4
5
|
code;
|
|
@@ -27,17 +28,17 @@ export function observePromise(raw, accept, reject) {
|
|
|
27
28
|
}
|
|
28
29
|
const refuseGraph = (code) => new AdmissionError(code);
|
|
29
30
|
const graphDescriptors = { translateTrapErrors: false, requireOwnValue: false };
|
|
30
|
-
export function
|
|
31
|
+
export function graphOwn(value, key) {
|
|
31
32
|
return ownData(value, key, refuseGraph, graphDescriptors);
|
|
32
33
|
}
|
|
33
34
|
export function plain(value) {
|
|
34
35
|
if (!value || typeof value !== 'object' || ![Object.prototype, null].includes(Object.getPrototypeOf(value)))
|
|
35
36
|
throw new AdmissionError();
|
|
36
37
|
}
|
|
37
|
-
export function
|
|
38
|
+
export function graphName(value, maximum = 2048) {
|
|
38
39
|
return boundedName(value, maximum, refuseGraph);
|
|
39
40
|
}
|
|
40
|
-
export function
|
|
41
|
+
export function graphList(value, maximum, read, overflow = 'invalid-data') {
|
|
41
42
|
return boundedList(value, maximum, read, {
|
|
42
43
|
refuse: refuseGraph,
|
|
43
44
|
overflow,
|
|
@@ -56,71 +57,53 @@ const reasons = new Set([
|
|
|
56
57
|
'OUTLIER_RECORDED',
|
|
57
58
|
'BUSINESS_RULE_MATCHED',
|
|
58
59
|
]);
|
|
59
|
-
const
|
|
60
|
-
'not-collected',
|
|
61
|
-
'untracked-input',
|
|
62
|
-
'redacted',
|
|
63
|
-
'conservative',
|
|
64
|
-
'scope-boundary',
|
|
65
|
-
'invalid-data',
|
|
66
|
-
'unavailable',
|
|
67
|
-
'timeout',
|
|
68
|
-
'cancelled',
|
|
69
|
-
'cycle',
|
|
70
|
-
'node-limit',
|
|
71
|
-
'depth-limit',
|
|
72
|
-
'edge-limit',
|
|
73
|
-
'call-limit',
|
|
74
|
-
'truncated',
|
|
75
|
-
'strategy-failed',
|
|
76
|
-
]);
|
|
77
|
-
export const refs = (value) => list(value, 16, (item) => name(item, 512));
|
|
60
|
+
export const refs = (value) => graphList(value, 16, (item) => graphName(item, 512));
|
|
78
61
|
export function attention(value) {
|
|
79
|
-
return
|
|
62
|
+
return graphList(value, 32, (item) => {
|
|
80
63
|
plain(item);
|
|
81
|
-
const kind =
|
|
64
|
+
const kind = graphOwn(item, 'kind'), reason = graphOwn(item, 'reason');
|
|
82
65
|
if (typeof kind !== 'string' ||
|
|
83
66
|
!/^[A-Za-z][A-Za-z0-9_-]{0,63}$/.test(kind) ||
|
|
84
67
|
typeof reason !== 'string' ||
|
|
85
68
|
!reasons.has(reason))
|
|
86
69
|
throw new AdmissionError();
|
|
87
|
-
const evidence = refs(
|
|
70
|
+
const evidence = refs(graphOwn(item, 'evidence'));
|
|
88
71
|
if (kind !== 'not-collected' && evidence.length === 0)
|
|
89
72
|
throw new AdmissionError();
|
|
90
73
|
return Object.freeze({ kind, reason: reason, evidence });
|
|
91
74
|
});
|
|
92
75
|
}
|
|
93
76
|
export function gaps(value) {
|
|
94
|
-
return
|
|
77
|
+
return graphList(value, 32, (item) => {
|
|
95
78
|
plain(item);
|
|
96
|
-
const code =
|
|
97
|
-
if (
|
|
79
|
+
const code = graphOwn(item, 'code'), nodeId = graphOwn(item, 'nodeId');
|
|
80
|
+
if (!isGapCode(code))
|
|
98
81
|
throw new AdmissionError();
|
|
99
82
|
return Object.freeze({
|
|
100
83
|
code: code,
|
|
101
|
-
...(nodeId === undefined ? {} : { nodeId:
|
|
102
|
-
evidence: refs(
|
|
84
|
+
...(nodeId === undefined ? {} : { nodeId: graphName(nodeId) }),
|
|
85
|
+
evidence: refs(graphOwn(item, 'evidence')),
|
|
103
86
|
});
|
|
104
87
|
});
|
|
105
88
|
}
|
|
106
89
|
export function adjacency(value, maximum) {
|
|
107
90
|
plain(value);
|
|
108
|
-
const complete =
|
|
91
|
+
const complete = graphOwn(value, 'complete');
|
|
109
92
|
if (typeof complete !== 'boolean')
|
|
110
93
|
throw new AdmissionError();
|
|
111
|
-
const edges =
|
|
94
|
+
const edges = graphList(graphOwn(value, 'edges'), maximum, (item) => {
|
|
112
95
|
plain(item);
|
|
113
|
-
const nodeId =
|
|
96
|
+
const nodeId = graphName(graphOwn(item, 'nodeId')), kind = graphOwn(item, 'kind'), attribution = graphOwn(item, 'attribution');
|
|
114
97
|
if ((kind !== 'data' && kind !== 'control') ||
|
|
115
98
|
(attribution !== 'per-write' && attribution !== 'stage' && attribution !== 'recorded-control') ||
|
|
116
99
|
(kind === 'control' && attribution !== 'recorded-control') ||
|
|
117
100
|
(kind === 'data' && attribution === 'recorded-control'))
|
|
118
101
|
throw new AdmissionError();
|
|
119
|
-
return Object.freeze({ nodeId, kind, attribution, evidence: refs(
|
|
102
|
+
return Object.freeze({ nodeId, kind, attribution, evidence: refs(graphOwn(item, 'evidence')) });
|
|
120
103
|
}, 'edge-limit');
|
|
121
104
|
return Object.freeze({ edges, complete });
|
|
122
105
|
}
|
|
123
|
-
export const nodeIds = (value, max) =>
|
|
106
|
+
export const nodeIds = (value, max) => graphList(value, max, (item) => graphName(item), 'node-limit');
|
|
124
107
|
export const gap = (code, nodeId) => Object.freeze({ code, ...(nodeId === undefined ? {} : { nodeId }), evidence: Object.freeze([]) });
|
|
125
108
|
export function uniqueGaps(items) {
|
|
126
109
|
const seen = new Set();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { GapCode } from './
|
|
1
|
+
import type { GapCode } from './codes.js';
|
|
2
2
|
/** One refusal representation; subclasses preserve the graph-port and record-loader boundaries. */
|
|
3
3
|
export declare class EvidenceRefusal extends Error {
|
|
4
4
|
readonly code: GapCode;
|
|
@@ -19,6 +19,10 @@ export declare function boundedList<T>(value: unknown, maximum: number, read: (i
|
|
|
19
19
|
readonly overflow: GapCode;
|
|
20
20
|
readonly freeze: boolean;
|
|
21
21
|
readonly descriptors: DescriptorPolicy;
|
|
22
|
+
/** Optional prior shape inspection must refer to the same admitted length. */
|
|
23
|
+
readonly expectedLength?: number;
|
|
24
|
+
/** New boundaries may normalize revoked-array errors without changing existing reader policy. */
|
|
25
|
+
readonly translateArrayErrors?: boolean;
|
|
22
26
|
}): readonly T[];
|
|
23
27
|
/** Charge retained fields against the same mutable allowance across admission helpers. */
|
|
24
28
|
export declare function spendFields(budget: {
|
|
@@ -39,11 +39,24 @@ export function boundedName(value, maximum, refuse, path = false) {
|
|
|
39
39
|
/** Bound iteration before reading entries, without invoking element accessors. */
|
|
40
40
|
export function boundedList(value, maximum, read, options) {
|
|
41
41
|
const { refuse, overflow, freeze, descriptors } = options;
|
|
42
|
-
|
|
42
|
+
let array;
|
|
43
|
+
if (options.translateArrayErrors) {
|
|
44
|
+
try {
|
|
45
|
+
array = Array.isArray(value);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
throw refuse();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
else
|
|
52
|
+
array = Array.isArray(value);
|
|
53
|
+
if (!array)
|
|
43
54
|
throw refuse();
|
|
44
55
|
const length = ownData(value, 'length', refuse, descriptors);
|
|
45
56
|
if (typeof length !== 'number' || !Number.isSafeInteger(length) || length < 0)
|
|
46
57
|
throw refuse();
|
|
58
|
+
if (options.expectedLength !== undefined && length !== options.expectedLength)
|
|
59
|
+
throw refuse();
|
|
47
60
|
if (length > maximum)
|
|
48
61
|
throw refuse(overflow);
|
|
49
62
|
const result = [];
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** One vocabulary; true marks a domain evidence reason, false a trace/admission gap only. */
|
|
2
|
+
export declare const GAP_CODES: Readonly<{
|
|
3
|
+
readonly 'not-collected': true;
|
|
4
|
+
readonly 'untracked-input': false;
|
|
5
|
+
readonly redacted: true;
|
|
6
|
+
readonly conservative: false;
|
|
7
|
+
readonly 'scope-boundary': false;
|
|
8
|
+
readonly 'invalid-data': true;
|
|
9
|
+
readonly unavailable: true;
|
|
10
|
+
readonly timeout: true;
|
|
11
|
+
readonly cancelled: false;
|
|
12
|
+
readonly cycle: false;
|
|
13
|
+
readonly 'node-limit': false;
|
|
14
|
+
readonly 'depth-limit': false;
|
|
15
|
+
readonly 'edge-limit': false;
|
|
16
|
+
readonly 'call-limit': false;
|
|
17
|
+
readonly truncated: false;
|
|
18
|
+
readonly 'strategy-failed': false;
|
|
19
|
+
readonly 'not-checked': true;
|
|
20
|
+
readonly 'no-match': true;
|
|
21
|
+
readonly ambiguous: true;
|
|
22
|
+
readonly stale: true;
|
|
23
|
+
readonly incompatible: true;
|
|
24
|
+
readonly unsupported: true;
|
|
25
|
+
readonly 'not-permitted': true;
|
|
26
|
+
}>;
|
|
27
|
+
export type GapCode = keyof typeof GAP_CODES;
|
|
28
|
+
/** Producer-asserted evidence reasons, excluding traversal and admission bounds. */
|
|
29
|
+
export type RequirementReason = {
|
|
30
|
+
[Code in GapCode]: (typeof GAP_CODES)[Code] extends true ? Code : never;
|
|
31
|
+
}[GapCode];
|
|
32
|
+
export declare const isGapCode: (value: unknown) => value is "not-collected" | "untracked-input" | "redacted" | "conservative" | "scope-boundary" | "invalid-data" | "unavailable" | "timeout" | "cancelled" | "cycle" | "node-limit" | "depth-limit" | "edge-limit" | "call-limit" | "truncated" | "strategy-failed" | "not-checked" | "no-match" | "ambiguous" | "stale" | "incompatible" | "unsupported" | "not-permitted";
|
|
33
|
+
export declare const isRequirementReason: (value: unknown) => value is RequirementReason;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** One vocabulary; true marks a domain evidence reason, false a trace/admission gap only. */
|
|
2
|
+
export const GAP_CODES = Object.freeze({
|
|
3
|
+
'not-collected': true,
|
|
4
|
+
'untracked-input': false,
|
|
5
|
+
redacted: true,
|
|
6
|
+
conservative: false,
|
|
7
|
+
'scope-boundary': false,
|
|
8
|
+
'invalid-data': true,
|
|
9
|
+
unavailable: true,
|
|
10
|
+
timeout: true,
|
|
11
|
+
cancelled: false,
|
|
12
|
+
cycle: false,
|
|
13
|
+
'node-limit': false,
|
|
14
|
+
'depth-limit': false,
|
|
15
|
+
'edge-limit': false,
|
|
16
|
+
'call-limit': false,
|
|
17
|
+
truncated: false,
|
|
18
|
+
'strategy-failed': false,
|
|
19
|
+
'not-checked': true,
|
|
20
|
+
'no-match': true,
|
|
21
|
+
ambiguous: true,
|
|
22
|
+
stale: true,
|
|
23
|
+
incompatible: true,
|
|
24
|
+
unsupported: true,
|
|
25
|
+
'not-permitted': true,
|
|
26
|
+
});
|
|
27
|
+
export const isGapCode = (value) => typeof value === 'string' && Object.hasOwn(GAP_CODES, value);
|
|
28
|
+
export const isRequirementReason = (value) => isGapCode(value) && GAP_CODES[value];
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { RequirementReason } from './codes.js';
|
|
2
|
+
/**
|
|
3
|
+
* Existing check wire words; a disposition records a producer assertion, not authorization.
|
|
4
|
+
* `unreachable` means the check could not run; its reason identifies why.
|
|
5
|
+
*/
|
|
6
|
+
export type RequirementCheckDisposition = 'checked-pass' | 'checked-fail' | 'unreachable' | 'not-applicable';
|
|
7
|
+
/** References to a real check and its retained evidence. No synthetic attempt is required or invented. */
|
|
8
|
+
export interface RequirementCheck {
|
|
9
|
+
readonly checkRef: string;
|
|
10
|
+
readonly disposition: RequirementCheckDisposition;
|
|
11
|
+
readonly reason?: RequirementReason;
|
|
12
|
+
/** A namespaced adapter detail ID, never a message, subject or payload. */
|
|
13
|
+
readonly detail?: string;
|
|
14
|
+
readonly evidenceRefs: readonly string[];
|
|
15
|
+
readonly attemptRefs: readonly string[];
|
|
16
|
+
}
|
|
17
|
+
interface RequirementBinding {
|
|
18
|
+
/** This result's identity, not the enclosing artifact reference minted by storage. */
|
|
19
|
+
readonly id: string;
|
|
20
|
+
/** Reference to the requirement definition or policy. */
|
|
21
|
+
readonly requirementRef: string;
|
|
22
|
+
readonly findingRef: string;
|
|
23
|
+
readonly windowRef: string;
|
|
24
|
+
readonly affects: readonly string[];
|
|
25
|
+
readonly evidenceRefs: readonly string[];
|
|
26
|
+
readonly checks: readonly RequirementCheck[];
|
|
27
|
+
/** References into a host-owned closed action registry; never executable tool names or arguments. */
|
|
28
|
+
readonly actionRefs: readonly string[];
|
|
29
|
+
/** A namespaced adapter detail ID, never a message, subject or payload. */
|
|
30
|
+
readonly detail?: string;
|
|
31
|
+
/** Source validity and learned time are separate opaque references; no freshness inference is made. */
|
|
32
|
+
readonly validTimeRef?: string;
|
|
33
|
+
readonly learnedTimeRef?: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* A producer assertion. Structural admission does not verify its source, scope, bindings, validity or truth.
|
|
37
|
+
* Hosts must verify those facts and must not let a satisfied record override an unresolved affected claim.
|
|
38
|
+
*/
|
|
39
|
+
export type RequirementResult = RequirementBinding & ({
|
|
40
|
+
readonly state: 'satisfied';
|
|
41
|
+
readonly reason?: never;
|
|
42
|
+
} | {
|
|
43
|
+
readonly state: 'unresolved';
|
|
44
|
+
readonly reason: RequirementReason;
|
|
45
|
+
});
|
|
46
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { EvidenceRefusal } from './admission.js';
|
|
2
|
+
import { type GapCode } from './codes.js';
|
|
3
|
+
import type { RequirementResult } from './requirementTypes.js';
|
|
4
|
+
/** Fixed per-call ceilings; characters are UTF-16 code units, not serialized transport bytes. */
|
|
5
|
+
export declare const REQUIREMENT_RESULT_LIMITS: Readonly<{
|
|
6
|
+
results: 512;
|
|
7
|
+
checks: 16;
|
|
8
|
+
affects: 32;
|
|
9
|
+
refs: 16;
|
|
10
|
+
name: 512;
|
|
11
|
+
fields: 32768;
|
|
12
|
+
characters: 1048576;
|
|
13
|
+
}>;
|
|
14
|
+
/** No input, field value or originating exception is retained on a refusal. */
|
|
15
|
+
export declare class RequirementValidationError extends EvidenceRefusal {
|
|
16
|
+
constructor(code?: GapCode);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Atomically project detached, frozen reference-only records. This is structural admission only.
|
|
20
|
+
* The host must authenticate producers, resolve authorized artifacts, verify current query/run/scope and
|
|
21
|
+
* domain validity, and enforce every unresolved affects reference before rendering any factual claim.
|
|
22
|
+
* Identifier grammar does not classify secrets; producers must supply non-secret identifiers only.
|
|
23
|
+
*/
|
|
24
|
+
export declare function readRequirementResults(value: unknown): readonly RequirementResult[];
|
|
@@ -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
|
+
}
|
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; } });
|