pi-background-tasks 2.5.0 → 2.6.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/BACKGROUND-TASKS-INSTRUCTIONS.md +1 -1
- package/README.md +70 -25
- package/TESTING.md +42 -2
- package/TEST_PLAN.md +7 -4
- package/dist/extensions/anthropic-attribution-child.js +4 -0
- package/dist/extensions/anthropic-attribution-child.js.map +1 -0
- package/dist/extensions/anthropic-attribution.js +107 -0
- package/dist/extensions/anthropic-attribution.js.map +1 -0
- package/dist/extensions/background-tasks.js +2 -0
- package/dist/extensions/background-tasks.js.map +1 -0
- package/dist/extensions/delegate-child.js +2 -0
- package/dist/extensions/delegate-child.js.map +1 -0
- package/dist/extensions/fusion-child.js +2 -0
- package/dist/extensions/fusion-child.js.map +1 -0
- package/dist/package.json +5 -0
- package/dist/src/core/anthropic-attribution-path.js +24 -0
- package/dist/src/core/anthropic-attribution-path.js.map +1 -0
- package/dist/src/core/anthropic-attribution.js +2247 -0
- package/dist/src/core/anthropic-attribution.js.map +1 -0
- package/dist/src/core/attested-pi-contract.js +5 -0
- package/dist/src/core/attested-pi-contract.js.map +1 -0
- package/dist/src/core/attested-pi-run.js +749 -0
- package/dist/src/core/attested-pi-run.js.map +1 -0
- package/dist/src/core/canonical-json.js +19 -0
- package/dist/src/core/canonical-json.js.map +1 -0
- package/dist/src/core/common.js +787 -0
- package/dist/src/core/common.js.map +1 -0
- package/dist/src/core/config.js +78 -0
- package/dist/src/core/config.js.map +1 -0
- package/dist/src/core/context/parent-snapshot.js +75 -0
- package/dist/src/core/context/parent-snapshot.js.map +1 -0
- package/dist/src/core/context/token-budget.js +631 -0
- package/dist/src/core/context/token-budget.js.map +1 -0
- package/dist/src/core/context/visible-conversation-v2.js +390 -0
- package/dist/src/core/context/visible-conversation-v2.js.map +1 -0
- package/dist/src/core/delegate/artifacts.js +357 -0
- package/dist/src/core/delegate/artifacts.js.map +1 -0
- package/dist/src/core/delegate/budget.js +266 -0
- package/dist/src/core/delegate/budget.js.map +1 -0
- package/dist/src/core/delegate/facade-contract.js +7 -0
- package/dist/src/core/delegate/facade-contract.js.map +1 -0
- package/dist/src/core/delegate/hook-contract-evidence.json +18 -0
- package/dist/src/core/delegate/hook-contract.js +124 -0
- package/dist/src/core/delegate/hook-contract.js.map +1 -0
- package/dist/src/core/delegate/launch.js +366 -0
- package/dist/src/core/delegate/launch.js.map +1 -0
- package/dist/src/core/delegate/result-package.js +293 -0
- package/dist/src/core/delegate/result-package.js.map +1 -0
- package/dist/src/core/delegate/runner.js +398 -0
- package/dist/src/core/delegate/runner.js.map +1 -0
- package/dist/src/core/delegate/seed.js +334 -0
- package/dist/src/core/delegate/seed.js.map +1 -0
- package/dist/src/core/delegate/types.js +103 -0
- package/dist/src/core/delegate/types.js.map +1 -0
- package/dist/src/core/durable-fs.js +372 -0
- package/dist/src/core/durable-fs.js.map +1 -0
- package/dist/src/core/extension-api.js +382 -0
- package/dist/src/core/extension-api.js.map +1 -0
- package/dist/src/core/fusion/artifacts.js +654 -0
- package/dist/src/core/fusion/artifacts.js.map +1 -0
- package/dist/src/core/fusion/budget.js +949 -0
- package/dist/src/core/fusion/budget.js.map +1 -0
- package/dist/src/core/fusion/child-protocol.js +176 -0
- package/dist/src/core/fusion/child-protocol.js.map +1 -0
- package/dist/src/core/fusion/claude-cache.js +156 -0
- package/dist/src/core/fusion/claude-cache.js.map +1 -0
- package/dist/src/core/fusion/clean-context.js +64 -0
- package/dist/src/core/fusion/clean-context.js.map +1 -0
- package/dist/src/core/fusion/config.js +349 -0
- package/dist/src/core/fusion/config.js.map +1 -0
- package/dist/src/core/fusion/context.js +184 -0
- package/dist/src/core/fusion/context.js.map +1 -0
- package/dist/src/core/fusion/evaluation.js +696 -0
- package/dist/src/core/fusion/evaluation.js.map +1 -0
- package/dist/src/core/fusion/facade-contract.js +3 -0
- package/dist/src/core/fusion/facade-contract.js.map +1 -0
- package/dist/src/core/fusion/orchestrator.js +867 -0
- package/dist/src/core/fusion/orchestrator.js.map +1 -0
- package/dist/src/core/fusion/output-contract.js +27 -0
- package/dist/src/core/fusion/output-contract.js.map +1 -0
- package/dist/src/core/fusion/pi-child.js +1741 -0
- package/dist/src/core/fusion/pi-child.js.map +1 -0
- package/dist/src/core/fusion/prompts.js +260 -0
- package/dist/src/core/fusion/prompts.js.map +1 -0
- package/dist/src/core/fusion/result-package.js +857 -0
- package/dist/src/core/fusion/result-package.js.map +1 -0
- package/dist/src/core/fusion/source-policy.js +259 -0
- package/dist/src/core/fusion/source-policy.js.map +1 -0
- package/dist/src/core/fusion/types.js +207 -0
- package/dist/src/core/fusion/types.js.map +1 -0
- package/dist/src/core/fusion/web-fetch.js +777 -0
- package/dist/src/core/fusion/web-fetch.js.map +1 -0
- package/dist/src/core/fusion/workflows.js +126 -0
- package/dist/src/core/fusion/workflows.js.map +1 -0
- package/dist/src/core/lazy-module.js +181 -0
- package/dist/src/core/lazy-module.js.map +1 -0
- package/dist/src/core/pi-launch.js +431 -0
- package/dist/src/core/pi-launch.js.map +1 -0
- package/dist/src/core/registry.js +3331 -0
- package/dist/src/core/registry.js.map +1 -0
- package/dist/src/core/reload-shell-owner.js +1396 -0
- package/dist/src/core/reload-shell-owner.js.map +1 -0
- package/dist/src/core/shell-policy.js +80 -0
- package/dist/src/core/shell-policy.js.map +1 -0
- package/dist/src/core/task-durable.js +51 -0
- package/dist/src/core/task-durable.js.map +1 -0
- package/dist/src/core/update-check.js +92 -0
- package/dist/src/core/update-check.js.map +1 -0
- package/dist/src/core/windows-taskkill.js +185 -0
- package/dist/src/core/windows-taskkill.js.map +1 -0
- package/dist/src/delegate-child-extension.js +776 -0
- package/dist/src/delegate-child-extension.js.map +1 -0
- package/dist/src/delegate-extension.js +746 -0
- package/dist/src/delegate-extension.js.map +1 -0
- package/dist/src/extension.js +953 -0
- package/dist/src/extension.js.map +1 -0
- package/dist/src/fusion-child-extension.js +760 -0
- package/dist/src/fusion-child-extension.js.map +1 -0
- package/dist/src/fusion-extension.js +1030 -0
- package/dist/src/fusion-extension.js.map +1 -0
- package/dist/src/ui/background-tasks-manager.js +689 -0
- package/dist/src/ui/background-tasks-manager.js.map +1 -0
- package/dist/src/ui/fusion-model-selector.js +277 -0
- package/dist/src/ui/fusion-model-selector.js.map +1 -0
- package/docs/INDEX.md +35 -33
- package/docs/api/eventbus-v1.md +21 -5
- package/docs/choose-a-workflow.md +2 -0
- package/docs/commands/bg-clear.md +3 -3
- package/docs/commands/bg-update.md +3 -3
- package/docs/commands/bg.md +18 -6
- package/docs/commands/claude-cache.md +4 -4
- package/docs/commands/fusion-models.md +49 -4
- package/docs/commands/fusion.md +3 -3
- package/docs/commands/jobs.md +3 -3
- package/docs/commands/kill.md +3 -3
- package/docs/commands/logs.md +3 -3
- package/docs/commands/task-manager.md +5 -5
- package/docs/concepts/completion-delivery.md +6 -2
- package/docs/getting-started.md +7 -1
- package/docs/manifest.json +228 -42
- package/docs/operations/configuration.md +58 -5
- package/docs/operations/releasing.md +4 -2
- package/docs/operations/testing.md +22 -0
- package/docs/operations/troubleshooting.md +6 -2
- package/docs/read-before-edit.md +44 -31
- package/docs/reference/runtime-contracts.md +87 -51
- package/docs/reference/shortcuts-and-dock.md +28 -14
- package/docs/subsystems/anthropic-attribution.md +23 -11
- package/docs/subsystems/attested-pi-runs.md +12 -7
- package/docs/subsystems/background-task-runtime.md +53 -11
- package/docs/subsystems/child-launch-durability-and-safety.md +28 -9
- package/docs/subsystems/delegation.md +16 -3
- package/docs/subsystems/docs-freshness-gate.md +14 -7
- package/docs/subsystems/fusion.md +36 -11
- package/docs/subsystems/host-ui-and-telemetry.md +22 -9
- package/docs/tools/bg_delegate.md +3 -1
- package/docs/tools/bg_kill.md +3 -1
- package/docs/tools/bg_logs.md +3 -1
- package/docs/tools/bg_result.md +5 -3
- package/docs/tools/bg_run.md +31 -5
- package/docs/tools/bg_run_pi_attested.md +3 -1
- package/docs/tools/bg_status.md +3 -1
- package/docs/tools/fusion_investigate.md +3 -1
- package/docs/tools/fusion_reason.md +3 -1
- package/docs/tools/fusion_research.md +3 -1
- package/docs/tools/fusion_validate.md +3 -1
- package/extensions/anthropic-attribution-child.ts +3 -0
- package/extensions/anthropic-attribution.ts +166 -1
- package/package.json +12 -10
- package/src/core/anthropic-attribution-path.ts +5 -4
- package/src/core/anthropic-attribution.ts +293 -113
- package/src/core/attested-pi-contract.ts +4 -0
- package/src/core/attested-pi-run.ts +468 -81
- package/src/core/canonical-json.ts +21 -0
- package/src/core/common.ts +450 -40
- package/src/core/config.ts +121 -0
- package/src/core/context/visible-conversation-v2.ts +3 -6
- package/src/core/delegate/artifacts.ts +13 -9
- package/src/core/delegate/budget.ts +13 -6
- package/src/core/delegate/facade-contract.ts +6 -0
- package/src/core/delegate/result-package.ts +32 -14
- package/src/core/delegate/runner.ts +129 -58
- package/src/core/delegate/seed.ts +5 -7
- package/src/core/durable-fs.ts +178 -30
- package/src/core/extension-api.ts +36 -8
- package/src/core/fusion/artifacts.ts +17 -16
- package/src/core/fusion/clean-context.ts +8 -5
- package/src/core/fusion/config.ts +2 -1
- package/src/core/fusion/context.ts +2 -6
- package/src/core/fusion/facade-contract.ts +2 -0
- package/src/core/fusion/orchestrator.ts +3 -3
- package/src/core/fusion/prompts.ts +1 -1
- package/src/core/fusion/result-package.ts +259 -72
- package/src/core/fusion/source-policy.ts +43 -17
- package/src/core/lazy-module.ts +215 -0
- package/src/core/pi-launch.ts +390 -65
- package/src/core/registry.ts +2010 -381
- package/src/core/reload-shell-owner.ts +1662 -0
- package/src/core/shell-policy.ts +134 -0
- package/src/core/task-durable.ts +67 -0
- package/src/delegate-extension.ts +404 -144
- package/src/extension.ts +387 -114
- package/src/fusion-extension.ts +202 -96
|
@@ -4,26 +4,32 @@ audience: user
|
|
|
4
4
|
mode: mixed
|
|
5
5
|
review_policy: contract
|
|
6
6
|
stability: stable
|
|
7
|
-
covers_surfaces: ['shortcut:ctrl+alt+c', 'shortcut:shift+down']
|
|
7
|
+
covers_surfaces: ['shortcut:ctrl+alt+b', 'shortcut:ctrl+alt+c', 'shortcut:shift+down']
|
|
8
8
|
covers_sources: []
|
|
9
9
|
---
|
|
10
10
|
# Shortcuts and dock reference
|
|
11
11
|
|
|
12
12
|
<!-- pi-docs:begin name="shortcut-contracts" generator="scripts/docs/generate.mjs" -->
|
|
13
|
-
| Shortcut | Description | Provenance |
|
|
14
|
-
| --- | --- | --- |
|
|
15
|
-
| `ctrl+alt+
|
|
16
|
-
| `
|
|
13
|
+
| Shortcut | Availability | Default | Description | Provenance |
|
|
14
|
+
| --- | --- | --- | --- | --- |
|
|
15
|
+
| `ctrl+alt+b` | `dock:ctrl+alt+b` | no | Open focused background task footer dock | `src/extension.ts:834` |
|
|
16
|
+
| `ctrl+alt+c` | `always` | yes | Clear finished background task footer notices (terminal-dependent fallback for /bg-clear) | `src/extension.ts:842` |
|
|
17
|
+
| `shift+down` | `dock:shift+down` | yes | Open focused background task footer dock | `src/extension.ts:825` |
|
|
17
18
|
<!-- pi-docs:end name="shortcut-contracts" -->
|
|
18
19
|
|
|
19
20
|
## Registered shortcuts
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|---|---|
|
|
23
|
-
| `Shift+Down` | Open the focused background task footer dock / task manager. |
|
|
24
|
-
| `Ctrl+Alt+C` | Clear finished background task footer notices; this is an optional terminal-dependent fallback for [`/bg-clear`](../commands/bg-clear.md). |
|
|
22
|
+
`PI_BG_DOCK_SHORTCUT` selects exactly one dock binding:
|
|
25
23
|
|
|
26
|
-
|
|
24
|
+
| Value | Registered dock key | Footer hint | Default |
|
|
25
|
+
|---|---|---|---|
|
|
26
|
+
| `shift+down` | `Shift+Down` | `Shift↓` | yes |
|
|
27
|
+
| `ctrl+alt+b` | `Ctrl+Alt+B` | `CtrlAltB` | no |
|
|
28
|
+
| `off` | none | `/tasks` | no |
|
|
29
|
+
|
|
30
|
+
Only the configured literal key is registered, so selecting the alternate key or `off` avoids a Shift+Down conflict with another extension rather than merely hiding a label. Invalid, blank, differently cased, or whitespace-bearing values fail extension load with `pi_bg_config_invalid`; they do not fall back to Shift+Down. The setting is re-read on `/reload`.
|
|
31
|
+
|
|
32
|
+
`Ctrl+Alt+C` is separate and remains registered in all three modes. It clears finished background task footer notices as an optional terminal-dependent fallback for [`/bg-clear`](../commands/bg-clear.md). If a terminal does not deliver it, use `/bg-clear`, the canonical command path.
|
|
27
33
|
|
|
28
34
|
## Footer states
|
|
29
35
|
|
|
@@ -38,6 +44,8 @@ Examples:
|
|
|
38
44
|
|
|
39
45
|
```text
|
|
40
46
|
bg 1 running · Shift↓
|
|
47
|
+
bg 1 running · CtrlAltB
|
|
48
|
+
bg 1 running · /tasks
|
|
41
49
|
bg 1 done · Shift↓ · /bg-clear
|
|
42
50
|
bg 1 running · 1 failed · 1 stopped · 1 done · Shift↓ · /bg-clear
|
|
43
51
|
bg 1 running · Shift↓ · ⬆ v999.0.0 /bg-update
|
|
@@ -48,11 +56,17 @@ The `/bg-clear` hint is hidden while the dock is open, where the entry hint beco
|
|
|
48
56
|
|
|
49
57
|
## Dock entry points
|
|
50
58
|
|
|
51
|
-
- `Shift+Down`
|
|
52
|
-
- [`/tasks`](../commands/task-manager.md)
|
|
53
|
-
- [`/bg-tasks`](../commands/task-manager.md)
|
|
59
|
+
- the configured `Shift+Down` or `Ctrl+Alt+B` key, unless set to `off`;
|
|
60
|
+
- [`/tasks`](../commands/task-manager.md);
|
|
61
|
+
- [`/bg-tasks`](../commands/task-manager.md).
|
|
62
|
+
|
|
63
|
+
All enabled entry points open the same task manager when an interactive UI is available. The commands remain available with the shortcut off and are the conflict-free fallback.
|
|
64
|
+
|
|
65
|
+
## Rerun and reload survival
|
|
66
|
+
|
|
67
|
+
`R` reruns only ordinary shell tasks. If the selected task used `surviveReload:true`, rerun preserves that opt-in but deliberately creates a new execution with a new task id and launch nonce. It resolves the current activation's shell policy and captures current timeout/output-cap configuration; it never reuses or restarts the selected process. Delegate and Fusion rows remain typed-workflow refusals.
|
|
54
68
|
|
|
55
|
-
|
|
69
|
+
A currently running opted ordinary task that crosses a supported real reload remains the same row/id/PID/output path in the fresh dock. The dock does not scan metadata or adopt PIDs.
|
|
56
70
|
|
|
57
71
|
## Dock output detail
|
|
58
72
|
|
|
@@ -5,15 +5,23 @@ mode: authored
|
|
|
5
5
|
review_policy: behavioral
|
|
6
6
|
stability: evolving
|
|
7
7
|
covers_surfaces: []
|
|
8
|
-
covers_sources: [extensions/anthropic-attribution.ts, src/core/anthropic-attribution-path.ts, src/core/anthropic-attribution.ts]
|
|
8
|
+
covers_sources: [extensions/anthropic-attribution-child.ts, extensions/anthropic-attribution.ts, src/core/anthropic-attribution-path.ts, src/core/anthropic-attribution.ts]
|
|
9
9
|
---
|
|
10
10
|
# Anthropic attribution subsystem
|
|
11
11
|
|
|
12
|
-
This subsystem owns the package-wide Anthropic subscription attribution provider, exact-match system-prompt sanitization, cache-retention command, and the
|
|
12
|
+
This subsystem owns the package-wide Anthropic subscription attribution provider, exact-match system-prompt sanitization, cache-retention command, the feature-aware ambient parent entrypoint, and the distinct always-on extension path shared by isolated child Pi processes.
|
|
13
13
|
|
|
14
14
|
## Global package behavior
|
|
15
15
|
|
|
16
|
-
`package.json.pi.extensions` loads `extensions/anthropic-attribution.
|
|
16
|
+
`package.json.pi.extensions` loads the compiled `dist/extensions/anthropic-attribution.js` entrypoint before the compiled background-task entrypoint; `extensions/anthropic-attribution.ts` remains its authoritative source. The light entrypoint parses the complete shared configuration before dynamically loading the attribution transport. With the default `attribution` capability, it activates the accepted implementation from `session_start`, after Pi has bound its public provider runtime; without that token the transport module is not imported and no parent provider, attribution lifecycle hooks, duplicate-owner responder, or `/claude-cache` command is registered. Invalid feature or dock configuration fails before ambient activation.
|
|
17
|
+
|
|
18
|
+
Before activation, the wrapper snapshots the effective provider plus the public legacy/native registration through `ctx.modelRegistry`. The accepted factory's provider call is then immediate rather than queued. A thrown application failure occurs before its command, hooks, or claim responder are published. A successful installation is owned only after a new current legacy-config object and changed effective provider are observed; that exact config object is the instance token. On shutdown the wrapper acts only while that token is still current. A later legacy or native owner is left untouched. Otherwise it restores the prior native registration directly, clears the package layer with the captured effective provider before reapplying a prior legacy config, or—when there was no prior dynamic registration—uses public unregister only after the exact token/later-owner checks to restore dynamic-registration absence and the captured built-in effective object. It never blindly deletes another owner's provider and never reads private host state. A subsequent real reload with attribution disabled therefore restores the host's prior public registration state. When enabled, the transport remains provider-gated: non-Anthropic sessions and payloads are unchanged.
|
|
19
|
+
|
|
20
|
+
## Initialized-host SDK contract and blocker
|
|
21
|
+
|
|
22
|
+
Normal Pi TUI, RPC, print, and JSON modes supply counted bindings and satisfy the initialized-host contract. SDK embedders must call `bindExtensions()` with at least one counted UI/command/shutdown/error binding; after reload, that binding causes Pi to emit `session_start`. Empty or mode-only bindings require an explicit `bindExtensions()` call after every reload.
|
|
23
|
+
|
|
24
|
+
**BLOCKED_SCOPE / SDK compatibility:** bare `createAgentSession()` never emits the activation event, and empty or mode-only binding state does not make `reload()` emit it. On those paths the selected ambient provider, `/claude-cache`, hooks, persisted cache initialization, and duplicate-owner responder remain absent until an explicit bind. There is no safe package-only repair through the current public API: factory-time provider registration is queued and gives no success/owner token. Closure still requires a guaranteed post-core-bind/reload callback or owner-token registration API; excluding these SDK hosts is not approved. Generated availability describes the initialized-host contract and is not a pre-bind availability guarantee.
|
|
17
25
|
|
|
18
26
|
For Anthropic sessions it registers the package-owned `anthropic` provider transport. Mandatory attribution is owned inside that transport from each request's Pi-supplied `options.sessionId`; `before_provider_request` remains optional middleware and is never an identity initializer. The transport applies the Claude Code subscription request contract:
|
|
19
27
|
|
|
@@ -26,15 +34,19 @@ For Anthropic sessions it registers the package-owned `anthropic` provider trans
|
|
|
26
34
|
- provider-authoritative usage, cache diagnostics, and one-hour cache-write accounting when reported;
|
|
27
35
|
- strict SSE completion: matching event names, one `message_start`, closed content blocks, a recognized terminal stop reason, and one `message_stop` are required before success or lineage persistence.
|
|
28
36
|
|
|
29
|
-
The extension reads `userID` and `oauthAccount.accountUuid`
|
|
37
|
+
The extension reads `userID` and `oauthAccount.accountUuid` without writing account configuration. The loader precedence is: an explicit `loadClaudeAttributionAccount(path)` argument (the programmatic/test seam), then `PI_ANTHROPIC_ACCOUNT_CONFIG_PATH`, then `~/.claude.json`. An explicit selection must be a non-empty absolute file path. The selected file must be readable JSON with both non-empty fields; failures identify the selected path and field without fabricating account data. The package does not infer a Claude config-directory convention. Package-owned children inherit the path variable unchanged without copying account contents into their environment.
|
|
38
|
+
|
|
39
|
+
The registered provider can be reached with a non-`anthropic` model that uses the `anthropic-messages` API. That path passes the host-owned model, context, and options by identity to the matching host SDK's supported `anthropicMessagesApi().streamSimple` implementation and returns its event stream directly. The package neither validates against a frozen transcript shape nor reconstructs events/results: legacy top-level `Context` and normalized system-message `TranscriptContext` contracts remain owned by the host version, including shared live partial identity, optional fields, tool metadata, usage, callbacks, and terminal error settlement. It does not receive account, identity, cache, endpoint, or header rewriting and does not dispatch through the registered API adapter again. Missing/malformed account data on the target route, unsupported model policy, malformed payload/cache controls, and non-OAuth target transport fail loudly.
|
|
30
40
|
|
|
31
41
|
## Cross-provider history and cache lineage
|
|
32
42
|
|
|
33
43
|
Assistant messages carry their producing `provider`, `api`, and `model`. The transport never parses an opaque reasoning signature. Foreign visible thinking is projected deterministically as text; foreign opaque, redacted, and signature-only blocks are omitted. Claude thinking is replayed only when a successful direct-Anthropic response carries a matching `anthropic-cache-lineage` diagnostic binding response ID, source tuple, assistant-content hash, system/tools attribution profile, effective cache retention, request-message count, and request-prefix hash. Empty, redacted, and valid non-BMP Unicode Claude blocks are preserved byte-for-byte. Fable 5.1 accepts lineage-proven earlier Claude blocks; reverse replay is denied.
|
|
34
44
|
|
|
35
|
-
Every target model has an independent append-only lane. Before transport, the adapter proves the prior successful wire history remains an exact prefix and that model, sanitized system, canonical tools, thinking/effort, beta profile, and effective retention are unchanged.
|
|
45
|
+
Every target model has an independent append-only lane. Before a receipt can select a lane, authorize `previous_message_id`, or authorize signed/redacted thinking replay, it must bind completely to its containing assistant: exact Anthropic provider/API/model, a supported successful terminal state, equal non-empty response IDs, and the exact assistant-content hash. Blank required receipt IDs are malformed. Before transport, the adapter also proves the prior successful wire history remains an exact prefix and that model, sanitized system, canonical tools, thinking/effort, beta profile, and effective retention are unchanged. If resume/reload reconstruction, edited history, or a changed profile breaks that proof, the adapter silently starts a deterministic non-inheriting signature epoch and clears `previous_message_id` instead of permanently blocking the session. It re-projects before transport: prior-epoch visible thinking becomes text, while stale signed/redacted blocks are omitted. A successful response persists the reset epoch and request fingerprint, so the next unchanged turn chains normally. Failed HTTP/SSE attempts, incomplete streams, middleware tampering, and concurrent continuations do not anchor an epoch. Returning to an old profile/history starts another reset rather than resurrecting old signatures. A latest relevant assistant with a missing, malformed, or mismatched lineage receipt is also a non-inheriting boundary, not permission to search backward and revive an older epoch.
|
|
46
|
+
|
|
47
|
+
An intentional TTL change starts a cryptographically named signature epoch and suppresses prior-epoch signed thinking permanently, including after a later short→long return. A canonical leading Pi compaction summary likewise opens one hash-bound signature epoch only; later unrelated drift starts another epoch even while that marker remains. Tool IDs, schemas, arguments, user messages, and text-only tool results have deterministic block-shaped serialization, so advancing the final cache marker does not rewrite prior content. Optional payload middleware runs exactly once after transport-owned attribution and cannot change the protected model/stream route, account/device/session metadata, billing identity, cache-control placement/value topology, four-breakpoint limit, or already-authorized message/static/profile/retention lineage.
|
|
36
48
|
|
|
37
|
-
Fable 5.1 always sends `thinking-binding-controls-2026-08-01` with prefix mismatch set to `error`, plus `cache-diagnosis-2026-04-07`. The previous successful response ID is chained within the same model lane; provider diagnostics and `input_transformations` are persisted outside model context. There is no automatic signature
|
|
49
|
+
Fable 5.1 always sends `thinking-binding-controls-2026-08-01` with prefix mismatch set to `error`, plus `cache-diagnosis-2026-04-07`. The previous successful response ID is chained within the same model lane; provider diagnostics and `input_transformations` are persisted outside model context. There is no automatic retry after a provider signature rejection. Local recovery uses the provenance-aware projection, preserves visible reasoning as text, and records the non-inheriting epoch only after one complete, strictly valid transport response.
|
|
38
50
|
|
|
39
51
|
## Sanitization
|
|
40
52
|
|
|
@@ -44,21 +56,21 @@ Only complete matching lines are removed. Other system text, non-text blocks, cu
|
|
|
44
56
|
|
|
45
57
|
## Duplicate-owner protocol
|
|
46
58
|
|
|
47
|
-
A package extension and an independent project/user copy can otherwise register duplicate provider hooks and `/claude-cache` commands.
|
|
59
|
+
A package extension and an independent project/user copy can otherwise register duplicate provider hooks and `/claude-cache` commands. During ordered `session_start` activation, the factory therefore probes `pi-anthropic-attribution:claim:v1` on Pi's shared EventBus before registration. The first successfully installed copy adds one responder; later compatible copies become inert.
|
|
48
60
|
|
|
49
|
-
Ownership is published only after all hooks and the command register.
|
|
61
|
+
Ownership is published only after the immediate provider application, all hooks, and the command register. EventBus listener invocation is synchronous at the probe boundary, so a failed first installation cannot strand a false claim and a later copy may still activate. The responder lives for the shared EventBus runtime, matching the extension registrations it protects.
|
|
50
62
|
|
|
51
63
|
## Isolated package children
|
|
52
64
|
|
|
53
|
-
Ambient discovery
|
|
65
|
+
Ambient discovery and the parent capability flag are both insufficient for child paths that use `--no-extensions`. `resolveAnthropicAttributionExtensionPath()` resolves the distinct `extensions/anthropic-attribution-child.ts` entrypoint and is the single package path seam used by:
|
|
54
66
|
|
|
55
67
|
- Fusion Anthropic children, before the Fusion runtime governor;
|
|
56
68
|
- Anthropic delegate children, before the delegate guard;
|
|
57
69
|
- Anthropic attested Pi children.
|
|
58
70
|
|
|
59
|
-
Non-Anthropic child argv does not resolve or add
|
|
71
|
+
The child entrypoint directly invokes the accepted implementation and deliberately does not consult `PI_BG_FEATURES`. Non-Anthropic child argv does not resolve or add it. Missing package extension bytes fail before child creation; no route substitution or sanitizer fallback is attempted. Delegate and Fusion keep attribution before their guard/governor, and attested Anthropic argv adds the same entrypoint before the prompt.
|
|
60
72
|
|
|
61
|
-
Arbitrary shell commands started through `bg_run` are not rewritten. An Anthropic child `pi` launched this way
|
|
73
|
+
Arbitrary shell commands started through `bg_run` are not rewritten. An Anthropic child `pi` launched this way may keep normal extension discovery enabled when ambient attribution is enabled. If the command deliberately uses `--no-extensions`, it must explicitly load this package's always-on `extensions/anthropic-attribution-child.ts` with `-e`/`--extension`; otherwise attribution and sanitization are bypassed and the launch is unsupported. The package does not parse or override arbitrary shell authority.
|
|
62
74
|
|
|
63
75
|
## Cache retention
|
|
64
76
|
|
|
@@ -5,17 +5,17 @@ mode: authored
|
|
|
5
5
|
review_policy: behavioral
|
|
6
6
|
stability: evolving
|
|
7
7
|
covers_surfaces: []
|
|
8
|
-
covers_sources: [src/core/attested-pi-run.ts]
|
|
8
|
+
covers_sources: [src/core/attested-pi-contract.ts, src/core/attested-pi-run.ts]
|
|
9
9
|
---
|
|
10
10
|
# Attested Pi runs subsystem
|
|
11
11
|
|
|
12
|
-
This document is the primary behavioral owner for `src/core/attested-pi-run.ts`.
|
|
12
|
+
This document is the primary behavioral owner for `src/core/attested-pi-contract.ts` and `src/core/attested-pi-run.ts`.
|
|
13
13
|
|
|
14
14
|
It does **not** claim ownership of shared registry, Pi-launch, common task, or durable-fs modules. Those modules spawn the prepared request, store task metadata, publish terminal state, and provide atomic write primitives used by this subsystem.
|
|
15
15
|
|
|
16
16
|
## Purpose
|
|
17
17
|
|
|
18
|
-
Attested Pi runs are opt-in local-evidence tasks for a structured child Pi invocation. They are intended for cases where an operator wants local hashes and observed route/auth/session facts, not just a background output file.
|
|
18
|
+
Attested Pi runs are opt-in local-evidence tasks for a structured child Pi invocation. The `bg_run_pi_attested` tool is registered only when `PI_BG_FEATURES` includes `attested`; that capability is independent of delegate, Fusion, and ambient attribution. They are intended for cases where an operator wants local hashes and observed route/auth/session facts, not just a background output file.
|
|
19
19
|
|
|
20
20
|
They do not replace ordinary `bg_run`, and they do not provide remote cryptographic proof.
|
|
21
21
|
|
|
@@ -40,7 +40,7 @@ The logical argv always begins:
|
|
|
40
40
|
pi --mode json --provider <provider> --model <model>
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
For an Anthropic request, the package then adds `--extension <package-owned-anthropic-attribution>` before optional thinking. Next come optional `--thinking <thinking>`, literal `extraPiArgs`, and the prompt as the final user prompt argument. Forbidden extra args are direct auth (`--api-key`, `--auth-file`), mode/print (`-p`, `--print`, `--mode`), and duplicate structured fields (`--provider`, `--model`, `--thinking`). Missing attribution bytes refuse an Anthropic launch before task creation.
|
|
43
|
+
For an Anthropic request, the package then adds `--extension <package-owned-anthropic-attribution-child>` before optional thinking. This resolves the always-on child entrypoint and remains mandatory even when parent ambient attribution is disabled. Next come optional `--thinking <thinking>`, literal `extraPiArgs`, and the prompt as the final user prompt argument. Forbidden extra args are direct auth (`--api-key`, `--auth-file`), mode/print (`-p`, `--print`, `--mode`), and duplicate structured fields (`--provider`, `--model`, `--thinking`). Missing attribution bytes refuse an Anthropic launch before task creation.
|
|
44
44
|
|
|
45
45
|
The registry launches exactly one child through the resolved Pi executable with `shell:false`. The attestation records the stable logical argv (`['pi', ...]`), including any package-owned attribution extension, not platform-specific Windows Node/CLI shims. Attested tasks are created with generic background completion notification/wake disabled; terminal snapshots are still published through the task system.
|
|
46
46
|
|
|
@@ -84,7 +84,11 @@ Before spawn, the subsystem records:
|
|
|
84
84
|
- repository root realpath;
|
|
85
85
|
- cwd realpath.
|
|
86
86
|
|
|
87
|
-
|
|
87
|
+
Startup Git commands share the admission's overall 30 second deadline and cancellation signal. Each command captures at most 4 MiB per stdout/stderr stream; crossing that limit fails explicitly rather than constructing authority from truncated bytes. Git is direct-spawned in a detached process group on POSIX. Admission cancellation or deadline sends TERM and then force-cleans that same group; Windows uses the shared bounded `taskkill /T` then `/T /F` helper. The producer waits for the direct Git child `close` before releasing admission ownership, and cancellation/timeout errors remain distinct from a genuine non-repository/nonzero Git result. There is no empty-authority fallback.
|
|
88
|
+
|
|
89
|
+
The deadline bounds when process-tree termination begins; it cannot make an OS reap an uninterruptible process. If the kernel never reports child close even after force termination, admission drain continues to wait rather than claiming a fictitious reap or releasing an unowned late child. The real subprocess regression qualifies the ordinary local process-tree case, while Windows behavior is mock/injected coverage rather than a native-Windows claim.
|
|
90
|
+
|
|
91
|
+
The worktree must be clean at start. On successful child completion, finish commit/tree/cleanliness are checked again under the same bounded Git-command policy. Changed commit/tree or dirty worktree prevents attestation.
|
|
88
92
|
|
|
89
93
|
## Files and hashes
|
|
90
94
|
|
|
@@ -103,7 +107,7 @@ The sidecar schema is `phase2.pi_task_attestation.v1`. It includes locator, sour
|
|
|
103
107
|
|
|
104
108
|
## Durability and visibility
|
|
105
109
|
|
|
106
|
-
Initial output/events/stderr/wrapper files and metadata are created before spawn. On close, stdout/stderr buffers are fsynced to the events/stderr files. Successful event parsing rewrites task output from the parsed transcript; failures write diagnostics/stderr output.
|
|
110
|
+
Initial output/events/stderr/wrapper files and metadata are created before spawn. Those admission-time durable writes receive admission cancellation, close any opened handle, and remove uncommitted temporary/partial task artifacts before the admission can drain. Cancellation overlapping an atomic rename completes the directory-durability phase before it reports that the replacement may have become visible; registry cleanup then removes the pre-insertion task artifact. On close, stdout/stderr buffers are fsynced to the events/stderr files. Successful event parsing rewrites task output from the parsed transcript; failures write diagnostics/stderr output.
|
|
107
111
|
|
|
108
112
|
For a completed child with parsed events, the registry asks this subsystem to build the attestation, then writes `<task-id>.attestation.json` using durable atomic replacement. Only after that write returns does the registry set in-memory `task.status` to `completed` and publish terminal state. The metadata file may receive a completed snapshot earlier in this path; terminal in-memory/UI visibility is held until after sidecar durability.
|
|
109
113
|
|
|
@@ -114,6 +118,7 @@ No successful sidecar is emitted when:
|
|
|
114
118
|
- request validation fails;
|
|
115
119
|
- route/auth observation fails;
|
|
116
120
|
- worktree is dirty at start;
|
|
121
|
+
- Git preflight is cancelled, exceeds its deadline/output cap, or returns a genuine Git/non-repository error;
|
|
117
122
|
- child spawn fails;
|
|
118
123
|
- child exits non-zero, times out, or is killed;
|
|
119
124
|
- stdout events are malformed, incomplete, not newline-terminated, route-drifted, or the last reported assistant stop reason is not `stop`;
|
|
@@ -135,7 +140,7 @@ When changing this subsystem, re-check:
|
|
|
135
140
|
- OAuth-only observation and provider class mapping;
|
|
136
141
|
- stripped metered environment keys;
|
|
137
142
|
- JSON event strictness and route consistency;
|
|
138
|
-
- git clean/commit/tree checks at start and finish;
|
|
143
|
+
- git clean/commit/tree checks at start and finish, including bounded cancellation/tree reaping;
|
|
139
144
|
- prompt/report/events/stderr/output/wrapper/metadata hash coverage;
|
|
140
145
|
- sidecar write ordering relative to terminal visibility;
|
|
141
146
|
- no sidecar on failure paths.
|
|
@@ -5,7 +5,7 @@ mode: authored
|
|
|
5
5
|
review_policy: behavioral
|
|
6
6
|
stability: stable
|
|
7
7
|
covers_surfaces: []
|
|
8
|
-
covers_sources: [src/core/common.ts, src/core/registry.ts, src/core/windows-taskkill.ts]
|
|
8
|
+
covers_sources: [src/core/common.ts, src/core/registry.ts, src/core/reload-shell-owner.ts, src/core/shell-policy.ts, src/core/windows-taskkill.ts]
|
|
9
9
|
---
|
|
10
10
|
# Background task runtime
|
|
11
11
|
|
|
@@ -16,10 +16,18 @@ The runtime owns task identity, shell invocation, process lifecycle, bounded log
|
|
|
16
16
|
- Task statuses are exactly `running`, `completed`, `failed`, and `killed`.
|
|
17
17
|
- Terminal statuses are exactly `completed`, `failed`, and `killed`.
|
|
18
18
|
- Runtime directory: `.pi/tasks/<session-id>-<pid>/` under the project cwd.
|
|
19
|
-
- Per task: `<task-id>.output` and `<task-id>.json`; some agent modes may add wrapper or attestation files.
|
|
20
|
-
- In-memory recent retention prunes oldest finished tasks over the limit while preserving running tasks.
|
|
19
|
+
- Per task: `<task-id>.output` and `<task-id>.json`; some agent modes may add wrapper or attestation files. Ordinary shell-task snapshots and metadata include the non-secret activation shell facts (`policy`, `executable`, `argvPrefix`, and `dialect`) used for that launch. New records also emit `surviveReload`; missing legacy fields mean false. Opted records carry `reloadSurvival` audit facts, but those bytes never grant process authority or permit adoption.
|
|
20
|
+
- In-memory recent retention prunes oldest finished tasks over the limit while preserving running tasks and newest-result recency. If the oldest finished task still owns pending publication, pruning first abandons and disposes that publication as `retention_limit`; pending gates cannot force eviction of a newer result or grow retained finished tasks without bound.
|
|
21
21
|
- `resolveTask` accepts exact ids or unambiguous prefixes and fails loudly for empty, unknown, or ambiguous ids.
|
|
22
22
|
|
|
23
|
+
## Task admission
|
|
24
|
+
|
|
25
|
+
Every registry starter (ordinary, managed, delegate, and attested Pi) holds a counted admission scope with a one-way `AbortSignal` and a 30 second overall preflight deadline. Session shutdown closes admissions and aborts every live scope before cleanup. Cooperative preflight receives that signal; all other started operations remain tracked until they settle. Insertion plus spawn retain immediate checks with no yielding gap between them. Shutdown drains accepted admissions before taking its running-task snapshot. Therefore a preflight crossing closure cannot insert or spawn, while a child that spawned before closure was already inserted and is owned by shutdown cleanup.
|
|
26
|
+
|
|
27
|
+
Interrupted managed work is cancelled immediately and its workflow/child cleanup promise is awaited before the lease is released; if it was already inserted, terminal finalization remains registry-owned. A process child already inserted/spawned is bound to the admission signal and receives the normal stop path even while an admission-time metadata write is still settling. Interrupted wrapper/durable-file preflight awaits opened-handle/stream cleanup and removes owned partial task files. Node does not provide physical cancellation for every filesystem syscall: such a syscall remains admission-owned and shutdown waits for its settlement rather than racing it and allowing late artifact work. Cleanup failures are surfaced; they are not treated as successful cancellation.
|
|
28
|
+
|
|
29
|
+
For reload survival, only an opted ordinary execution whose initial metadata write and admission commit both completed is transferable. A pre-commit child remains old-registry shutdown work and can never appear in a reload claim. If its bounded stop wait fails, caller admission still rejects and the task leaves the registry, but the process-global owner retains the child, streams, listeners, tree state, and timers without a host adapter until actual terminal settlement permits release.
|
|
30
|
+
|
|
23
31
|
## Starting managed tasks
|
|
24
32
|
|
|
25
33
|
`startManagedTask` tracks a package-owned in-process asynchronous workflow through the same metadata, output, dock, status, logs, kill, EventBus terminal, and notification surfaces as process tasks. Fusion uses this path only after its no-child-yet durable preflight barrier. Managed cancellation invokes a task-owned callback; terminal state is not published until the workflow promise has settled and completed its own child cleanup/audit sealing.
|
|
@@ -34,11 +42,23 @@ Shell commands are spawned in the task cwd using `stdio: ['ignore','pipe','pipe'
|
|
|
34
42
|
|
|
35
43
|
Default delivery at registry level is `notifyOnCompletion:true` and `triggerOnCompletion:false`; surface tools may override that. [`bg_run`](../tools/bg_run.md) explicitly defaults both to true.
|
|
36
44
|
|
|
45
|
+
`surviveReload:true` is opt-in and requires `isAgent:false`. Validation occurs before admission timers, runtime directories, files, wrappers, insertion, or spawn. The task keeps the exact same child, PID, detached group/tree authority, pipes, output stream/path, launch nonce, completion id, shell policy, timeout deadline, output cap, and cumulative byte count across a supported reload. It is never restarted. Agent, managed, delegate, Fusion, and attested work refuse survival-shaped input; EventBus request v1 cannot request it. Dock rerun preserves the flag but creates a new execution/id/nonce under the current activation policy.
|
|
46
|
+
|
|
37
47
|
## Shell policy
|
|
38
48
|
|
|
39
|
-
|
|
49
|
+
One immutable policy is resolved per extension activation and shared by actual registry spawns and the agent-visible guidance hook. Mutating shell-selection environment variables cannot change that activation; `/reload` creates a new activation and resolves them again.
|
|
40
50
|
|
|
41
|
-
Windows
|
|
51
|
+
On non-Windows platforms, `PI_BG_POSIX_SHELL` accepts exactly `inherit`, `bash`, or `sh` and defaults to `inherit`:
|
|
52
|
+
|
|
53
|
+
- `inherit` preserves compatibility: use a non-empty `$SHELL`, otherwise `/bin/sh`, with `-c <command>`. The inherited executable is not replaced or turned into a login shell.
|
|
54
|
+
- `bash` checks executable `/bin/bash` first, then checks `bash` in `PATH` order.
|
|
55
|
+
- `sh` checks executable `/bin/sh` first, then checks `sh` in `PATH` order.
|
|
56
|
+
|
|
57
|
+
`PI_BG_POSIX_SHELL_PATH` is accepted only with explicit `bash` or `sh`. It must be a non-empty absolute path whose target is a regular executable file. A bad explicit path fails without falling back to search. The selected path is passed as the spawn executable and is never interpolated into the command. Bash and sh use `-c`, never `-lc`.
|
|
58
|
+
|
|
59
|
+
Inherited basename `bash` is classified as Bash. Reviewed Bourne-family shells (`sh`, `dash`, `ash`, `ksh`, `ksh93`, `mksh`, `pdksh`, `zsh`, `yash`, and `posh`) are classified as POSIX-function compatible. Nu, fish, csh/tcsh, and unknown names are reported as `user-non-posix`; they are never mislabeled as POSIX or Bash. This classification does not validate or replace an inherited executable, preserving existing spawn-failure behavior.
|
|
60
|
+
|
|
61
|
+
Windows ignores both POSIX variables. It defaults to `cmd.exe` or `ComSpec`, with args `['/d','/s','/c','"<command>"']` and `windowsVerbatimArguments:true`. `PI_BG_SHELL=cmd|bash` can select a shell; `PI_BG_SHELL_PATH` is accepted only with `PI_BG_SHELL` and must be an absolute `.exe`/`.com` path. `PI_BG_SHELL=bash` without a path searches PATH for `bash.exe` or `bash.com`; unresolved or invalid Windows shell settings fail before creating a task. Existing structured argv behavior is unchanged.
|
|
42
62
|
|
|
43
63
|
## Logs and output caps
|
|
44
64
|
|
|
@@ -50,23 +70,45 @@ Model-visible log reads use bounded file reads capped by `MAX_LOG_BYTES` (curren
|
|
|
50
70
|
|
|
51
71
|
Telemetry is task-owned. It is parsed from task output/control lines when the task reports it; it is never copied from the parent session. Optional telemetry includes context usage, token usage, tool usage, and model. Malformed optional telemetry is ignored without clearing prior task state; unknown wrapped-agent JSON is written to the transcript rather than silently dropped.
|
|
52
72
|
|
|
53
|
-
`isAgent` explicitly controls telemetry wrapping. If `isAgent:false`, a `pi -p` command is treated as an ordinary command. If `isAgent:true
|
|
73
|
+
`isAgent` explicitly controls telemetry wrapping. If `isAgent:false`, a `pi -p` command is treated as an ordinary command. If `isAgent:true`, the command contains an interceptable `pi -p`, `pi --print`, or `pi --mode json` invocation, and the resolved policy supports POSIX function syntax, the runtime writes a wrapper and converts Pi JSON events into task-owned metrics and human transcript lines. Path-qualified `pi` commands are not intercepted. Capability, not a generic “non-Windows” label, controls injection: Windows cmd records `win32-cmd-cannot-safely-intercept-pi-argv`, while an inherited Nu/fish/csh/unknown shell records `user-non-posix-shell-cannot-safely-intercept-pi-argv`. Neither route receives a Bash/POSIX function wrapper.
|
|
54
74
|
|
|
55
75
|
## Finalization and completion
|
|
56
76
|
|
|
57
77
|
A child closing with code `0` becomes `completed` unless killed/timeout/cap state overrides it. Nonzero exit becomes `failed` with `Exited with code ...`. User or shutdown kills become `killed`; timeout and output cap become `failed`.
|
|
58
78
|
|
|
59
|
-
During finalization, the runtime flushes wrapped-agent output, ends and waits for the output stream to finish/close, writes terminal metadata through the durable metadata path, updates waiters, initiates terminal EventBus publication, sends the completion notification when enabled and not shutting down, persists notification state, then prunes old finished tasks. Actual EventBus emission may wait behind the run-response publication gate and therefore may occur after the completion notification; it still occurs only after stream close and terminal metadata. The registry calls a historically named `closeAndFsyncOutputStream()` helper, but its current implementation ends and observes the stream rather than issuing `fsync` for ordinary `.output`; durable terminal truth refers to the metadata-backed status, not a stronger crash-durability guarantee for every output byte.
|
|
79
|
+
During finalization, the runtime flushes wrapped-agent output, ends and waits for the output stream to finish/close, writes terminal metadata through the durable metadata path, updates waiters, initiates terminal EventBus publication, sends the completion notification when enabled and not shutting down, persists notification state, then prunes old finished tasks. After a POSIX tree stop, finalization first waits for the originally owned detached group to be observed gone or records a loud failed result when force/proof fails; direct-child close alone cannot publish successful cleanup. Actual EventBus emission may wait behind the run-response publication gate and therefore may occur after the completion notification; it still occurs only after stream close and terminal metadata. The registry calls a historically named `closeAndFsyncOutputStream()` helper, but its current implementation ends and observes the stream rather than issuing `fsync` for ordinary `.output`; durable terminal truth refers to the metadata-backed status, not a stronger crash-durability guarantee for every output byte.
|
|
80
|
+
|
|
81
|
+
Terminal EventBus publication has separate `pending`, `delivered`, and `abandoned` truth. The legacy internal `terminalPublished` latch means delivered only; abandonment never sets it. A genuine synchronous emitter failure is retried after 100 ms, up to three total emit attempts. Exhaustion abandons publication with bounded diagnostics. Since an earlier listener can receive before a later listener throws, retries are at-least-once and consumers deduplicate by task id.
|
|
82
|
+
|
|
83
|
+
Publication gates race both activation closure and task-local abandonment. Gate resolution is followed by a lifecycle re-check; gate rejection abandons publication; shutdown, publisher disposal, or retention pruning clears gate references and retry timers. A late gate cannot emit or re-arm an old registry, and pruning an old gate releases its waiting continuation. These outcomes do not rewrite durable task status, waiter completion, or notification receipt state.
|
|
84
|
+
|
|
85
|
+
Synchronous emission has its own in-flight settlement phase. Reentrant shutdown/service close clears queued work but does not log abandonment or prune that task while its emitter is on the stack. A normal emitter return settles delivered. A throw settles abandonment/retry policy once only while that registry still owns the exact task; if the emitter synchronously transferred reload ownership before throwing, the old catch leaves the ledger pending with that attempt consumed and schedules no old retry. This prevents contradictory abandoned-then-delivered outcomes while preserving ordinary non-handoff close behavior.
|
|
86
|
+
|
|
87
|
+
For a survivor, pending/delivered/abandoned state and the attempt count move with the live execution. Reload clears old physical gates/retry handles without recording abandonment; a fresh adapter resumes the same cumulative three-attempt budget, including after a reentrant transferred throw. A terminal close during the hostless gap is queued. Notification uses a task-owned sending token, so a successful old or fresh send latches `notified` once and reload never resets it.
|
|
60
88
|
|
|
61
89
|
## Stopping tasks
|
|
62
90
|
|
|
63
91
|
Only `running` tasks can be stopped. Managed tasks invoke their task-owned cancellation callback and wait for workflow settlement; process tasks use the platform paths below.
|
|
64
92
|
|
|
93
|
+
Session shutdown atomically closes task admission before managed-workflow cleanup starts. For a real `reason:"reload"`, it first detaches and removes only admission-committed opted ordinary executions, then closes old publication/EventBus and applies normal stop paths to everything else. Pending survivor publication is transferred without false abandonment. For `quit`, `new`, `resume`, or `fork` (including clone), nothing transfers: all work follows normal cleanup. Registry admission/publication closure is one-way; stale lifecycle, admission, gate, lease, claim, adapter, and retry continuations cannot mutate a newer activation.
|
|
94
|
+
|
|
95
|
+
## Same-process reload owner
|
|
96
|
+
|
|
97
|
+
`src/core/reload-shell-owner.ts` installs a structurally checked process-global v1 hub under `Symbol.for('pi-background-tasks.reload-shell-owner.v1')`. Identity is the length-delimited tuple `(process.pid, exact session id, canonical cwd)`. It uses random activation/claim nonces, monotonic generations, and a two-phase claim: the fresh registry imports the same task objects and durably advances audit facts before the adapter becomes visible. There is no `instanceof` protocol test, metadata scan, PID adoption, external daemon, or liveness-derived exit result.
|
|
98
|
+
|
|
99
|
+
The adapter is the only retained closure over the current registry/Pi/EventBus/notification host and is removed synchronously before shutdown awaits. The owner retains only the living Node child, pipes/listeners, stream, timers, immutable launch facts, tree authority, metadata chain, and logical delivery state. A fixed referenced 30-second handoff deadline is not reset by claim/abort. No compatible claimant (including extension removal, config/factory failure, or an incompatible package copy) triggers retained-tree cleanup and `pi_bg_reload_handoff_expired`; a real close code/signal and tree proof remain required. A bounded stop timeout never releases those resources: one owner-only terminal continuation removes the execution and empty slot if a close settles later. If close/tree proof never arrives, force/proof uncertainty stays loud and retains minimal authority rather than fabricating cleanup or permitting a conflicting activation.
|
|
100
|
+
|
|
101
|
+
Supported scope is same-process normal Pi reload with the same session id and canonical cwd. Hard crash, SIGKILL, power loss, process restart, resume/new/fork/clone, and PID/file reconstruction are unsupported. Normal TUI/RPC/print/JSON modes provide lifecycle binding. Empty or mode-only SDK reload lacks the fresh `session_start` in Pi 0.84/0.86 and is blocked upstream; direct `AgentSession.dispose()` also lacks `session_shutdown`, while `AgentSessionRuntime.dispose()` is supported.
|
|
102
|
+
|
|
65
103
|
POSIX stop path:
|
|
66
104
|
|
|
67
|
-
1.
|
|
68
|
-
2.
|
|
69
|
-
3.
|
|
105
|
+
1. use the immutable process-group id captured directly from the detached spawn (never a restored metadata PID), and publish one shared grace/force owner before signaling,
|
|
106
|
+
2. send `SIGTERM` to that process group (`-pid`), falling back to the child handle's `kill` when the group signal fails,
|
|
107
|
+
3. if the direct child closes, probe the still-owned group; only `ESRCH` disarms escalation,
|
|
108
|
+
4. after the grace window, probe and send at most one group `SIGKILL`, then perform bounded signal-0 probes until `ESRCH`,
|
|
109
|
+
5. on force failure or inability to prove the group gone within the stop window, report `failed` with `Descendant processes may have leaked` rather than claim a successful kill.
|
|
110
|
+
|
|
111
|
+
The POSIX grace/proof owner remains referenced after leader close, is shared by concurrent stop requests, and is permanently disarmed once group absence is observed so it cannot later signal a reused group id. This proves process-group disappearance for the ordinary local case; it does not claim that Node reaps grandchildren or that signals can cure a kernel-uninterruptible process. Such a limit is surfaced as a bounded cleanup failure.
|
|
70
112
|
|
|
71
113
|
Windows stop path:
|
|
72
114
|
|
|
@@ -88,4 +130,4 @@ Windows never falls back to root-only `child.kill` for tree termination. The tas
|
|
|
88
130
|
|
|
89
131
|
## Source ownership/reference
|
|
90
132
|
|
|
91
|
-
Primary source ownership for this document is `src/core/common.ts`, `src/core/registry.ts`, and `src/core/windows-taskkill.ts`.
|
|
133
|
+
Primary source ownership for this document is `src/core/common.ts`, `src/core/registry.ts`, `src/core/reload-shell-owner.ts`, `src/core/shell-policy.ts`, and `src/core/windows-taskkill.ts`.
|
|
@@ -5,22 +5,37 @@ mode: authored
|
|
|
5
5
|
review_policy: behavioral
|
|
6
6
|
stability: evolving
|
|
7
7
|
covers_surfaces: []
|
|
8
|
-
covers_sources: [src/core/durable-fs.ts, src/core/pi-launch.ts]
|
|
8
|
+
covers_sources: [src/core/canonical-json.ts, src/core/durable-fs.ts, src/core/pi-launch.ts, src/core/task-durable.ts]
|
|
9
9
|
---
|
|
10
10
|
# Child launch, durability, and safety
|
|
11
11
|
|
|
12
|
-
Primary sources: `src/core/pi-launch.ts` and `src/core/
|
|
12
|
+
Primary sources: `src/core/pi-launch.ts`, `src/core/durable-fs.ts`, `src/core/task-durable.ts`, and `src/core/canonical-json.ts`.
|
|
13
13
|
|
|
14
14
|
## Pi launch resolution
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
`resolvePiLaunch()` returns a verified executable plus an argv prefix; callers pass both directly to `spawn` without a shell.
|
|
17
17
|
|
|
18
|
-
On
|
|
18
|
+
On POSIX platforms, resolution searches `PATH` in order. A candidate named `pi` must canonicalize to a regular file and pass execute-access validation. Invalid candidates are skipped so a later executable can win. The returned `executable` is the canonical absolute candidate that passed those checks, not the bare name `pi`; changing the child cwd or spawn environment/PATH therefore cannot reselect a different file.
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
- `.exe`, `.com`: launch the target directly.
|
|
20
|
+
If no PATH candidate qualifies, resolution canonicalizes the running host script and walks upward through package manifests. It skips only valid nameless sub-manifests, stops at the nearest named package boundary, and accepts the host only when:
|
|
22
21
|
|
|
23
|
-
|
|
22
|
+
- the package name is exactly `@earendil-works/pi-coding-agent`;
|
|
23
|
+
- the package has a valid npm `bin` string or `bin.pi` entry;
|
|
24
|
+
- the canonical host script is the canonical declared bin target.
|
|
25
|
+
|
|
26
|
+
A verified running Pi host is authoritative. If the host path is genuinely absent or the nearest named package is foreign, POSIX can use the same exact installed-package route as an embedded Windows SDK host: resolve the Pi manifest directly, or resolve the package entry and walk to its nearest named manifest. This supports a Node/Bun application that embeds the documented Pi SDK without pretending its own `process.argv[1]` is the Pi CLI. An arbitrary JavaScript host is not itself a Pi fallback merely because its filename ends in `.js`, `.cjs`, or `.mjs`.
|
|
27
|
+
|
|
28
|
+
Windows never consults PATH shims. It first applies the same named-package check to the running host, which covers a global Pi installation loading this package from Pi's separate extension prefix. A valid running Pi host is authoritative. A genuinely absent/foreign SDK host may use the exact installed-package route. Every accepted module manifest on either platform must carry the exact Pi package name; a host that is unreadable or claims to be Pi but has an invalid bin does not silently fall through to another installation.
|
|
29
|
+
|
|
30
|
+
All package routes realpath the manifest root and bin target, reject absolute or escaping bins, require a regular target file, and preserve these launch forms:
|
|
31
|
+
|
|
32
|
+
- `.js`, `.cjs`, `.mjs`: launch through a generic `node`, `nodejs`, or `bun` `process.execPath`, with the canonical target as `argvPrefix[0]` (`package-node-cli`);
|
|
33
|
+
- Windows `.exe`, `.com`: launch the canonical target directly with an empty argv prefix (retaining the historical `package-node-cli` kind);
|
|
34
|
+
- `.cmd`, `.bat`, `.ps1`, extensionless package targets, and other forms: reject rather than invoke a shell.
|
|
35
|
+
|
|
36
|
+
A non-generic `process.execPath` whose executable name is exactly `pi` (`pi.exe`/`pi.com` on Windows) is a separate `compiled-host` authority: the canonical regular host executable is relaunched directly (with POSIX execute-access or Windows native-extension validation), and a virtual script is not passed as an argument. A Bun virtual host-script shape such as `/$bunfs/root/...` is packaging evidence shared by arbitrary compiled SDK applications and grants no authority by itself. Renamed compiled Pi executables are therefore not claimed as supported; this branch preserves limited compiled-host mechanics but does not certify any vendor binary.
|
|
37
|
+
|
|
38
|
+
Malformed, unreadable, non-object, or malformed-name manifests are hard package-boundary failures. A missing host source (`ENOENT`/`ENOTDIR`), no named boundary, or a nearest foreign package can permit exact module discovery; source `realpath` permission/I/O failures and all claimed-Pi integrity failures are fatal and cannot be hidden by another installation. Resolution failures throw `PiLaunchResolutionError` with code `pi_executable_resolution_failed`; no substitute route, model, shell interpolation, or invalid-manifest fallback is selected.
|
|
24
39
|
|
|
25
40
|
## Windows argv and command-line length
|
|
26
41
|
|
|
@@ -32,13 +47,17 @@ Delegate seed bytes are delivered over stdin, not argv, so large seeds do not re
|
|
|
32
47
|
|
|
33
48
|
`durable-fs.ts` provides two public operations:
|
|
34
49
|
|
|
35
|
-
- `writeFileDurable(path, data)`: open the target once with `w`, write, `sync()`, close.
|
|
36
|
-
- `replaceFileDurable(path, data)`: create a task-owned temp file with exclusive `wx` at `0o600`, write, `sync()`, close, rename over the target, then directory-sync on non-Windows.
|
|
50
|
+
- `writeFileDurable(path, data, { signal? })`: open the target once with `w`, write, `sync()`, close.
|
|
51
|
+
- `replaceFileDurable(path, data, { signal? })`: create a task-owned temp file with exclusive `wx` at `0o600`, write, `sync()`, close, rename over the target, then directory-sync on non-Windows.
|
|
37
52
|
|
|
38
53
|
Invariant: a pathname is never reopened merely to fsync it. Sync failures are fatal and surfaced as `DurableFileError`; cleanup failures are retained in the error object instead of hiding the primary failure.
|
|
39
54
|
|
|
55
|
+
Cancellation is cooperative between filesystem phases, not a claim that Node can interrupt every in-flight kernel syscall. Once a handle is opened, cancellation waits for the current operation and handle close. Before rename it removes the owned temp and never commits it. If cancellation overlaps a successful rename, directory sync still completes before `DurableFileCancellationError` reports `renameCompleted: true`; the caller therefore knows the replacement may already be visible and can perform its owning cleanup.
|
|
56
|
+
|
|
40
57
|
Temp ownership matters: if exclusive temp creation collides, the caller does not delete the other writer's file. A successful rename is the commit point; if a post-rename directory sync fails, the error marks `renameCompleted: true` because the replacement may already be visible.
|
|
41
58
|
|
|
59
|
+
`task-durable.ts` is the lightweight task-facing wrapper for durable files, atomic JSON, and output-stream closure. Keeping it separate prevents ordinary process startup from importing the opt-in attested producer. `canonical-json.ts` owns stable key ordering and SHA-256 byte labels shared by delegate, Fusion, and attested artifacts; the attested module re-exports those helpers for API compatibility.
|
|
60
|
+
|
|
42
61
|
## POSIX directory sync limitation
|
|
43
62
|
|
|
44
63
|
After atomic replace, POSIX-like platforms open and sync the parent directory to durably record the rename. Windows skips directory sync because Node/Windows directory fsync is not portable in the same way. This is an explicit platform limitation, not a silent success claim; file contents are still written and synced before rename.
|
|
@@ -5,7 +5,7 @@ mode: authored
|
|
|
5
5
|
review_policy: behavioral
|
|
6
6
|
stability: evolving
|
|
7
7
|
covers_surfaces: []
|
|
8
|
-
covers_sources: [extensions/delegate-child.ts, src/core/delegate/artifacts.ts, src/core/delegate/budget.ts, src/core/delegate/hook-contract-evidence.json, src/core/delegate/hook-contract.ts, src/core/delegate/launch.ts, src/core/delegate/result-package.ts, src/core/delegate/runner.ts, src/core/delegate/seed.ts, src/core/delegate/types.ts, src/delegate-child-extension.ts, src/delegate-extension.ts]
|
|
8
|
+
covers_sources: [extensions/delegate-child.ts, src/core/delegate/artifacts.ts, src/core/delegate/budget.ts, src/core/delegate/facade-contract.ts, src/core/delegate/hook-contract-evidence.json, src/core/delegate/hook-contract.ts, src/core/delegate/launch.ts, src/core/delegate/result-package.ts, src/core/delegate/runner.ts, src/core/delegate/seed.ts, src/core/delegate/types.ts, src/core/lazy-module.ts, src/delegate-child-extension.ts, src/delegate-extension.ts]
|
|
9
9
|
---
|
|
10
10
|
# Delegation subsystem
|
|
11
11
|
|
|
@@ -15,12 +15,15 @@ This document is the primary behavioral owner for delegation runtime code:
|
|
|
15
15
|
- `src/delegate-child-extension.ts`
|
|
16
16
|
- `extensions/delegate-child.ts`
|
|
17
17
|
- every current file under `src/core/delegate/**`, including `hook-contract-evidence.json`
|
|
18
|
+
- the internal activation-local loader in `src/core/lazy-module.ts`, shared with the Fusion facade
|
|
18
19
|
|
|
19
20
|
It does **not** claim ownership of shared `common`, `registry`, `pi-launch`, or `durable-fs`; delegation consumes those integration points.
|
|
20
21
|
|
|
21
22
|
## Behavioral contract
|
|
22
23
|
|
|
23
|
-
Delegation provides one background child Pi agent, one directive, one pinned route, and read-only inspection tools. The parent gets a launch receipt immediately and later retrieves a verified answer through `bg_result`.
|
|
24
|
+
Delegation provides one background child Pi agent, one directive, one pinned route, and read-only inspection tools. It is registered only when `PI_BG_FEATURES` includes `delegate`. The parent gets a launch receipt immediately and later retrieves a verified answer through `bg_result`.
|
|
25
|
+
|
|
26
|
+
`bg_result` is a shared derived surface, not part of the delegate toggle: it is registered exactly once when delegate or Fusion is enabled. Delegate-only and Fusion-only configurations therefore retain the correct verifier/retrieval path, while process-only has neither a producer nor `bg_result`.
|
|
24
27
|
|
|
25
28
|
The design deliberately separates:
|
|
26
29
|
|
|
@@ -30,6 +33,14 @@ The design deliberately separates:
|
|
|
30
33
|
- parent adjudication (`outcome.json`),
|
|
31
34
|
- user retrieval (`bg_result`).
|
|
32
35
|
|
|
36
|
+
## Lazy facade activation
|
|
37
|
+
|
|
38
|
+
Enabled schemas, descriptions, argument preparation, renderers, and tool registration are immediate. Delegate launch/seed/budget/runner code is imported only on the first valid `bg_delegate` execution. `bg_result` has independent delegate and Fusion verifier loaders and selects one only after task facts identify the producer; a running result imports neither verifier.
|
|
39
|
+
|
|
40
|
+
Each loader is activation-local and single-flight: simultaneous cold calls await one module import but continue as independent runs. Its states are `unloaded`, `loading`, `loaded`, `failed`, and `closed`. An import failure is wrapped with a bounded module-specific diagnostic and remains sticky for that activation. The entrypoint constructs one activation-local synchronous close fence and explicitly gives it to every enabled core, Fusion, delegate, and result lane. Its first shutdown handler closes every loader, advances Fusion generation, aborts Fusion controllers, and aborts delegate preparation before any later handler can await cleanup. A module arriving after closure is discarded before its operation can create an artifact, task, or child or touch stale host APIs. Reload constructs a new facade and may retry; the closed instance is never reopened, and no process-global cache stores `pi` or host context.
|
|
41
|
+
|
|
42
|
+
The main entrypoint dynamically imports the lightweight delegate/result facade only when delegate or Fusion is enabled. Process-only startup therefore imports neither advanced facade nor verifier/producer code. Published Pi entrypoints execute precompiled JavaScript from `dist/`; authoritative TypeScript remains shipped for source/API compatibility and development tests.
|
|
43
|
+
|
|
33
44
|
## Seed and context policy
|
|
34
45
|
|
|
35
46
|
The seed schema is `pi-background-tasks.delegate-seed.v2`. It wraps the frozen `visible-conversation-ledger-v2` projection under delegate policy id `delegate-inspect-v1`; it never emits Fusion input schemas or claims Fusion provenance. The exact selected `extension_mode` is hash-bound into the seed, and is also recorded in launch details, task facts, and `manifest.json`.
|
|
@@ -55,6 +66,8 @@ The assistant message containing the active `bg_delegate` call is excluded as a
|
|
|
55
66
|
|
|
56
67
|
Public admission first loads hook evidence and resolves the requested/current route; launch preparation then resolves the package-owned child guard extension. Inside `preflightDelegateLaunch()`, the hook-contract gate runs before capability/tool policy, limit checks, seed construction, and launch budget admission. All of these checks complete before child process, child session directory, or artifact root creation. Route, guard-extension, hook-contract, or later admission refusal therefore leaves zero child processes and zero delegate artifacts; do not rely on one absolute error-precedence order across those pre-preflight resolutions.
|
|
57
68
|
|
|
69
|
+
Preparation remains uncommitted until the registry accepts task ownership. It receives the activation abort signal and checks it around every awaited artifact/session write. A complete prepared value carries an idempotent producer-owned rollback. If shutdown wins before registration, the facade waits for that rollback and removes the run root plus only empty delegate parent directories before rejecting; no seed, projected context, child-session directory, or starter survives. If a starter rejects, the facade checks the exact task id in the registry and rolls back only when it is unregistered. Registered task artifacts stay under the existing registry lifecycle and are never deleted by preparation rollback. Cleanup failure is bounded, logged, and propagated rather than hidden.
|
|
70
|
+
|
|
58
71
|
The child launch:
|
|
59
72
|
|
|
60
73
|
- direct Pi spawn through the registry, not a shell;
|
|
@@ -66,7 +79,7 @@ The child launch:
|
|
|
66
79
|
- extension discovery disabled by default in `extensionMode:"isolated"`;
|
|
67
80
|
- extension discovery deliberately enabled only by `extensionMode:"ambient"`;
|
|
68
81
|
- non-Anthropic children explicitly load the package-owned child guard in both modes;
|
|
69
|
-
- Anthropic children explicitly load
|
|
82
|
+
- Anthropic children explicitly load the always-on `extensions/anthropic-attribution-child.ts` safety entrypoint first, then the child guard, in both modes, regardless of the parent ambient-attribution capability.
|
|
70
83
|
|
|
71
84
|
Ambient mode exists for providers registered by user/project Pi extensions. It omits only `--no-extensions`; it accepts no caller-supplied extension paths and performs no provider fallback or route substitution. Ambient discovery executes arbitrary trusted-location extension code in the child process. That code has Node process privileges and is not sandboxed by Pi's model-visible tool allowlist, so ambient mode deliberately weakens the inspect-only process-isolation guarantee. It must not be described as safe or equivalent to isolated mode.
|
|
72
85
|
|
|
@@ -9,17 +9,24 @@ covers_sources: []
|
|
|
9
9
|
---
|
|
10
10
|
# Docs freshness gate
|
|
11
11
|
|
|
12
|
-
This authored section defines the boundary: documentation facts are extracted from package metadata and TypeScript ASTs, then generated into docs and the manifest. Unsupported syntax fails the gate rather than falling back to regex or stale hand-maintained inventories. Public registrations must remain unconditional top-level direct calls
|
|
12
|
+
This authored section defines the boundary: documentation facts are extracted from package metadata and TypeScript ASTs, then generated into docs and the manifest. Unsupported syntax fails the gate rather than falling back to regex or stale hand-maintained inventories. Public registrations must remain unconditional top-level direct calls, use the one validated local tool-wrapper shape, use the closed finite-variant grammar below, or occur in a direct inline `session_start` activation callback whose containing statement already has validated availability. Host/method aliases, computed access, nested/dynamic conditions, loops, wrapper chaining/passing, constructor helpers, ambiguous public metadata, destructured Pi parameters, and repeated imported registrars are rejected.
|
|
13
|
+
|
|
14
|
+
The finite grammar recognizes only a top-level immutable binding returned by the actual unshadowed import of `parseBackgroundTasksConfig()` and direct top-level registration/registrar calls guarded by a validated feature atom, the exact delegate-or-Fusion derived-result disjunction, or one of the two registrable dock literals. The parser itself must have the reviewed structural shape: exactly one final direct return, a frozen returned config, and the exact frozen feature record; nested return paths are structurally rejected. Config authority remains lexical through supported activation callbacks and independently validated imported registrars. Every captured or local config-binding use is checked; aliases, destructuring, writes, updates, mutation calls, argument/closure escapes, shadowed parser names, and unsupported conditions fail. `off` cannot guard a registration. Runtime feature/shortcut/default enums are compared with the docs engine's closed enum, so drift fails generation.
|
|
15
|
+
|
|
16
|
+
Every registration-owning function, imported registrar, and supported activation callback is checked as a whole, including parameter declarations and initializers. Destructured registration bindings, computed or property registration methods on unknown hosts, default-parameter host aliases, initializer calls/throws/control flow, and unmodeled body returns or throws are rejected. The sole early-return form is the structurally validated synchronous Anthropic duplicate-owner guard: the exact production claim channel, exact schema field/value and closed probe shape, one local empty acknowledgement array, one adjacent direct claim emit with the exact acknowledgement append, and the positive-length bare return. A local pre-probe claim listener is not accepted. Returns inside command/tool/event handlers that own no registrations remain unrelated and legal. Extracted surfaces carry a normalized availability expression and source-derived default status through the manifest, INDEX/read gate, README facts, and generated surface contracts.
|
|
13
17
|
|
|
14
18
|
<!-- pi-docs:begin name="docs-freshness-gate" generator="scripts/docs/generate.mjs" -->
|
|
15
|
-
- Canonical package version: `2.
|
|
19
|
+
- Canonical package version: `2.6.0`
|
|
16
20
|
- Governed markdown docs: 42
|
|
17
|
-
- Public surfaces extracted:
|
|
18
|
-
-
|
|
21
|
+
- Public surfaces extracted: 32
|
|
22
|
+
- Public surfaces available by default: 31
|
|
23
|
+
- Finite feature values: `process`, `delegate`, `fusion`, `attested`, `attribution`
|
|
24
|
+
- Finite dock shortcut values: `shift+down`, `ctrl+alt+b`, `off`
|
|
25
|
+
- Governed production sources: 60
|
|
19
26
|
- Tool contracts extracted: 11
|
|
20
|
-
- Schema IDs extracted:
|
|
21
|
-
- Environment variable references extracted:
|
|
22
|
-
- Behavioral attestation receipts not passing:
|
|
27
|
+
- Schema IDs extracted: 48
|
|
28
|
+
- Environment variable references extracted: 54
|
|
29
|
+
- Behavioral attestation receipts not passing: 9
|
|
23
30
|
- Receipt store: `docs/attestations.json`
|
|
24
31
|
|
|
25
32
|
`npm run docs:verify` is read-only: it renders generated files twice in memory and compares them with committed bytes. `npm run docs:generate` is the only docs writer.
|