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.
Files changed (2) hide show
  1. package/README.md +6 -70
  2. 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` (ESM) | Optional bounded attention, cause and impact queries; in-memory and lazily loaded stored-record adapters |
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` reads recorded dependencies; it never reruns business code, calls a model, or accesses storage by itself. An application supplies a graph or a stored-record host. `explain(graph, nodeId)` walks the selected node's recorded data and control dependencies, identifies the earliest marked ancestors in that dependency order, and walks forward to report impact. Independent candidates remain `alternatives`; timestamps and ID order never break a causal tie. A missing or cut backward dependency withholds `rootCause`; missing evidence in either direction returns `confidence: 'incomplete'` with fixed gap codes. Confidence describes evidence coverage, not proof of a provider's internal failure.
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
- **For precise per-write cause queries, enable `writeProvenance: 'reads-prefix'` when recording the flow.** Step-level data edges can add false ancestors; the record adapter marks that fallback `conservative`, so those queries return incomplete evidence and alternatives rather than a definite root. This setting cannot restore provenance in old records and does not guarantee a root: complete recorded read/control and attention coverage, one supported candidate and no backward gaps are still required. Other graph adapters can provide their own precise dependency evidence.
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
- **Module compatibility:** `/cause` has an `import` export condition only. CommonJS consumers must use native `import('foottrace/cause')`; `require('foottrace/cause')` is not exported, even on Node versions that support requiring ESM. Preserve native `import()` in the compiled CommonJS loader: compilers that rewrite it to `require()` need a dedicated import-preserving bridge. Existing root, `/write` and `/paths` CommonJS imports remain supported.
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
- Attention marks contain a fixed reason code and evidence references, never a raw error or state value. Every mark needs a proof reference except `not-collected`, which records absence. The application decides which business conditions deserve a mark. `error`, `unknown-effect`, `gave-up`, `timeout`, `retried`, `quality-drop`, `not-collected` and `outlier` are standard kinds; future bounded kind names remain readable. An absent mark does not prove a node was healthy. `largestDrop(scores)` and `mostAffected(scores)` return optional root strategies over host-supplied, already recorded numeric scores; equal best scores remain tied and missing scores become gaps. The default uses the earliest marked ancestor, with no scoring model. An optional `rankImpact(nodes, context)` may reorder the discovered descendants, but cannot add, omit or duplicate references; failed ranking preserves the unranked references and reports a gap.
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.0",
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/esm/cause.d.ts"
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 --exclude-entrypoints cause --profile strict && attw --pack --entrypoints cause --profile esm-only",
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",