foottrace 1.2.0 → 1.4.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 +32 -71
- 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 +7 -3
- /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
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ Foottrace owns the record layer used by footprintjs 10. Older stored records rem
|
|
|
13
13
|
| `foottrace` | Record types, runtime and attempt IDs, replay, time-travel cursors, causal and variable slices, honesty codes, the stored-record reader |
|
|
14
14
|
| `foottrace/write` | `SharedMemory`, `EventLog`, `RecordFrame`, encoding and write types, writer-owned freezing, the record sink port and `memorySink` |
|
|
15
15
|
| `foottrace/paths` | Record-path encoding/decoding, nested access and safe diagnostic writes |
|
|
16
|
-
| `foottrace/cause`
|
|
16
|
+
| `foottrace/cause` | Optional bounded attention, cause and impact queries; in-memory and lazily loaded stored-record adapters |
|
|
17
17
|
|
|
18
18
|
Every declaration has one public entry point. Producer-facing `Attention` and `AttentionKind` are type-only root exports; the cause algorithms live only on `/cause`. Existing root, `/write` and `/paths` imports retain their ESM and CommonJS doors. There are no public deep imports or `/testing` entry point. FootPrint's engine, observer channels, recorders and stores remain in `footprintjs`.
|
|
19
19
|
|
|
@@ -43,6 +43,31 @@ Writers record during execution. Readers query what was recorded; they do not re
|
|
|
43
43
|
|
|
44
44
|
See the [record contract](docs/guides/record-contract.md), [time-travel contract](src/lib/time-travel/README.md), and [slice contract](src/lib/slice/README.md).
|
|
45
45
|
|
|
46
|
+
## Evidence requirements — 1.3.0
|
|
47
|
+
|
|
48
|
+
`RequirementResult` and `readRequirementResults(unknown)` on the root door carry
|
|
49
|
+
one producer-issued requirement result per finding/window, the claims it
|
|
50
|
+
`affects`, explicit unresolved `RequirementReason` codes, check/evidence/attempt
|
|
51
|
+
references and available action references. Domain details are namespaced IDs;
|
|
52
|
+
raw subjects, payloads, credentials and executable actions do not enter this
|
|
53
|
+
contract. The bounded reader rejects malformed or oversized batches atomically
|
|
54
|
+
and returns detached frozen records. It works through the root ESM and CommonJS
|
|
55
|
+
doors without loading the optional cause reader.
|
|
56
|
+
|
|
57
|
+
`RequirementReason` is the twelve-code domain-evidence subset of the single
|
|
58
|
+
`GapCode` registry. Result and check reasons refuse trace-walk codes such as
|
|
59
|
+
`cycle` and `node-limit`; generic cause gaps and typed admission errors retain
|
|
60
|
+
their broader vocabulary.
|
|
61
|
+
|
|
62
|
+
Structural admission never authenticates a producer or establishes that a
|
|
63
|
+
`satisfied` assertion is true. The host verifies current query, run, caller scope,
|
|
64
|
+
artifact provenance and domain validity before rendering each claim. An unresolved
|
|
65
|
+
requirement blocks only its affected conclusions; independently supported facts
|
|
66
|
+
can remain. Valid-time and learned-time references are separate, and missing
|
|
67
|
+
validity is unknown. Action references resolve through a host-owned closed
|
|
68
|
+
registry, with permissions checked again at execution. See the [contract and
|
|
69
|
+
limits](src/lib/evidence/README.md) and [reference-only example](examples/post-execution/time-travel/08-evidence-requirements.ts).
|
|
70
|
+
|
|
46
71
|
## A record that survives the run
|
|
47
72
|
|
|
48
73
|
A log can also hand every entry to a sink **as it lands**, so a run that is killed still leaves its record behind. The sink sees four entries — `opened` (the base), `commit` (each bundle, after it is frozen), `note` (anything the writer wants kept beside the log, as it happens) and `closed` (how the log ended) — and `memorySink` is the reference adapter. Write your own for a file, a bucket or a table by implementing those four methods.
|
|
@@ -100,7 +125,7 @@ The parser reads from the right, so a stage ID may contain `@` or `.` and no cha
|
|
|
100
125
|
|
|
101
126
|
## Compatibility and development
|
|
102
127
|
|
|
103
|
-
Node.js 22 or newer; develop with Node.js 24 and npm 11. Both CommonJS and ESM are provided. Browser-capable source uses no Node runtime APIs. Use one module format consistently in an application; CJS and ESM have separate module instances.
|
|
128
|
+
Node.js 22 or newer; develop with Node.js 24 and npm 11. Both CommonJS and ESM are provided. Browser-capable source uses no Node runtime APIs. Use one module format consistently in an application; CJS and ESM have separate module instances. For a documented refusal such as `RequirementValidationError`, inspect the known operation's fixed `code` (and fixed `message` when distinguishing error kinds), rather than using `instanceof` across module formats.
|
|
104
129
|
|
|
105
130
|
```sh
|
|
106
131
|
npm ci
|
|
@@ -114,13 +139,13 @@ Record bytes are part of the versioning promise: refactors never re-pin; a named
|
|
|
114
139
|
MIT license. Contact: sanjay@footprintjs.dev.
|
|
115
140
|
|
|
116
141
|
|
|
117
|
-
## Cause and impact — 1.2.0
|
|
142
|
+
## Cause and impact — 1.2.0
|
|
118
143
|
|
|
119
|
-
`foottrace/cause`
|
|
144
|
+
`foottrace/cause` answers bounded, read-only cause and impact queries from recorded data and control dependencies. It never reruns business code or calls a model. Findings carry evidence references, alternatives and gaps; incomplete backward evidence withholds a definite root. Confidence describes evidence coverage, not proof of a provider's internal failure.
|
|
120
145
|
|
|
121
|
-
|
|
146
|
+
Enable `writeProvenance: 'reads-prefix'` in the producer for precise per-write queries. Stage-level capture remains conservative, and a reader cannot restore missing provenance in old records.
|
|
122
147
|
|
|
123
|
-
|
|
148
|
+
The `/cause` door provides typed ESM and CommonJS exports. Use `import { explain } from 'foottrace/cause'` in ESM or `const { explain } = require('foottrace/cause')` in CommonJS. Native `import('foottrace/cause')` remains available for lazy loading.
|
|
124
149
|
|
|
125
150
|
```ts
|
|
126
151
|
import type { Attention } from 'foottrace';
|
|
@@ -140,68 +165,4 @@ const finding = await explain(graph, selectedNodeId, {
|
|
|
140
165
|
// An LLM can phrase these validated findings without inventing a cause.
|
|
141
166
|
```
|
|
142
167
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
The default earliest-ancestor rule does not restrict a custom `rankRoots`: that callback receives all supported marked nodes in the backward walk, including a marked selected node, and may select a subset containing a nearer ancestor. It cannot introduce foreign or duplicate identities. This differs from `rankImpact`, which must return an exact permutation of every discovered descendant. Name the chosen root strategy when presenting its findings; a custom choice is not the default earliest-ancestor conclusion.
|
|
146
|
-
|
|
147
|
-
Query defaults are 100 nodes, depth 20, 1,000 edges, 1,000 calls and one second. Hard ceilings are 10,000 nodes, depth 1,000, 100,000 edges/calls and 30 seconds. The same budget covers both directions and ranking. Callbacks are trusted executable code: asynchronous waiting is bounded and receives an abort signal, while synchronous callbacks must cooperate. A late synchronous result is rejected after it returns. Hosts must use non-secret node and evidence IDs; the reader does not classify arbitrary ID strings for personal information.
|
|
148
|
-
|
|
149
|
-
Async hosts must return ordinary current-realm native Promises with no own `constructor`; promise wrappers are trusted host control objects, while their resolved data is untrusted. Custom thenables, subclasses and constructor/species overrides are outside this contract and the host must handle their rejections itself. The reader never mutates a promise to observe it. Ordinary native rejections are consumed and become fixed gaps; synchronous data objects' `then` accessors are never invoked.
|
|
150
|
-
|
|
151
|
-
### Per-write provenance recipe
|
|
152
|
-
|
|
153
|
-
For precise edges between individual writes, configure the producer's record encoding with `writeProvenance: 'reads-prefix'`. `RecordFrame` captures the reads that preceded each write. For an execution that reads `a`, writes `x`, then reads `b` and writes `y`, the `x` query uses its recorded read prefix; the `y` query uses the later prefix. Without per-write provenance, the reader must conservatively consider all reads of that stage for either value and report that fallback. Later reads cannot be retroactively assigned to an earlier write. An overwritten version is distinct from the newer value, so impact follows the selected write rather than every later use of the same key name.
|
|
154
|
-
|
|
155
|
-
```ts
|
|
156
|
-
import { EventLog, RecordFrame, SharedMemory } from 'foottrace/write';
|
|
157
|
-
const state = new SharedMemory(undefined, { a: 1, b: 2 });
|
|
158
|
-
const log = new EventLog(state.getState());
|
|
159
|
-
const frame = new RecordFrame(state, log);
|
|
160
|
-
frame.useEncoding({ commitValues: 'full', writeProvenance: 'reads-prefix' });
|
|
161
|
-
frame.noteRead([], 'a');
|
|
162
|
-
frame.write(frame.at([], 'x'), 1, 'set');
|
|
163
|
-
frame.noteRead([], 'b');
|
|
164
|
-
frame.write(frame.at([], 'y'), 3, 'set');
|
|
165
|
-
frame.commit(() => ({ stage: 'Derive', stageId: 'derive', runtimeStageId: 'derive#0' }));
|
|
166
|
-
frame.release();
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
A stored host can expose one authorized log at a time:
|
|
170
|
-
|
|
171
|
-
```ts
|
|
172
|
-
import { createStoredRecordGraph, explain, type StoredRecordHost } from 'foottrace/cause';
|
|
173
|
-
|
|
174
|
-
// loadLog(address, { signal }) returns native entries for exactly that address,
|
|
175
|
-
// plus recorded read/control/attention maps when available. It checks access.
|
|
176
|
-
declare const host: StoredRecordHost;
|
|
177
|
-
const graph = createStoredRecordGraph(host, {
|
|
178
|
-
maxScopes: 8, maxCommits: 256, maxFields: 20_000, maxEdges: 64, loadTimeoutMs: 500,
|
|
179
|
-
});
|
|
180
|
-
const address = { logRunId: 'run-1', drillPath: [] };
|
|
181
|
-
const selected = graph.nodeId(address, { runtimeStageId: 'derive#0', commitIdx: 0, key: 'y' });
|
|
182
|
-
const finding = await explain(graph, selected, { timeoutMs: 750, maxNodes: 50 });
|
|
183
|
-
// Only the requested scope is loaded; a different subflow is opened on demand.
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
The stored adapter loads a requested log through the host, reuses `readStoredRecord`, and retains incomplete-record and gap evidence. A graph instance caches admitted scope snapshots; create a new instance to refresh a record that is still being written. Bind each host/graph/cache to one authorization domain or authorize before every query: a cache hit does not call `loadLog` again. Never share a global graph whose loader consults a changing request identity. Subflow containment is a navigation relationship, not automatically a causal dependency. Scope, execution, write position and optional key qualify node identities. The host must authorize each storage scope; cross-service and cross-run causal stitching remain outside this candidate. Lazy loading is real only when the host loader fetches a requested scope: expanding a cursor over an already loaded snapshot is an in-memory drill.
|
|
187
|
-
|
|
188
|
-
### Established algorithms
|
|
189
|
-
|
|
190
|
-
Foundations: [Weiser, *Program slicing* (1981)](https://dl.acm.org/doi/10.5555/800078.802557), [Korel and Laski, *Dynamic program slicing* (1988)](https://doi.org/10.1016/0020-0190(88)90054-3), [Agrawal and Horgan, *Dynamic program slicing* (1990)](https://doi.org/10.1145/93542.93576), [Ferrante, Ottenstein and Warren, *The program dependence graph and its use in optimization* (1987)](https://doi.org/10.1145/24039.24041), and [Buneman, Khanna and Tan, *Why and Where: A Characterization of Data Provenance* (2001)](https://www.pure.ed.ac.uk/ws/files/16509989/Why_and_Where_A_Characterization_of_Data_Provenance.pdf). These are the historical techniques; this reader applies them only to dependencies present in a recording.
|
|
191
|
-
|
|
192
|
-
| Query | Established name |
|
|
193
|
-
| --- | --- |
|
|
194
|
-
| Recorded ancestors influencing a value or node | **Backward dynamic program slicing** (Weiser, 1981; Korel and Laski, 1988; Agrawal and Horgan, 1990) |
|
|
195
|
-
| Recorded data plus decision/control edges | **Program dependence graph** (Ferrante, Ottenstein and Warren, 1987) |
|
|
196
|
-
| Recorded downstream effects | **Forward slicing**, or change-impact analysis |
|
|
197
|
-
| Why a value exists and where it came from | **Why- and where-provenance** (Buneman, Khanna and Tan, 2001) |
|
|
198
|
-
| Loading only queried graph nodes/scopes | **Demand-driven, or lazy, graph traversal** |
|
|
199
|
-
| Default candidate selection | **Root-cause localisation on a dependency DAG**, using the earliest anomalous ancestor |
|
|
200
|
-
|
|
201
|
-
These names describe the techniques, not a claim of complete language-level instrumentation. Findings are bounded by the dependencies the producer recorded. Cause queries share one deadline and node/edge/call budget across reverse traversal, ranking and forward impact; limits, cancellation and unavailable reads become gaps. Output carries references and fixed codes, never a serialized DAG or business payload. `/cause` is ESM-only and `sideEffects: false`; its unused-import browser bundle is tested to match a bundle without the import. The package has no runtime dependencies. This is an unpublished candidate; later engine stamping, viewer integration and agent tools are separate changes.
|
|
202
|
-
|
|
203
|
-
Direct `graph.openScope(address, { signal })` discovery rejects with a fixed `cause: record unavailable` error and a gap `code` on failure; an unavailable scope is never reported as an empty successful log. Public `explain` and `attentionList` queries turn unavailable reads into bounded incomplete findings.
|
|
204
|
-
|
|
205
|
-
Record-adapter coverage is conservative: its shared scope gaps can withhold a cause when a local forward edge cap is reached, even if the visible backward path is covered. A withheld cause is unknown under those bounds; it is not proof that no cause exists.
|
|
206
|
-
|
|
207
|
-
Package verification retains strict automatic entrypoint discovery for every door except `/cause`, then checks only `/cause` with Are the Types Wrong’s `esm-only` profile. No resolution error is ignored globally; the regression test fixes that exception to this one ESM-only subpath.
|
|
168
|
+
Follow the [cause and impact manual](https://github.com/footprintjs/foottrace/blob/main/docs/cause.md) to record per-write provenance, adapt an authorized stored record, run bounded queries and interpret roots, alternatives and gaps. A complete synthetic [record example](https://github.com/footprintjs/foottrace/blob/main/examples/cause-record.ts) demonstrates both in-memory and stored adapters.
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -30,3 +30,7 @@ export { deepEqual } from './lib/memory/equality.js';
|
|
|
30
30
|
export { serveRecord } from './lib/capture/freeze.js';
|
|
31
31
|
/** Record attention types are shared with producers; the optional cause reader has its own door. */
|
|
32
32
|
export type { Attention, AttentionKind } from './lib/cause/types.js';
|
|
33
|
+
/** Reference-only requirement records: structural admission never grants authority or verifies domain truth. */
|
|
34
|
+
export type { RequirementReason } from './lib/evidence/codes.js';
|
|
35
|
+
export type { RequirementCheck, RequirementCheckDisposition, RequirementResult, } from './lib/evidence/requirementTypes.js';
|
|
36
|
+
export { readRequirementResults, RequirementValidationError, REQUIREMENT_RESULT_LIMITS, } from './lib/evidence/requirements.js';
|
package/dist/esm/index.js
CHANGED
|
@@ -11,3 +11,4 @@ export { CommitRangeIndex } from './lib/recorder/CommitRangeIndex.js';
|
|
|
11
11
|
export { EXECUTION_DELIMITER, PATH_DELIMITER, isExecutionKey, joinPath, pathSegments as idPathSegments, stageIdOf, subflowSegmentsOf, } from './lib/ids/runtimeStageId.js';
|
|
12
12
|
export { deepEqual } from './lib/memory/equality.js';
|
|
13
13
|
export { serveRecord } from './lib/capture/freeze.js';
|
|
14
|
+
export { readRequirementResults, RequirementValidationError, REQUIREMENT_RESULT_LIMITS, } from './lib/evidence/requirements.js';
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { Session } from './session.js';
|
|
2
2
|
import { frontier, pathTo, walk } from './walk.js';
|
|
3
|
-
import { gap,
|
|
3
|
+
import { gap, graphName, nodeIds, graphOwn, uniqueGaps } from './validation.js';
|
|
4
4
|
import { admitRanking } from './ranking.js';
|
|
5
5
|
/** Read-only deterministic cause localisation and impact. No inference from timestamps or structural ancestry. */
|
|
6
6
|
export async function explain(graph, nodeId, options = {}) {
|
|
7
|
-
|
|
7
|
+
graphName(nodeId);
|
|
8
8
|
const session = new Session(graph, options);
|
|
9
9
|
try {
|
|
10
10
|
const backward = await walk(session, [nodeId], 'parents');
|
|
11
11
|
const missing = [...backward.gaps];
|
|
12
12
|
let candidates = frontier(backward).map((item) => item.nodeId);
|
|
13
|
-
const rank =
|
|
13
|
+
const rank = graphOwn(options, 'rankRoots');
|
|
14
14
|
if (rank !== undefined) {
|
|
15
15
|
if (typeof rank !== 'function')
|
|
16
16
|
throw new TypeError('CAUSE_CONFIGURATION_INVALID');
|
|
@@ -31,7 +31,7 @@ export async function explain(graph, nodeId, options = {}) {
|
|
|
31
31
|
const rootCause = causeComplete && candidates.length === 1 ? candidates[0] : undefined;
|
|
32
32
|
const forward = await walk(session, [nodeId], 'children');
|
|
33
33
|
let impactNodes = Object.freeze(forward.nodes.filter((id) => id !== nodeId));
|
|
34
|
-
const impactGaps = [...forward.gaps], rankImpact =
|
|
34
|
+
const impactGaps = [...forward.gaps], rankImpact = graphOwn(options, 'rankImpact');
|
|
35
35
|
if (typeof rankImpact === 'function') {
|
|
36
36
|
const selection = await session.invoke(undefined, () => rankImpact(impactNodes, session.context), (raw) => admitRanking(raw, new Set(impactNodes), {
|
|
37
37
|
exactPermutation: true,
|
|
@@ -69,19 +69,19 @@ export async function attentionList(graph, options = {}) {
|
|
|
69
69
|
const session = new Session(graph, options);
|
|
70
70
|
try {
|
|
71
71
|
let seeds = [], missing = [];
|
|
72
|
-
const requested =
|
|
72
|
+
const requested = graphOwn(options, 'nodeIds');
|
|
73
73
|
const admitSeeds = (raw) => {
|
|
74
74
|
// Only inspect the bounded prefix; the unread tail is explicitly incomplete.
|
|
75
75
|
if (!Array.isArray(raw))
|
|
76
76
|
return nodeIds(raw, session.limits.maxNodes);
|
|
77
|
-
const length =
|
|
77
|
+
const length = graphOwn(raw, 'length');
|
|
78
78
|
if (typeof length !== 'number' || !Number.isSafeInteger(length) || length < 0)
|
|
79
79
|
return nodeIds(raw, session.limits.maxNodes);
|
|
80
80
|
if (length > session.limits.maxNodes)
|
|
81
81
|
missing.push(gap('node-limit'));
|
|
82
82
|
const prefix = [];
|
|
83
83
|
for (let i = 0; i < Math.min(length, session.limits.maxNodes); i++)
|
|
84
|
-
prefix.push(
|
|
84
|
+
prefix.push(graphOwn(raw, String(i)));
|
|
85
85
|
return nodeIds(prefix, session.limits.maxNodes);
|
|
86
86
|
};
|
|
87
87
|
if (requested !== undefined) {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { GraphContext } from './types.js';
|
|
2
|
+
/** Share bounded derived work within one query without retaining it in the graph's storage cache. */
|
|
3
|
+
declare class QueryMemo {
|
|
4
|
+
private readonly maximumEntries;
|
|
5
|
+
private readonly owners;
|
|
6
|
+
private entries;
|
|
7
|
+
private closed;
|
|
8
|
+
constructor(maximumEntries: number);
|
|
9
|
+
remember<T>(owner: object, key: unknown, create: () => T): T;
|
|
10
|
+
close(): void;
|
|
11
|
+
}
|
|
12
|
+
/** Reserve one resolution and one inspection per admitted node, shared across all adapter owners. */
|
|
13
|
+
export declare function beginQueryMemo(context: GraphContext, maximumNodes: number): void;
|
|
14
|
+
/** Direct graph calls have no managed query lifetime and deliberately remain uncached. */
|
|
15
|
+
export declare function queryMemo(context: GraphContext): QueryMemo | undefined;
|
|
16
|
+
/** Clear synchronously without aborting an otherwise successful query's public signal. */
|
|
17
|
+
export declare function endQueryMemo(context: GraphContext): void;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/** Share bounded derived work within one query without retaining it in the graph's storage cache. */
|
|
2
|
+
class QueryMemo {
|
|
3
|
+
maximumEntries;
|
|
4
|
+
owners = new Map();
|
|
5
|
+
entries = 0;
|
|
6
|
+
closed = false;
|
|
7
|
+
constructor(maximumEntries) {
|
|
8
|
+
this.maximumEntries = maximumEntries;
|
|
9
|
+
}
|
|
10
|
+
remember(owner, key, create) {
|
|
11
|
+
const previous = this.owners.get(owner);
|
|
12
|
+
if (previous?.has(key))
|
|
13
|
+
return previous.get(key);
|
|
14
|
+
if (this.closed || this.entries >= this.maximumEntries)
|
|
15
|
+
return create();
|
|
16
|
+
const value = create();
|
|
17
|
+
// Creation can finish after disposal or re-enter other readers; neither may refill a closed/full memo.
|
|
18
|
+
if (!this.closed && this.entries < this.maximumEntries) {
|
|
19
|
+
const bucket = this.owners.get(owner) ?? new Map();
|
|
20
|
+
if (!bucket.has(key))
|
|
21
|
+
this.entries++;
|
|
22
|
+
bucket.set(key, value);
|
|
23
|
+
this.owners.set(owner, bucket);
|
|
24
|
+
}
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
close() {
|
|
28
|
+
this.closed = true;
|
|
29
|
+
this.owners.clear();
|
|
30
|
+
this.entries = 0;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const queries = new WeakMap();
|
|
34
|
+
/** Reserve one resolution and one inspection per admitted node, shared across all adapter owners. */
|
|
35
|
+
export function beginQueryMemo(context, maximumNodes) {
|
|
36
|
+
queries.set(context, new QueryMemo(maximumNodes * 2));
|
|
37
|
+
}
|
|
38
|
+
/** Direct graph calls have no managed query lifetime and deliberately remain uncached. */
|
|
39
|
+
export function queryMemo(context) {
|
|
40
|
+
return queries.get(context);
|
|
41
|
+
}
|
|
42
|
+
/** Clear synchronously without aborting an otherwise successful query's public signal. */
|
|
43
|
+
export function endQueryMemo(context) {
|
|
44
|
+
queries.get(context)?.close();
|
|
45
|
+
queries.delete(context);
|
|
46
|
+
}
|
|
@@ -1,38 +1,31 @@
|
|
|
1
1
|
import { childrenForRecordNode } from './recordChildren.js';
|
|
2
2
|
import { attentionForRecordNode, gapForRecordFailure } from './recordCoverage.js';
|
|
3
|
-
import { nodeIdFor
|
|
3
|
+
import { nodeIdFor } from './recordIdentity.js';
|
|
4
4
|
import { createRecordScopeLoader, openRecordScope } from './recordLoading.js';
|
|
5
|
-
import {
|
|
5
|
+
import { createRecordQuery, whenResolved } from './recordQuery.js';
|
|
6
6
|
import { recordOptions } from './recordScope.js';
|
|
7
|
-
import { addressKey, addressOf,
|
|
7
|
+
import { addressKey, addressOf, recordList, recordOwn, RecordRefusal } from './recordValidation.js';
|
|
8
|
+
/** Give each failed port its own incomplete page rather than sharing a mutable edge array. */
|
|
9
|
+
const unavailableAdjacency = () => ({ edges: [], complete: false });
|
|
8
10
|
export { recordOptions } from './recordScope.js';
|
|
9
11
|
/** Common engine for memory and one-scope-at-a-time host storage. Loader functions are trusted executable code. */
|
|
10
12
|
export function createLazyRecordGraph(load, options = {}) {
|
|
11
13
|
const limits = recordOptions(options), loader = createRecordScopeLoader(load, limits);
|
|
12
|
-
const
|
|
14
|
+
const query = createRecordQuery(loader, limits.maxEdges);
|
|
13
15
|
return Object.freeze({
|
|
14
16
|
nodeId: (address, node) => nodeIdFor(address, node),
|
|
15
17
|
openScope: (address, context) => openRecordScope(address, context, loader),
|
|
16
18
|
async parents(nodeId, context) {
|
|
17
|
-
|
|
18
|
-
return 'failure' in node
|
|
19
|
-
? { edges: [], complete: false }
|
|
20
|
-
: inspectRecordNode(node, limits.maxEdges, loader.extraGapCodes(node.scope)).adjacency;
|
|
19
|
+
return whenResolved(await query.resolve(nodeId, context), (node) => query.inspect(node, context).adjacency, unavailableAdjacency);
|
|
21
20
|
},
|
|
22
21
|
async gaps(nodeId, context) {
|
|
23
|
-
|
|
24
|
-
return 'failure' in node
|
|
25
|
-
? gapForRecordFailure(node.failure)
|
|
26
|
-
: inspectRecordNode(node, limits.maxEdges, loader.extraGapCodes(node.scope)).gaps;
|
|
22
|
+
return whenResolved(await query.resolve(nodeId, context), (node) => query.inspect(node, context).gaps, (failure) => gapForRecordFailure(failure.failure));
|
|
27
23
|
},
|
|
28
24
|
async attention(nodeId, context) {
|
|
29
|
-
return
|
|
25
|
+
return whenResolved(await query.resolve(nodeId, context), attentionForRecordNode, () => []);
|
|
30
26
|
},
|
|
31
27
|
async children(nodeId, context) {
|
|
32
|
-
|
|
33
|
-
return 'failure' in node
|
|
34
|
-
? { edges: [], complete: false }
|
|
35
|
-
: childrenForRecordNode(node, limits.maxEdges, loader.extraGapCodes(node.scope));
|
|
28
|
+
return whenResolved(await query.resolve(nodeId, context), (node) => childrenForRecordNode(node, limits.maxEdges, query.inspect(node, context)), unavailableAdjacency);
|
|
36
29
|
},
|
|
37
30
|
});
|
|
38
31
|
}
|
|
@@ -40,8 +33,8 @@ export function createLazyRecordGraph(load, options = {}) {
|
|
|
40
33
|
export function createRecordGraph(scopes, options = {}) {
|
|
41
34
|
const limits = recordOptions(options);
|
|
42
35
|
return createLazyRecordGraph((address) => {
|
|
43
|
-
for (const raw of
|
|
44
|
-
if (addressKey(addressOf(
|
|
36
|
+
for (const raw of recordList(scopes, limits.maxScopes)) {
|
|
37
|
+
if (addressKey(addressOf(recordOwn(raw, 'address'))) === addressKey(address))
|
|
45
38
|
return { scope: raw };
|
|
46
39
|
}
|
|
47
40
|
throw new RecordRefusal('unavailable');
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ResolvedRecordNode } from './recordTypes.js';
|
|
2
|
-
import type { Adjacency
|
|
1
|
+
import type { RecordInspection, ResolvedRecordNode } from './recordTypes.js';
|
|
2
|
+
import type { Adjacency } from './types.js';
|
|
3
3
|
/** Share parent-side coverage checks before appending data then control children in their established order. */
|
|
4
|
-
export declare function childrenForRecordNode(node: ResolvedRecordNode, maximumEdges: number,
|
|
4
|
+
export declare function childrenForRecordNode(node: ResolvedRecordNode, maximumEdges: number, inspection: RecordInspection): Adjacency;
|
|
@@ -2,7 +2,6 @@ import { forwardSliceForKey } from '../slice/forwardSliceForKey.js';
|
|
|
2
2
|
import { appendControlChildren } from './recordControl.js';
|
|
3
3
|
import { allStageReadsCollected, writtenKeysForNode } from './recordCoverage.js';
|
|
4
4
|
import { nodeIdFor } from './recordIdentity.js';
|
|
5
|
-
import { inspectRecordNode } from './recordParents.js';
|
|
6
5
|
import { readKeysForStage } from './recordScope.js';
|
|
7
6
|
/** Keep per-write and conservative fed edges distinct while retaining existing cap and deduplication order. */
|
|
8
7
|
function appendFedChildren(scope, root, maximumEdges, edges) {
|
|
@@ -67,10 +66,10 @@ function appendDataChildren(scope, identity, commitIndex, maximumEdges, edges) {
|
|
|
67
66
|
return complete;
|
|
68
67
|
}
|
|
69
68
|
/** Share parent-side coverage checks before appending data then control children in their established order. */
|
|
70
|
-
export function childrenForRecordNode(node, maximumEdges,
|
|
69
|
+
export function childrenForRecordNode(node, maximumEdges, inspection) {
|
|
71
70
|
const { scope, identity, commitIndex } = node;
|
|
72
|
-
const
|
|
73
|
-
let complete =
|
|
71
|
+
const edges = [];
|
|
72
|
+
let complete = inspection.gaps.length === 0;
|
|
74
73
|
if (!allStageReadsCollected(scope))
|
|
75
74
|
complete = false;
|
|
76
75
|
complete = appendDataChildren(scope, identity, commitIndex, maximumEdges, edges) && complete;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CommitBundle } from '../memory/types.js';
|
|
2
|
-
import type { AddRecordGap, PreparedRecordScope, RecordIdentity,
|
|
2
|
+
import type { AddRecordGap, PreparedRecordScope, RecordIdentity, ResolvedRecordNode } from './recordTypes.js';
|
|
3
3
|
import type { Attention, Gap } from './types.js';
|
|
4
4
|
/** Preserve first-observed coverage order while collapsing repeated codes from different evidence paths. */
|
|
5
5
|
export declare function recordGapCollector(): {
|
|
@@ -9,7 +9,7 @@ export declare function recordGapCollector(): {
|
|
|
9
9
|
/** Failed loading exposes a fixed category, never the host error or an apparently complete empty graph. */
|
|
10
10
|
export declare function gapForRecordFailure(code: Gap['code']): readonly Gap[];
|
|
11
11
|
/** Return captured mark references without treating an omitted attention map as collected evidence. */
|
|
12
|
-
export declare function attentionForRecordNode(node: ResolvedRecordNode
|
|
12
|
+
export declare function attentionForRecordNode(node: ResolvedRecordNode): readonly Attention[];
|
|
13
13
|
/** Emit host and capture gaps before query-derived gaps so existing evidence order stays stable. */
|
|
14
14
|
export declare function collectScopeCoverage(scope: PreparedRecordScope, bundle: CommitBundle, extraGapCodes: readonly Gap['code'][] | undefined, addGap: AddRecordGap): void;
|
|
15
15
|
/** A read-only identity has no new value life; unqualified executions retain written-key encounter order. */
|
|
@@ -20,7 +20,7 @@ export function gapForRecordFailure(code) {
|
|
|
20
20
|
/** Return captured mark references without treating an omitted attention map as collected evidence. */
|
|
21
21
|
export function attentionForRecordNode(node) {
|
|
22
22
|
// An omitted map stays unknown coverage; it is never replaced by an affirmative empty map.
|
|
23
|
-
return
|
|
23
|
+
return node.scope.marks?.get(node.identity.runtimeStageId) ?? [];
|
|
24
24
|
}
|
|
25
25
|
/** Emit host and capture gaps before query-derived gaps so existing evidence order stays stable. */
|
|
26
26
|
export function collectScopeCoverage(scope, bundle, extraGapCodes, addGap) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readKeysForStage } from './recordScope.js';
|
|
2
|
-
import { addressOf,
|
|
2
|
+
import { addressOf, recordName, recordOwn, RecordRefusal } from './recordValidation.js';
|
|
3
3
|
/** Parse only the bounded identity encoding; payload fields never become graph addresses. */
|
|
4
4
|
export function readNodeIdentity(nodeId) {
|
|
5
5
|
if (typeof nodeId !== 'string' || nodeId.length > 2048 || !nodeId.startsWith('record:'))
|
|
@@ -11,7 +11,7 @@ export function readNodeIdentity(nodeId) {
|
|
|
11
11
|
catch {
|
|
12
12
|
throw new RecordRefusal();
|
|
13
13
|
}
|
|
14
|
-
const address = addressOf(
|
|
14
|
+
const address = addressOf(recordOwn(fields, 'address')), runtimeStageId = recordName(recordOwn(fields, 'runtimeStageId')), commitIndex = recordOwn(fields, 'commitIdx'), key = recordOwn(fields, 'key'), read = recordOwn(fields, 'read');
|
|
15
15
|
if (commitIndex !== undefined && (!Number.isSafeInteger(commitIndex) || commitIndex < 0))
|
|
16
16
|
throw new RecordRefusal();
|
|
17
17
|
if ((read !== undefined && read !== true) || (read === true && key === undefined))
|
|
@@ -20,14 +20,14 @@ export function readNodeIdentity(nodeId) {
|
|
|
20
20
|
address,
|
|
21
21
|
runtimeStageId,
|
|
22
22
|
...(commitIndex === undefined ? {} : { commitIdx: commitIndex }),
|
|
23
|
-
...(key === undefined ? {} : { key:
|
|
23
|
+
...(key === undefined ? {} : { key: recordName(key, true) }),
|
|
24
24
|
...(read === true ? { read: true } : {}),
|
|
25
25
|
};
|
|
26
26
|
}
|
|
27
27
|
/** Stable field order preserves existing address/execution/key IDs across storage and memory readers. */
|
|
28
28
|
export function nodeIdFor(addressInput, node, read) {
|
|
29
29
|
try {
|
|
30
|
-
const address = addressOf(addressInput), runtimeStageId =
|
|
30
|
+
const address = addressOf(addressInput), runtimeStageId = recordName(recordOwn(node, 'runtimeStageId')), commitIndex = recordOwn(node, 'commitIdx'), key = recordOwn(node, 'key');
|
|
31
31
|
if (commitIndex !== undefined && (!Number.isSafeInteger(commitIndex) || commitIndex < 0))
|
|
32
32
|
throw new RecordRefusal();
|
|
33
33
|
const nodeId = 'record:' +
|
|
@@ -35,7 +35,7 @@ export function nodeIdFor(addressInput, node, read) {
|
|
|
35
35
|
address,
|
|
36
36
|
runtimeStageId,
|
|
37
37
|
...(commitIndex === undefined ? {} : { commitIdx: commitIndex }),
|
|
38
|
-
...(key === undefined ? {} : { key:
|
|
38
|
+
...(key === undefined ? {} : { key: recordName(key, true) }),
|
|
39
39
|
...(read ? { read: true } : {}),
|
|
40
40
|
});
|
|
41
41
|
return nodeId.length <= 2048 ? nodeId : 'record:invalid';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { gaps as admitGaps } from './validation.js';
|
|
2
2
|
import { nodeIdFor } from './recordIdentity.js';
|
|
3
3
|
import { prepareRecordScope } from './recordScope.js';
|
|
4
|
-
import { addressKey, addressOf,
|
|
4
|
+
import { addressKey, addressOf, recordList, recordOwn, projectLoaded, RecordRefusal } from './recordValidation.js';
|
|
5
5
|
/** Admit before caching so a cache hit cannot bypass payload limits or lose scope-owned coverage. */
|
|
6
6
|
function admitLoadedScope(result, request, limits, state) {
|
|
7
7
|
if (request.controller.signal.aborted || performance.now() >= request.deadline) {
|
|
@@ -10,8 +10,8 @@ function admitLoadedScope(result, request, limits, state) {
|
|
|
10
10
|
}
|
|
11
11
|
let prepared;
|
|
12
12
|
try {
|
|
13
|
-
prepared = prepareRecordScope(
|
|
14
|
-
const extra =
|
|
13
|
+
prepared = prepareRecordScope(recordOwn(result, 'scope'), request.address, limits.maxCommits, limits.maxFields);
|
|
14
|
+
const extra = recordList(recordOwn(result, 'gaps') ?? [], 32);
|
|
15
15
|
// Coverage belongs to this admitted object, so cached scopes retain the same gap ordering and ownership.
|
|
16
16
|
state.extraGaps.set(prepared, admitGaps(extra.map((code) => ({ code, evidence: [] }))).map((gap) => gap.code));
|
|
17
17
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { RecordLoadFailure, RecordScopeLoader, ResolvedRecordNode } from './recordTypes.js';
|
|
2
|
+
import type { GraphContext, NodeId } from './types.js';
|
|
3
|
+
/** Both dependency directions share the same admitted node and canonical inspection within a query. */
|
|
4
|
+
export declare function createRecordQuery(loader: RecordScopeLoader, maximumEdges: number): {
|
|
5
|
+
resolve(nodeId: NodeId, context: GraphContext): Promise<RecordLoadFailure | ResolvedRecordNode>;
|
|
6
|
+
inspect(node: ResolvedRecordNode, context: GraphContext): import("./recordTypes.js").RecordInspection;
|
|
7
|
+
};
|
|
8
|
+
/** Every port maps the same fixed loading failure while projecting only admitted nodes. */
|
|
9
|
+
export declare function whenResolved<T>(node: ResolvedRecordNode | RecordLoadFailure, onNode: (node: ResolvedRecordNode) => T, onFailure: (failure: RecordLoadFailure) => T): T;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { queryMemo } from './queryMemo.js';
|
|
2
|
+
import { resolveRecordNode } from './recordIdentity.js';
|
|
3
|
+
import { inspectRecordNode } from './recordInspection.js';
|
|
4
|
+
/** Both dependency directions share the same admitted node and canonical inspection within a query. */
|
|
5
|
+
export function createRecordQuery(loader, maximumEdges) {
|
|
6
|
+
const resolutionOwner = {}, inspectionOwner = {};
|
|
7
|
+
return {
|
|
8
|
+
resolve(nodeId, context) {
|
|
9
|
+
const resolve = () => resolveRecordNode(nodeId, context, loader), memo = queryMemo(context);
|
|
10
|
+
// A retained success must never bypass the loader's existing cancellation check.
|
|
11
|
+
return memo && !context.signal.aborted ? memo.remember(resolutionOwner, nodeId, resolve) : resolve();
|
|
12
|
+
},
|
|
13
|
+
inspect(node, context) {
|
|
14
|
+
const inspect = () => inspectRecordNode(node, maximumEdges, loader.extraGapCodes(node.scope));
|
|
15
|
+
return queryMemo(context)?.remember(inspectionOwner, node, inspect) ?? inspect();
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/** Every port maps the same fixed loading failure while projecting only admitted nodes. */
|
|
20
|
+
export function whenResolved(node, onNode, onFailure) {
|
|
21
|
+
return 'failure' in node ? onFailure(node) : onNode(node);
|
|
22
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { addressKey, addressOf, bundleOf,
|
|
1
|
+
import { addressKey, addressOf, bundleOf, recordList, mapOf, marksOf, recordName, names, recordOwn, RecordRefusal, spend, } from './recordValidation.js';
|
|
2
2
|
/** Refuse an invalid limit before loading a scope or allocating its preparation budget. */
|
|
3
3
|
function boundedOption(value, defaultValue, maximum) {
|
|
4
4
|
const configuredValue = value ?? defaultValue;
|
|
@@ -16,11 +16,11 @@ export function recordOptions(options) {
|
|
|
16
16
|
if (prototype !== Object.prototype && prototype !== null)
|
|
17
17
|
throw new TypeError();
|
|
18
18
|
return Object.freeze({
|
|
19
|
-
maxCommits: boundedOption(
|
|
20
|
-
maxFields: boundedOption(
|
|
21
|
-
maxEdges: boundedOption(
|
|
22
|
-
maxScopes: boundedOption(
|
|
23
|
-
loadTimeoutMs: boundedOption(
|
|
19
|
+
maxCommits: boundedOption(recordOwn(options, 'maxCommits'), 256, 4096),
|
|
20
|
+
maxFields: boundedOption(recordOwn(options, 'maxFields'), 20000, 1000000),
|
|
21
|
+
maxEdges: boundedOption(recordOwn(options, 'maxEdges'), 64, 256),
|
|
22
|
+
maxScopes: boundedOption(recordOwn(options, 'maxScopes'), 32, 256),
|
|
23
|
+
loadTimeoutMs: boundedOption(recordOwn(options, 'loadTimeoutMs'), 1000, 30000),
|
|
24
24
|
});
|
|
25
25
|
}
|
|
26
26
|
catch {
|
|
@@ -29,22 +29,22 @@ export function recordOptions(options) {
|
|
|
29
29
|
}
|
|
30
30
|
/** Spend one shared field budget before canonical readers see any untrusted scope data. */
|
|
31
31
|
export function prepareRecordScope(value, address, maximumCommits, maximumFields) {
|
|
32
|
-
if (addressKey(addressOf(
|
|
32
|
+
if (addressKey(addressOf(recordOwn(value, 'address'))) !== addressKey(address))
|
|
33
33
|
throw new RecordRefusal();
|
|
34
|
-
const rawLog =
|
|
35
|
-
const log = rawLog.map((_, commitIndex) => bundleOf(
|
|
36
|
-
const reads = mapOf(
|
|
34
|
+
const rawLog = recordList(recordOwn(value, 'commitLog'), maximumCommits), budget = { left: maximumFields };
|
|
35
|
+
const log = rawLog.map((_, commitIndex) => bundleOf(recordOwn(rawLog, String(commitIndex)), commitIndex, budget));
|
|
36
|
+
const reads = mapOf(recordOwn(value, 'reads'), maximumCommits, (value) => {
|
|
37
37
|
const readKeys = names(value, 128, true);
|
|
38
38
|
spend(budget, readKeys.length);
|
|
39
39
|
return readKeys;
|
|
40
40
|
}, budget);
|
|
41
|
-
const control = mapOf(
|
|
42
|
-
const marks = mapOf(
|
|
41
|
+
const control = mapOf(recordOwn(value, 'controlDeps'), maximumCommits, (value) => ({ deciderId: recordName(recordOwn(value, 'deciderId')) }), budget);
|
|
42
|
+
const marks = mapOf(recordOwn(value, 'attention'), maximumCommits, (value) => {
|
|
43
43
|
const attention = marksOf(value);
|
|
44
44
|
spend(budget, attention.length + attention.reduce((count, mark) => count + mark.evidence.length, 0));
|
|
45
45
|
return attention;
|
|
46
46
|
}, budget);
|
|
47
|
-
return { address, log, reads, control, marks, complete:
|
|
47
|
+
return { address, log, reads, control, marks, complete: recordOwn(value, 'complete') === true };
|
|
48
48
|
}
|
|
49
49
|
/** Preserve both stage reads and stronger per-write prefixes, including their original encounter order. */
|
|
50
50
|
export function readKeysForStage(scope, runtimeStageId) {
|
|
@@ -3,17 +3,17 @@ import type { LogAddress } from '../memory/eventPosition.js';
|
|
|
3
3
|
import type { CommitBundle } from '../memory/types.js';
|
|
4
4
|
import type { GapCode } from './types.js';
|
|
5
5
|
import { attention as marksOf } from './validation.js';
|
|
6
|
-
import { EvidenceRefusal } from '
|
|
6
|
+
import { EvidenceRefusal } from '../evidence/admission.js';
|
|
7
7
|
export { marksOf };
|
|
8
8
|
/** Loader refusals stay distinct from graph-port refusals so a failed host load keeps its original classification. */
|
|
9
9
|
export declare class RecordRefusal extends EvidenceRefusal {
|
|
10
10
|
readonly code: GapCode;
|
|
11
11
|
constructor(code?: GapCode);
|
|
12
12
|
}
|
|
13
|
-
export declare function
|
|
13
|
+
export declare function recordOwn(value: unknown, key: string): unknown;
|
|
14
14
|
export declare function names(value: unknown, maximum?: number, path?: boolean): string[];
|
|
15
|
-
export declare function
|
|
16
|
-
export declare function
|
|
15
|
+
export declare function recordName(value: unknown, path?: boolean): string;
|
|
16
|
+
export declare function recordList(value: unknown, maximum: number): readonly unknown[];
|
|
17
17
|
export declare function addressOf(value: unknown): LogAddress;
|
|
18
18
|
export declare function spend(budget: {
|
|
19
19
|
left: number;
|