foottrace 1.1.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +121 -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 +6 -0
- package/dist/esm/index.js +1 -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/queryMemo.d.ts +18 -0
- package/dist/esm/lib/cause/queryMemo.js +46 -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 +42 -0
- package/dist/esm/lib/cause/recordChildren.d.ts +4 -0
- package/dist/esm/lib/cause/recordChildren.js +78 -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/recordInspection.d.ts +4 -0
- package/dist/esm/lib/cause/recordInspection.js +86 -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/recordQuery.d.ts +9 -0
- package/dist/esm/lib/cause/recordQuery.js +22 -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 +164 -0
- package/dist/esm/lib/cause/storedRecordAdapter.d.ts +11 -0
- package/dist/esm/lib/cause/storedRecordAdapter.js +63 -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 +91 -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 +117 -0
- package/dist/esm/lib/cause/walk.d.ts +12 -0
- package/dist/esm/lib/cause/walk.js +174 -0
- package/dist/esm/lib/evidence/admission.d.ts +31 -0
- package/dist/esm/lib/evidence/admission.js +72 -0
- 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/esm/lib/memory/types.d.ts +4 -1
- package/dist/esm/lib/memory/types.js +1 -1
- package/dist/index.js +5 -1
- package/dist/lib/cause/explain.js +119 -0
- package/dist/lib/cause/queryMemo.js +52 -0
- package/dist/lib/cause/ranking.js +14 -0
- package/dist/lib/cause/recordAdapter.js +48 -0
- package/dist/lib/cause/recordChildren.js +82 -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/recordInspection.js +90 -0
- package/dist/lib/cause/recordLoading.js +121 -0
- package/dist/lib/cause/recordQuery.js +27 -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 +168 -0
- package/dist/lib/cause/storedRecordAdapter.js +67 -0
- package/dist/lib/cause/strategies.js +26 -0
- package/dist/lib/cause/types.js +2 -0
- package/dist/lib/cause/validation.js +133 -0
- package/dist/lib/cause/walk.js +180 -0
- package/dist/lib/evidence/admission.js +80 -0
- 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/lib/memory/types.js +1 -1
- package/dist/types/cause.d.ts +8 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/lib/cause/explain.d.ts +5 -0
- package/dist/types/lib/cause/queryMemo.d.ts +18 -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/recordInspection.d.ts +4 -0
- package/dist/types/lib/cause/recordLoading.d.ts +7 -0
- package/dist/types/lib/cause/recordQuery.d.ts +9 -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 +91 -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/evidence/admission.d.ts +31 -0
- 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/dist/types/lib/memory/types.d.ts +4 -1
- package/package.json +67 -13
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { boundedList, boundedName, EvidenceRefusal, ownData } from '../evidence/admission.js';
|
|
2
|
+
import { isGapCode } from '../evidence/codes.js';
|
|
3
|
+
/** Graph-port refusals carry their code through Session; loader refusals are handled at the record boundary. */
|
|
4
|
+
export class AdmissionError extends EvidenceRefusal {
|
|
5
|
+
code;
|
|
6
|
+
constructor(code = 'invalid-data') {
|
|
7
|
+
super(code, 'Invalid cause evidence');
|
|
8
|
+
this.code = code;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Only ordinary native promise wrappers belong to the async host contract.
|
|
13
|
+
* An own constructor could run an accessor/species during intrinsic then().
|
|
14
|
+
* Exotic wrappers remain the trusted host's responsibility, including rejection handling.
|
|
15
|
+
*/
|
|
16
|
+
export function observePromise(raw, accept, reject) {
|
|
17
|
+
if (!raw || typeof raw !== 'object' || Object.getPrototypeOf(raw) !== Promise.prototype)
|
|
18
|
+
return false;
|
|
19
|
+
if (Object.getOwnPropertyDescriptor(raw, 'constructor'))
|
|
20
|
+
throw new AdmissionError();
|
|
21
|
+
try {
|
|
22
|
+
Promise.prototype.then.call(raw, accept, reject);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
throw new AdmissionError();
|
|
26
|
+
}
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
const refuseGraph = (code) => new AdmissionError(code);
|
|
30
|
+
const graphDescriptors = { translateTrapErrors: false, requireOwnValue: false };
|
|
31
|
+
export function graphOwn(value, key) {
|
|
32
|
+
return ownData(value, key, refuseGraph, graphDescriptors);
|
|
33
|
+
}
|
|
34
|
+
export function plain(value) {
|
|
35
|
+
if (!value || typeof value !== 'object' || ![Object.prototype, null].includes(Object.getPrototypeOf(value)))
|
|
36
|
+
throw new AdmissionError();
|
|
37
|
+
}
|
|
38
|
+
export function graphName(value, maximum = 2048) {
|
|
39
|
+
return boundedName(value, maximum, refuseGraph);
|
|
40
|
+
}
|
|
41
|
+
export function graphList(value, maximum, read, overflow = 'invalid-data') {
|
|
42
|
+
return boundedList(value, maximum, read, {
|
|
43
|
+
refuse: refuseGraph,
|
|
44
|
+
overflow,
|
|
45
|
+
freeze: true,
|
|
46
|
+
descriptors: graphDescriptors,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
const reasons = new Set([
|
|
50
|
+
'ERROR_RECORDED',
|
|
51
|
+
'UNKNOWN_EFFECT_RECORDED',
|
|
52
|
+
'RETRY_STOP_RECORDED',
|
|
53
|
+
'TIMEOUT_RECORDED',
|
|
54
|
+
'RETRY_RECORDED',
|
|
55
|
+
'QUALITY_DROP_RECORDED',
|
|
56
|
+
'EVIDENCE_NOT_COLLECTED',
|
|
57
|
+
'OUTLIER_RECORDED',
|
|
58
|
+
'BUSINESS_RULE_MATCHED',
|
|
59
|
+
]);
|
|
60
|
+
export const refs = (value) => graphList(value, 16, (item) => graphName(item, 512));
|
|
61
|
+
export function attention(value) {
|
|
62
|
+
return graphList(value, 32, (item) => {
|
|
63
|
+
plain(item);
|
|
64
|
+
const kind = graphOwn(item, 'kind'), reason = graphOwn(item, 'reason');
|
|
65
|
+
if (typeof kind !== 'string' ||
|
|
66
|
+
!/^[A-Za-z][A-Za-z0-9_-]{0,63}$/.test(kind) ||
|
|
67
|
+
typeof reason !== 'string' ||
|
|
68
|
+
!reasons.has(reason))
|
|
69
|
+
throw new AdmissionError();
|
|
70
|
+
const evidence = refs(graphOwn(item, 'evidence'));
|
|
71
|
+
if (kind !== 'not-collected' && evidence.length === 0)
|
|
72
|
+
throw new AdmissionError();
|
|
73
|
+
return Object.freeze({ kind, reason: reason, evidence });
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
export function gaps(value) {
|
|
77
|
+
return graphList(value, 32, (item) => {
|
|
78
|
+
plain(item);
|
|
79
|
+
const code = graphOwn(item, 'code'), nodeId = graphOwn(item, 'nodeId');
|
|
80
|
+
if (!isGapCode(code))
|
|
81
|
+
throw new AdmissionError();
|
|
82
|
+
return Object.freeze({
|
|
83
|
+
code: code,
|
|
84
|
+
...(nodeId === undefined ? {} : { nodeId: graphName(nodeId) }),
|
|
85
|
+
evidence: refs(graphOwn(item, 'evidence')),
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
export function adjacency(value, maximum) {
|
|
90
|
+
plain(value);
|
|
91
|
+
const complete = graphOwn(value, 'complete');
|
|
92
|
+
if (typeof complete !== 'boolean')
|
|
93
|
+
throw new AdmissionError();
|
|
94
|
+
const edges = graphList(graphOwn(value, 'edges'), maximum, (item) => {
|
|
95
|
+
plain(item);
|
|
96
|
+
const nodeId = graphName(graphOwn(item, 'nodeId')), kind = graphOwn(item, 'kind'), attribution = graphOwn(item, 'attribution');
|
|
97
|
+
if ((kind !== 'data' && kind !== 'control') ||
|
|
98
|
+
(attribution !== 'per-write' && attribution !== 'stage' && attribution !== 'recorded-control') ||
|
|
99
|
+
(kind === 'control' && attribution !== 'recorded-control') ||
|
|
100
|
+
(kind === 'data' && attribution === 'recorded-control'))
|
|
101
|
+
throw new AdmissionError();
|
|
102
|
+
return Object.freeze({ nodeId, kind, attribution, evidence: refs(graphOwn(item, 'evidence')) });
|
|
103
|
+
}, 'edge-limit');
|
|
104
|
+
return Object.freeze({ edges, complete });
|
|
105
|
+
}
|
|
106
|
+
export const nodeIds = (value, max) => graphList(value, max, (item) => graphName(item), 'node-limit');
|
|
107
|
+
export const gap = (code, nodeId) => Object.freeze({ code, ...(nodeId === undefined ? {} : { nodeId }), evidence: Object.freeze([]) });
|
|
108
|
+
export function uniqueGaps(items) {
|
|
109
|
+
const seen = new Set();
|
|
110
|
+
return Object.freeze(items.filter((item) => {
|
|
111
|
+
const key = JSON.stringify(item);
|
|
112
|
+
if (seen.has(key))
|
|
113
|
+
return false;
|
|
114
|
+
seen.add(key);
|
|
115
|
+
return true;
|
|
116
|
+
}));
|
|
117
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Gap, MarkedNode, NodeId, TraversedEdge } from './types.js';
|
|
2
|
+
import { Session } from './session.js';
|
|
3
|
+
export interface Walk {
|
|
4
|
+
readonly nodes: readonly NodeId[];
|
|
5
|
+
readonly marked: readonly MarkedNode[];
|
|
6
|
+
readonly edges: readonly TraversedEdge[];
|
|
7
|
+
readonly gaps: readonly Gap[];
|
|
8
|
+
}
|
|
9
|
+
/** BFS discovers the shallowest arrival; a repeated diamond node is not a cycle. */
|
|
10
|
+
export declare function walk(session: Session, seeds: readonly NodeId[], direction: 'parents' | 'children'): Promise<Walk>;
|
|
11
|
+
export declare function frontier(result: Walk): readonly MarkedNode[];
|
|
12
|
+
export declare function pathTo(edges: readonly TraversedEdge[], start: NodeId, target: NodeId): readonly NodeId[];
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { adjacency, attention, gap, gaps, uniqueGaps } from './validation.js';
|
|
2
|
+
/** BFS discovers the shallowest arrival; a repeated diamond node is not a cycle. */
|
|
3
|
+
export async function walk(session, seeds, direction) {
|
|
4
|
+
const queue = [], seen = new Set(), marks = [], edges = [], missing = [];
|
|
5
|
+
const enqueue = (id, depth) => {
|
|
6
|
+
if (seen.has(id))
|
|
7
|
+
return;
|
|
8
|
+
const refusal = session.admit(id);
|
|
9
|
+
if (refusal) {
|
|
10
|
+
missing.push(refusal);
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
seen.add(id);
|
|
14
|
+
queue.push([id, depth]);
|
|
15
|
+
};
|
|
16
|
+
for (const id of seeds)
|
|
17
|
+
enqueue(id, 0);
|
|
18
|
+
for (let index = 0; index < queue.length; index++) {
|
|
19
|
+
const [id, depth] = queue[index], stopped = session.stopGap(id);
|
|
20
|
+
if (stopped) {
|
|
21
|
+
missing.push(stopped);
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
const marked = await session.read('attention', id, attention);
|
|
25
|
+
if (marked.gap)
|
|
26
|
+
missing.push(marked.gap);
|
|
27
|
+
if (marked.value?.length) {
|
|
28
|
+
marks.push(Object.freeze({ nodeId: id, attention: marked.value }));
|
|
29
|
+
if (marked.value.some((item) => item.kind === 'not-collected'))
|
|
30
|
+
missing.push(gap('not-collected', id));
|
|
31
|
+
}
|
|
32
|
+
const coverage = await session.read('gaps', id, gaps);
|
|
33
|
+
if (coverage.gap)
|
|
34
|
+
missing.push(coverage.gap);
|
|
35
|
+
if (coverage.value)
|
|
36
|
+
missing.push(...coverage.value);
|
|
37
|
+
const page = await session.read(direction, id, (raw) => adjacency(raw, session.limits.maxEdges));
|
|
38
|
+
if (page.gap) {
|
|
39
|
+
missing.push(page.gap);
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (!page.value)
|
|
43
|
+
continue;
|
|
44
|
+
if (!page.value.complete)
|
|
45
|
+
missing.push(gap('not-collected', id));
|
|
46
|
+
if (depth >= session.limits.maxDepth) {
|
|
47
|
+
if (page.value.edges.length)
|
|
48
|
+
missing.push(gap('depth-limit', id));
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
for (const edge of page.value.edges) {
|
|
52
|
+
if (session.edges >= session.limits.maxEdges) {
|
|
53
|
+
missing.push(gap('edge-limit', id));
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
session.edges++;
|
|
57
|
+
const refusal = session.admit(edge.nodeId);
|
|
58
|
+
if (refusal) {
|
|
59
|
+
missing.push(refusal);
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
edges.push(Object.freeze({
|
|
63
|
+
from: id,
|
|
64
|
+
to: edge.nodeId,
|
|
65
|
+
kind: edge.kind,
|
|
66
|
+
attribution: edge.attribution,
|
|
67
|
+
evidence: edge.evidence,
|
|
68
|
+
}));
|
|
69
|
+
if (edge.attribution === 'stage')
|
|
70
|
+
missing.push(gap('conservative', id));
|
|
71
|
+
enqueue(edge.nodeId, depth + 1);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (hasCycle(edges))
|
|
75
|
+
missing.push(gap('cycle'));
|
|
76
|
+
return Object.freeze({
|
|
77
|
+
nodes: Object.freeze(queue.map((row) => row[0])),
|
|
78
|
+
marked: Object.freeze(marks),
|
|
79
|
+
edges: Object.freeze(edges),
|
|
80
|
+
gaps: uniqueGaps(missing),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
/** Iterative three-colour walk; shared nodes remain distinguishable from back edges. */
|
|
84
|
+
function hasCycle(edges) {
|
|
85
|
+
const next = new Map();
|
|
86
|
+
for (const edge of edges) {
|
|
87
|
+
if (!next.has(edge.from))
|
|
88
|
+
next.set(edge.from, []);
|
|
89
|
+
next.get(edge.from).push(edge.to);
|
|
90
|
+
}
|
|
91
|
+
const state = new Map();
|
|
92
|
+
for (const seed of next.keys()) {
|
|
93
|
+
if (state.has(seed))
|
|
94
|
+
continue;
|
|
95
|
+
const stack = [[seed, 0]];
|
|
96
|
+
state.set(seed, 1);
|
|
97
|
+
while (stack.length) {
|
|
98
|
+
const frame = stack[stack.length - 1], children = next.get(frame[0]) ?? [];
|
|
99
|
+
if (frame[1] >= children.length) {
|
|
100
|
+
state.set(frame[0], 2);
|
|
101
|
+
stack.pop();
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
const child = children[frame[1]++];
|
|
105
|
+
if (state.get(child) === 1)
|
|
106
|
+
return true;
|
|
107
|
+
if (!state.has(child)) {
|
|
108
|
+
state.set(child, 1);
|
|
109
|
+
stack.push([child, 0]);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
export function frontier(result) {
|
|
116
|
+
const marked = new Set(result.marked
|
|
117
|
+
.filter((item) => item.attention.some((mark) => mark.kind !== 'not-collected'))
|
|
118
|
+
.map((item) => item.nodeId));
|
|
119
|
+
const pending = new Map(result.nodes.map((id) => [id, 0]));
|
|
120
|
+
const dependents = new Map();
|
|
121
|
+
for (const edge of result.edges) {
|
|
122
|
+
pending.set(edge.from, (pending.get(edge.from) ?? 0) + 1);
|
|
123
|
+
if (!dependents.has(edge.to))
|
|
124
|
+
dependents.set(edge.to, []);
|
|
125
|
+
dependents.get(edge.to).push(edge.from);
|
|
126
|
+
}
|
|
127
|
+
// Process causal ancestors first, propagating one boolean through the DAG.
|
|
128
|
+
// This is O(nodes + edges), rather than a fresh ancestry scan per marked node.
|
|
129
|
+
const queue = [...pending].filter(([, count]) => count === 0).map(([id]) => id);
|
|
130
|
+
const earlierMark = new Set(), eligible = new Set();
|
|
131
|
+
for (let i = 0; i < queue.length; i++) {
|
|
132
|
+
const id = queue[i];
|
|
133
|
+
if (marked.has(id) && !earlierMark.has(id))
|
|
134
|
+
eligible.add(id);
|
|
135
|
+
for (const dependent of dependents.get(id) ?? []) {
|
|
136
|
+
if (marked.has(id) || earlierMark.has(id))
|
|
137
|
+
earlierMark.add(dependent);
|
|
138
|
+
const count = pending.get(dependent) - 1;
|
|
139
|
+
pending.set(dependent, count);
|
|
140
|
+
if (count === 0)
|
|
141
|
+
queue.push(dependent);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// Nodes left in a cycle cannot supply a validated earliest ancestor.
|
|
145
|
+
return result.marked.filter((item) => eligible.has(item.nodeId));
|
|
146
|
+
}
|
|
147
|
+
export function pathTo(edges, start, target) {
|
|
148
|
+
const queue = [start], previous = new Map([[start, undefined]]);
|
|
149
|
+
const next = new Map();
|
|
150
|
+
for (const edge of edges) {
|
|
151
|
+
if (!next.has(edge.from))
|
|
152
|
+
next.set(edge.from, []);
|
|
153
|
+
next.get(edge.from).push(edge.to);
|
|
154
|
+
}
|
|
155
|
+
for (let i = 0; i < queue.length; i++) {
|
|
156
|
+
const id = queue[i];
|
|
157
|
+
if (id === target)
|
|
158
|
+
break;
|
|
159
|
+
for (const child of next.get(id) ?? [])
|
|
160
|
+
if (!previous.has(child)) {
|
|
161
|
+
previous.set(child, id);
|
|
162
|
+
queue.push(child);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (!previous.has(target))
|
|
166
|
+
return Object.freeze([]);
|
|
167
|
+
const path = [];
|
|
168
|
+
let id = target;
|
|
169
|
+
while (id !== undefined) {
|
|
170
|
+
path.push(id);
|
|
171
|
+
id = previous.get(id);
|
|
172
|
+
}
|
|
173
|
+
return Object.freeze(path.reverse());
|
|
174
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { GapCode } from './codes.js';
|
|
2
|
+
/** One refusal representation; subclasses preserve the graph-port and record-loader boundaries. */
|
|
3
|
+
export declare class EvidenceRefusal extends Error {
|
|
4
|
+
readonly code: GapCode;
|
|
5
|
+
constructor(code: GapCode, message: string);
|
|
6
|
+
}
|
|
7
|
+
export type RefuseEvidence = (code?: GapCode) => Error;
|
|
8
|
+
interface DescriptorPolicy {
|
|
9
|
+
readonly translateTrapErrors: boolean;
|
|
10
|
+
readonly requireOwnValue: boolean;
|
|
11
|
+
}
|
|
12
|
+
/** Read data descriptors only; each boundary keeps its existing treatment of proxy traps. */
|
|
13
|
+
export declare function ownData(value: unknown, key: string, refuse: RefuseEvidence, policy: DescriptorPolicy): unknown;
|
|
14
|
+
/** Names share one grammar, with explicit limits and the record path separator exception. */
|
|
15
|
+
export declare function boundedName(value: unknown, maximum: number, refuse: RefuseEvidence, path?: boolean): string;
|
|
16
|
+
/** Bound iteration before reading entries, without invoking element accessors. */
|
|
17
|
+
export declare function boundedList<T>(value: unknown, maximum: number, read: (item: unknown) => T, options: {
|
|
18
|
+
readonly refuse: RefuseEvidence;
|
|
19
|
+
readonly overflow: GapCode;
|
|
20
|
+
readonly freeze: boolean;
|
|
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;
|
|
26
|
+
}): readonly T[];
|
|
27
|
+
/** Charge retained fields against the same mutable allowance across admission helpers. */
|
|
28
|
+
export declare function spendFields(budget: {
|
|
29
|
+
left: number;
|
|
30
|
+
}, count: number, refuse: RefuseEvidence): void;
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/** One refusal representation; subclasses preserve the graph-port and record-loader boundaries. */
|
|
2
|
+
export class EvidenceRefusal extends Error {
|
|
3
|
+
code;
|
|
4
|
+
constructor(code, message) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.code = code;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
/** Read data descriptors only; each boundary keeps its existing treatment of proxy traps. */
|
|
10
|
+
export function ownData(value, key, refuse, policy) {
|
|
11
|
+
if (!value || typeof value !== 'object')
|
|
12
|
+
throw refuse();
|
|
13
|
+
let descriptor;
|
|
14
|
+
if (policy.translateTrapErrors) {
|
|
15
|
+
try {
|
|
16
|
+
descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
throw refuse();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
else
|
|
23
|
+
descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
24
|
+
if (!descriptor)
|
|
25
|
+
return undefined;
|
|
26
|
+
if (policy.requireOwnValue ? !Object.hasOwn(descriptor, 'value') : !('value' in descriptor))
|
|
27
|
+
throw refuse();
|
|
28
|
+
return descriptor.value;
|
|
29
|
+
}
|
|
30
|
+
/** Names share one grammar, with explicit limits and the record path separator exception. */
|
|
31
|
+
export function boundedName(value, maximum, refuse, path = false) {
|
|
32
|
+
if (typeof value !== 'string' ||
|
|
33
|
+
!value.length ||
|
|
34
|
+
value.length > maximum ||
|
|
35
|
+
(path ? /[\u0000-\u001e\u007f-\u009f]/ : /[\u0000-\u001f\u007f-\u009f]/).test(value))
|
|
36
|
+
throw refuse();
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
/** Bound iteration before reading entries, without invoking element accessors. */
|
|
40
|
+
export function boundedList(value, maximum, read, options) {
|
|
41
|
+
const { refuse, overflow, freeze, descriptors } = options;
|
|
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)
|
|
54
|
+
throw refuse();
|
|
55
|
+
const length = ownData(value, 'length', refuse, descriptors);
|
|
56
|
+
if (typeof length !== 'number' || !Number.isSafeInteger(length) || length < 0)
|
|
57
|
+
throw refuse();
|
|
58
|
+
if (options.expectedLength !== undefined && length !== options.expectedLength)
|
|
59
|
+
throw refuse();
|
|
60
|
+
if (length > maximum)
|
|
61
|
+
throw refuse(overflow);
|
|
62
|
+
const result = [];
|
|
63
|
+
for (let index = 0; index < length; index++)
|
|
64
|
+
result.push(read(ownData(value, String(index), refuse, descriptors)));
|
|
65
|
+
return freeze ? Object.freeze(result) : result;
|
|
66
|
+
}
|
|
67
|
+
/** Charge retained fields against the same mutable allowance across admission helpers. */
|
|
68
|
+
export function spendFields(budget, count, refuse) {
|
|
69
|
+
budget.left -= count;
|
|
70
|
+
if (budget.left < 0)
|
|
71
|
+
throw refuse('node-limit');
|
|
72
|
+
}
|
|
@@ -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[];
|