foottrace 1.3.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 +6 -70
- package/package.json +7 -3
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
|
|
|
@@ -125,7 +125,7 @@ The parser reads from the right, so a stage ID may contain `@` or `.` and no cha
|
|
|
125
125
|
|
|
126
126
|
## Compatibility and development
|
|
127
127
|
|
|
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.
|
|
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.
|
|
129
129
|
|
|
130
130
|
```sh
|
|
131
131
|
npm ci
|
|
@@ -141,11 +141,11 @@ MIT license. Contact: sanjay@footprintjs.dev.
|
|
|
141
141
|
|
|
142
142
|
## Cause and impact — 1.2.0
|
|
143
143
|
|
|
144
|
-
`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.
|
|
145
145
|
|
|
146
|
-
|
|
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.
|
|
147
147
|
|
|
148
|
-
|
|
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.
|
|
149
149
|
|
|
150
150
|
```ts
|
|
151
151
|
import type { Attention } from 'foottrace';
|
|
@@ -165,68 +165,4 @@ const finding = await explain(graph, selectedNodeId, {
|
|
|
165
165
|
// An LLM can phrase these validated findings without inventing a cause.
|
|
166
166
|
```
|
|
167
167
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
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.
|
|
171
|
-
|
|
172
|
-
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.
|
|
173
|
-
|
|
174
|
-
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.
|
|
175
|
-
|
|
176
|
-
### Per-write provenance recipe
|
|
177
|
-
|
|
178
|
-
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.
|
|
179
|
-
|
|
180
|
-
```ts
|
|
181
|
-
import { EventLog, RecordFrame, SharedMemory } from 'foottrace/write';
|
|
182
|
-
const state = new SharedMemory(undefined, { a: 1, b: 2 });
|
|
183
|
-
const log = new EventLog(state.getState());
|
|
184
|
-
const frame = new RecordFrame(state, log);
|
|
185
|
-
frame.useEncoding({ commitValues: 'full', writeProvenance: 'reads-prefix' });
|
|
186
|
-
frame.noteRead([], 'a');
|
|
187
|
-
frame.write(frame.at([], 'x'), 1, 'set');
|
|
188
|
-
frame.noteRead([], 'b');
|
|
189
|
-
frame.write(frame.at([], 'y'), 3, 'set');
|
|
190
|
-
frame.commit(() => ({ stage: 'Derive', stageId: 'derive', runtimeStageId: 'derive#0' }));
|
|
191
|
-
frame.release();
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
A stored host can expose one authorized log at a time:
|
|
195
|
-
|
|
196
|
-
```ts
|
|
197
|
-
import { createStoredRecordGraph, explain, type StoredRecordHost } from 'foottrace/cause';
|
|
198
|
-
|
|
199
|
-
// loadLog(address, { signal }) returns native entries for exactly that address,
|
|
200
|
-
// plus recorded read/control/attention maps when available. It checks access.
|
|
201
|
-
declare const host: StoredRecordHost;
|
|
202
|
-
const graph = createStoredRecordGraph(host, {
|
|
203
|
-
maxScopes: 8, maxCommits: 256, maxFields: 20_000, maxEdges: 64, loadTimeoutMs: 500,
|
|
204
|
-
});
|
|
205
|
-
const address = { logRunId: 'run-1', drillPath: [] };
|
|
206
|
-
const selected = graph.nodeId(address, { runtimeStageId: 'derive#0', commitIdx: 0, key: 'y' });
|
|
207
|
-
const finding = await explain(graph, selected, { timeoutMs: 750, maxNodes: 50 });
|
|
208
|
-
// Only the requested scope is loaded; a different subflow is opened on demand.
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
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.
|
|
212
|
-
|
|
213
|
-
### Established algorithms
|
|
214
|
-
|
|
215
|
-
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.
|
|
216
|
-
|
|
217
|
-
| Query | Established name |
|
|
218
|
-
| --- | --- |
|
|
219
|
-
| Recorded ancestors influencing a value or node | **Backward dynamic program slicing** (Weiser, 1981; Korel and Laski, 1988; Agrawal and Horgan, 1990) |
|
|
220
|
-
| Recorded data plus decision/control edges | **Program dependence graph** (Ferrante, Ottenstein and Warren, 1987) |
|
|
221
|
-
| Recorded downstream effects | **Forward slicing**, or change-impact analysis |
|
|
222
|
-
| Why a value exists and where it came from | **Why- and where-provenance** (Buneman, Khanna and Tan, 2001) |
|
|
223
|
-
| Loading only queried graph nodes/scopes | **Demand-driven, or lazy, graph traversal** |
|
|
224
|
-
| Default candidate selection | **Root-cause localisation on a dependency DAG**, using the earliest anomalous ancestor |
|
|
225
|
-
|
|
226
|
-
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. The cause reader shipped in 1.2.0; later engine stamping, viewer integration and agent tools are separate changes.
|
|
227
|
-
|
|
228
|
-
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.
|
|
229
|
-
|
|
230
|
-
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.
|
|
231
|
-
|
|
232
|
-
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "foottrace",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"type": "commonjs",
|
|
5
5
|
"description": "Execution records, replay, time travel and honest attribution \u2014 independent of the engine that produced them",
|
|
6
6
|
"license": "MIT",
|
|
@@ -59,6 +59,10 @@
|
|
|
59
59
|
"import": {
|
|
60
60
|
"types": "./dist/esm/cause.d.ts",
|
|
61
61
|
"default": "./dist/esm/cause.js"
|
|
62
|
+
},
|
|
63
|
+
"require": {
|
|
64
|
+
"types": "./dist/types/cause.d.ts",
|
|
65
|
+
"default": "./dist/cause.js"
|
|
62
66
|
}
|
|
63
67
|
}
|
|
64
68
|
},
|
|
@@ -71,7 +75,7 @@
|
|
|
71
75
|
"dist/types/paths.d.ts"
|
|
72
76
|
],
|
|
73
77
|
"cause": [
|
|
74
|
-
"dist/
|
|
78
|
+
"dist/types/cause.d.ts"
|
|
75
79
|
]
|
|
76
80
|
}
|
|
77
81
|
},
|
|
@@ -81,7 +85,7 @@
|
|
|
81
85
|
"test:coverage": "vitest run --coverage",
|
|
82
86
|
"typecheck": "tsc -p tsconfig.test.json",
|
|
83
87
|
"check:layering": "node scripts/check-layering.mjs",
|
|
84
|
-
"check:package": "publint && attw --pack --
|
|
88
|
+
"check:package": "publint && attw --pack --profile strict && tsc -p test/consumer/cause/tsconfig.json",
|
|
85
89
|
"lint": "eslint 'src/**/*.ts'",
|
|
86
90
|
"format": "prettier --check 'src/**/*.ts' 'test/**/*.ts'",
|
|
87
91
|
"test:examples": "tsc -p examples/tsconfig.json",
|