dsh-ptc-plus 0.5.1 → 0.5.3

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 (85) hide show
  1. package/README.en.md +10 -4
  2. package/README.md +10 -4
  3. package/client.cjs +1623 -104
  4. package/compiler-core.cjs +132 -132
  5. package/compiler-core.cjs.map +3 -3
  6. package/docs/adr/0005-temporary-rejected-cell-edit-transport.md +2 -0
  7. package/docs/adr/0017-track-the-latest-dsh-public-surface.md +4 -2
  8. package/docs/adr/0019-plugin-settings-and-kill-switch.md +2 -2
  9. package/docs/adr/0023-global-user-bindings.md +12 -0
  10. package/docs/adr/0027-run-the-session-kernel-in-a-killable-helper-process.md +11 -1
  11. package/docs/adr/0028-attach-to-the-host-ptc-execution-seam.md +1 -1
  12. package/docs/adr/0030-serve-both-typert-codec-generations-from-one-codec.md +2 -2
  13. package/docs/adr/0032-own-session-log-facts-in-a-host-only-projection.md +31 -1
  14. package/docs/adr/0044-coexist-with-the-official-persistent-repl.md +4 -4
  15. package/docs/adr/0045-customize-model-visible-prompt-text.md +38 -0
  16. package/docs/adr/0046-gate-the-commit-with-the-semantic-evidence-tier.md +35 -0
  17. package/docs/adr/0047-store-journal-argument-digests-in-the-projection.md +88 -0
  18. package/docs/adr/0048-supersede-a-rewritten-cell.md +48 -0
  19. package/docs/adr/0049-prove-a-settlement-against-its-published-record.md +53 -0
  20. package/docs/adr/0050-read-host-surfaces-through-their-published-contract.md +48 -0
  21. package/docs/adr/README.md +7 -1
  22. package/docs/architecture.md +3 -3
  23. package/docs/client-ui.md +5 -2
  24. package/docs/durability-design.md +15 -2
  25. package/docs/installation.md +9 -9
  26. package/docs/releases/v0.5.2.md +91 -0
  27. package/docs/releases/v0.5.3.md +77 -0
  28. package/docs/runtime-reference.md +43 -3
  29. package/docs/unmeasured-cell-recovery-repair-proposal.md +387 -0
  30. package/docs/user-bindings.en.md +15 -0
  31. package/docs/user-bindings.md +15 -0
  32. package/docs/v0.5.3-goals.md +117 -0
  33. package/docs/verification.md +7 -3
  34. package/index.js +100 -31
  35. package/internal/binding-adoption.js +136 -0
  36. package/internal/bounded-literal.js +85 -0
  37. package/internal/capability-command-receipt.js +18 -4
  38. package/internal/capability-delivery-proof.js +70 -0
  39. package/internal/capability-environment.js +200 -11
  40. package/internal/capability-plan.js +11 -8
  41. package/internal/cell-error.js +13 -0
  42. package/internal/config-spec.js +126 -6
  43. package/internal/cordis-tools-owner.js +2 -1
  44. package/internal/direct-surface-owner.js +39 -21
  45. package/internal/edit-transport-owner.js +81 -15
  46. package/internal/execution-provider-owner.js +6 -0
  47. package/internal/execution-seam-compat.js +25 -0
  48. package/internal/host-tool-sdk.js +308 -0
  49. package/internal/isolated-worker.js +134 -17
  50. package/internal/kernel-worker.js +167 -3
  51. package/internal/legacy-cell-analysis.js +14 -2
  52. package/internal/legacy-repl-convenience.js +16 -3
  53. package/internal/official-repl-priority.js +31 -10
  54. package/internal/program-bindings.js +12 -1
  55. package/internal/projection-retention.js +238 -0
  56. package/internal/prompt-text.js +858 -0
  57. package/internal/recovery-tips.js +50 -11
  58. package/internal/rejected-cell-editor.js +3 -2
  59. package/internal/repl-memory-projection.js +21 -2
  60. package/internal/runtime-bridge-owner.js +114 -9
  61. package/internal/runtime-config.js +2 -0
  62. package/internal/runtime-contexts.js +14 -5
  63. package/internal/runtime-messages.js +48 -15
  64. package/internal/session-cell-executor.js +132 -11
  65. package/internal/session-journal-recovery.js +1204 -116
  66. package/internal/session-journal-schema.js +10 -5
  67. package/internal/session-journal.js +214 -14
  68. package/internal/session-log-view.js +133 -26
  69. package/internal/session-runtime.js +746 -38
  70. package/internal/session-state.js +27 -0
  71. package/internal/stateful-root-compiler.js +11 -0
  72. package/internal/stateful-root-runtime.js +17 -0
  73. package/internal/user-binding-console.js +7 -0
  74. package/internal/user-bindings-owner.js +112 -6
  75. package/internal/user-bindings.js +2 -2
  76. package/internal/validated-parse-repair.js +6 -1
  77. package/internal/worker-client.js +27 -4
  78. package/package.json +16 -6
  79. package/src/client-activity.js +24 -4
  80. package/src/client-capabilities-view.js +504 -27
  81. package/src/client-copy.js +187 -2
  82. package/src/client-indicator-view.js +2 -1
  83. package/src/client-repl-view.js +35 -11
  84. package/src/client-styles.js +64 -2
  85. package/src/client.js +3 -1
@@ -28,6 +28,8 @@ If the public Host never invokes `presentationMeta` for this owned nested succes
28
28
 
29
29
  The root result's private `dshPtcPlusPostEdit` evidence keeps the editor cause in `startSeq` and the actual derived execution in `executionStartSeq`, alongside the derived source, journal and target sequence. Recovery validates both start/settle pairs, the root journal's accepted editor arguments and receipt, the canonical derived outcome and the transform against the captured target. It folds the derived journal after the root cell using the actual execution-start sequence. Model-visible provenance comes from the root source and the original target's source; raw dispatch records alone do not prove it. Losing either source or a required execution fact contracts the derived binding frontier. A failed start append prevents dispatch; a failed settlement append invalidates future recovery evidence without repeating or undoing an execution. Cold replay uses recorded nested results and does not redispatch the editor or repeat external effects. A successfully published derived source becomes the next editable target through its own execution-start sequence.
30
30
 
31
+ The derived execution can also settle without running: when the cell cannot obtain a compute baseline before its run frame, its journal records an unmeasured `noop` settlement. That settlement is admissible evidence for the accepted edit, because it proves the dispatch relation while claiming no state: the root cell keeps its own journal and stays provable, no revision binding is published, no successful revision is recorded, and the unexecuted source never becomes the next editable target. Only the unmeasured route qualifies; a rejected source, an aborted cell or a lost helper keep the rule that a derived edit journal must record an execution, and cold recovery of an unexecuted settlement folds no derived state and replays no editor.
32
+
31
33
  The derived source itself may contain an editor call. Such a call does not gain a fresh root edit authority during the derived execution: it receives an unavailable receipt rather than recursively scheduling another derived run. Each model-authored root call can accept at most one deferred edit. This bounds recursion and prevents a derived call from resetting the root edit lifecycle.
32
34
 
33
35
  ## Provider and diagnostic handling
@@ -10,9 +10,11 @@ PTC Plus must remain independently installable on unmodified official DSH distri
10
10
 
11
11
  The compatibility target is the latest available DSH release. Production behavior, configuration, tests, installation guidance, and current compatibility claims depend on public extension contracts and observed live schemas, never on a DSH version allowlist or comparison. An upstream default projection is not an extension ceiling: when public scoped registration and presentation support a plugin-owned capability, PTC Plus preserves that capability and its model-visible identity instead of deleting or renaming it to match the default. Deterministic normalization remains valid for a call that is outside the declared direct surface when the live schema proves one native target and the lowering preserves arguments, call identity, authority, and result semantics. Deterministic tests exercise the consumed contracts; model-backed and packaging acceptance runners use the installed DSH and record `dsh --version` in generated reports so failures remain attributable without turning the observed value into policy. Concrete DSH release numbers may appear only in source comments that identify historical integration or evaluation evidence and cannot influence execution or acceptance.
12
12
 
13
- Release discovery is separated from candidate verification. The root manifest declares the official `next` channel for the DSH packages used by development and freezes the resolved cohort in the root lockfile. `compat/latest` declares only the official DSH meta-package on `latest` and freezes that complete graph in its own lockfile. Resolved versions in these generated lock snapshots are reproducibility evidence selected by the channel declarations; they are not compatibility allowlists or runtime branches. `compat/current` separately freezes the cohort of the newest published official GitHub release, including prereleases. Its generated manifest selects the matching official npm channel, and `release.json` records the discovery provenance. This published-release identity, rather than a fixed list of channel names, owns the current target. Ordinary `verify` and the full branch of `check` make no registry request and fail quickly when the installed root graph differs from the root lock; they also validate the published cohort against its frozen provenance.
13
+ Release discovery is separated from candidate verification. Support is expressed as channels, not versions. `compat/channels.json` is generated from the official dist-tags and records, per channel, the resolved host release, the tarball integrity of every host package whose published surface the plugin probes, and whether that channel may block a release: `latest` (newest published release) and `next` (in-flight release candidate) block, while `alpha` (a pre-adaptation for a line the host has not released yet) only reports. A probe package the release does not publish is recorded with the version and integrity that release's own dependency range resolves to, so the channel still names the bytes an install receives; a probe package the release neither publishes nor depends on is recorded as absent, and the install proof rejects a graph that contains one. These records are reproducibility evidence selected by the channel declarations; they are not compatibility allowlists or runtime branches. `npm run host:channels:update` is the only way to refresh them and starts from the registry; ordinary `verify` and the full branch of `check` make no registry request and validate the tracked manifest offline. The root manifest declares the official `next` channel for the DSH packages used by development, and `host:channels:graph` moves the lock onto the release that channel resolves to; the scheduled channel workflow runs it in the same commit as a channel change. Ordinary gates read whichever graph the lock froze and never resolve a tag.
14
14
 
15
- The scheduled baseline refresh queries the official registry. It also queries official GitHub releases and matches the newest non-draft DSH release to the official meta-package dist-tags. A missing distribution fails discovery instead of silently using an older Host. When either channel or the published-release cohort moves, it updates the affected lock snapshots on an automation-owned branch, opens or updates one pull request, and dispatches the normal CI workflow once for that frozen candidate. GitHub suppresses workflow runs from branch and pull-request events produced by the workflow's `GITHUB_TOKEN`, so the explicit dispatch is the candidate's only CI run. If neither tag nor the published-release cohort moved, it does not resolve or install the dependency graph. The Host contract suite tests the packed plugin against the root `next` graph and creates a repository-external consumer from the tracked `latest` lock. That consumer exposes the public packages from the selected Host graph to the plugin and runs the execution-seam, RPC-codec, and compressed Session persistence contracts. The suite also creates a repository-external consumer from `compat/current` and tests the packed plugin on that unmodified published graph. `npm run test:host-contract -- current` selects this object alone. No compatibility run independently selects tags for DSH service packages, because their dist-tags need not move as one cohort.
15
+ The scheduled channel refresh resolves every channel from its dist-tag. When a channel moves it commits the regenerated manifest on an automation-owned branch, opens or updates one pull request, and dispatches the normal CI workflow once for that candidate. GitHub suppresses workflow runs from branch and pull-request events produced by the workflow's `GITHUB_TOKEN`, so the explicit dispatch is the candidate's only CI run. If no channel moved, the refresh writes the identical document and nothing is committed. The Host contract suite installs each channel on demand from the manifest into an ignored cache, proves the installed versions against the manifest, packs the plugin, and runs the execution-seam, RPC-codec, compressed Session persistence, and pathless Cordis companion contracts through a repository-external consumer that exposes the selected Host's public packages. `npm run test:host-contract -- <channel>` selects one channel, and the default selection is the blocking set. CI runs one job per channel and lets the `alpha` job report without failing the workflow. No compatibility run independently selects tags for DSH service packages, because their dist-tags need not move as one cohort.
16
+
17
+ The development-lock move is a mechanical convenience with one known limit. `host:channels:graph` re-pins the DSH declarations in `package.json` and then asks npm to resolve the graph, which works when the cohort already moved together in the lock. A development graph that still carries a large previous-release cohort (the lock holds far more `@deepseek-ai/dsh-*` packages than the manifest declares) makes npm reject the half-moved peer graph with `ERESOLVE`; the command then throws, and both `package.json` and `package-lock.json` are restored to the bytes the command found, so no partly moved graph is left behind. Because that command runs before the refresh workflow commits, a channel move waits for a maintainer to install the whole cohort and re-run the check, and `host:channels:verify` keeps reporting the drift until then. This limit touches release tooling only: the plugin's behaviour, its install proof and the compatibility claim a consumer reads are unaffected, and every gate that ships the package reads the tracked manifest rather than the development lock.
16
18
 
17
19
  Preceding public contracts remain supported where capability detection is
18
20
  unambiguous. Tools presentation is selected by the installed public schema;
@@ -18,7 +18,7 @@ The settings card belongs to plugin configuration. Current DSH renders a bundle'
18
18
 
19
19
  The settings form groups controls into core workspace (REPL syntax, state and recovery), extensions and tolerance (tool extensions, tool call tolerance), interface and resources (interface display, resource limits), and experimental (capabilities and context). Page and dialog navigation integrate the single main switch into a borderless PTC Plus title row without a duplicate enabled badge; static module captions differ from section links with icons, arrows and selected markers. The legacy disclosure card retains its first switch section. Experimental controls remain last and carry an explicit marker. The wide settings dialog presents the groups as a two-column workbench with independently scrolling navigation and content; settings wheel scrolling and section selection leave the rail and shared modal frame in place. Narrow viewports use horizontal navigation above independently scrolling content. Recovery tips and their thresholds stay together, and global binding management appears directly below its enable switch. `CONFIG_MODULES` and `CONFIG_GROUPS` own this presentation grouping and order independently of Host schema field order and defaults. The session capability-plan section inside capabilities and context shows effective selections and pending configuration changes only while capability management is enabled; disabling it cancels plan inspection and discards stale replies while manual configuration and catalog reads remain available. Capabilities and context uses four tabs for manual preferences, statistics, budgets and session plans. MCP lists group tools/resources under a server master switch; closing the server preserves child strategies and takes precedence when the frozen plan is derived. Typed catalog rows keep common preferences, manual strategies and selected frozen-session results separate, with source/strategy/common/availability filters and stable within-group sorting. Display names and source metadata belong only to the catalog RPC view: Skill provider/source and public MCP server directory and captured MCP owner sections supply evidence, while ordinary tools are attributed to the Host registry without guessing their package origin. Catalog errors preserve last-observed entries as unconfirmed, and unavailable saved preferences remain editable/removable. The core execution surface is read-only. Capability controls serialize structured selections and retain invalid numeric drafts without committing them; saving capability settings affects new sessions, while existing sessions retain their frozen plan until `/capabilities refresh` as defined by [ADR 0038](0038-freeze-session-capability-plans.md). The sparkle menu refreshes its current session through the authenticated Host command; settings require an explicit target from a read-only active-PTC identity view. This identity view does not prepare or inspect plans and remains available with capability management off so users can synchronize the disabled configuration. Reloading catalog/plans is read-only. Global binding disablement hides binding actions while settings and capability refresh remain accessible; the plugin switch and configured sparkle visibility still gate the menu. Refresh receipts preserve Host outcomes, and cancellation stops waiting without claiming rollback. `autoDescribeRunCode` defaults to true and controls only a request-bound local execution policy. When enabled, a call that omits the outer `description` uses derived arguments for local DSH validation, and the fallback summary enters presentation metadata only; when disabled, DSH validates the original arguments. Both states produce byte-stable model-visible tool schemas, order, and system sections with the required `description` declaration. Original call arguments, explicit summaries, cell source, and nested native-tool arguments remain unchanged.
20
20
 
21
- The configurable set is the Host runtime configuration plus `enabled: boolean` (default true). `enabled` is the kill switch: when false the Host keeps the settings namespace, client card, and passive message cleanup defined in [ADR 0010](0010-session-log-derived-recovery-tips.md). The next permitted accepted step may clear earlier PTC declarations. It does not install the runtime bridge, edit transport, direct-surface owner, system prompt section, tool registrations, or execution hooks, and does not read binding storage. When true the same runtime as pre-settings PTC Plus is installed. On the generations that expose a section installer, every field is live when its owner can reconcile it: the Host observes the settings scope and installs, uninstalls, or reconfigures owners without replacing existing session-bound bindings. The volatile-only settings shape exposed by both frozen Host graphs has no section installer and serves only fields marked volatile in the plugin's exported `Config`; the Host provides the form and commits volatile-only changes into the live references, notifying the owning fiber through the public `loader/volatile-update` event. The plugin subscribes and reconciles through its existing update path instead of running a per-field settings watch. Node fixes a worker's V8 `maxOldGenerationSizeMb` limit at construction, so changing that field while a session worker is active or a submitted cell is preparing to execute is rejected and the settings provider is restored to the previous value; other reconfiguration failures use the same compensation path. The TypeScript language check occurs at activation, allowing disabled settings to load on a host using another code runtime while rejecting and rolling back an enable attempt. If the settings service is absent, the Host falls back to composition config and enables the runtime.
21
+ The configurable set is the Host runtime configuration plus `enabled: boolean` (default true). `enabled` is the kill switch: when false the Host keeps the settings namespace, client card, and passive message cleanup defined in [ADR 0010](0010-session-log-derived-recovery-tips.md). The next permitted accepted step may clear earlier PTC declarations. It does not install the runtime bridge, edit transport, direct-surface owner, system prompt section, tool registrations, or execution hooks, and does not read binding storage. When true the same runtime as pre-settings PTC Plus is installed. On the generations that expose a section installer, every field is live when its owner can reconcile it: the Host observes the settings scope and installs, uninstalls, or reconfigures owners without replacing existing session-bound bindings. The volatile-only settings shape exposed by both installed channel graphs has no section installer and serves only fields marked volatile in the plugin's exported `Config`; the Host provides the form and commits volatile-only changes into the live references, notifying the owning fiber through the public `loader/volatile-update` event. The plugin subscribes and reconciles through its existing update path instead of running a per-field settings watch. Node fixes a worker's V8 `maxOldGenerationSizeMb` limit at construction, so changing that field while a session worker is active or a submitted cell is preparing to execute is rejected and the settings provider is restored to the previous value; other reconfiguration failures use the same compensation path. The TypeScript language check occurs at activation, allowing disabled settings to load on a host using another code runtime while rejecting and rolling back an enable attempt. If the settings service is absent, the Host falls back to composition config and enables the runtime.
22
22
 
23
23
  The host-only log-fact projection remains owned by the plugin/controller lifecycle when the runtime is disabled, as specified in [ADR 0032](0032-own-session-log-facts-in-a-host-only-projection.md). It supports passive message cleanup without retaining a worker, tool or default model-visible state projection.
24
24
 
@@ -43,7 +43,7 @@ namespace. Enable-failure and live-reconfiguration compensation use that same
43
43
  bound writer, not a fixed namespace substituted for the owning entry. The
44
44
  writer and page policy are released with the settings provider.
45
45
 
46
- Compatibility evidence and the consequences of removing historical branches are bounded by [Installation: Compatibility Evidence](../installation.md#compatibility-evidence). The two frozen graphs do not establish full Host coverage of the section-installer or settingsScope shapes.
46
+ Compatibility evidence and the consequences of removing historical branches are bounded by [Installation: Compatibility Evidence](../installation.md#compatibility-evidence). The two installed channel graphs do not establish full Host coverage of the section-installer or settingsScope shapes.
47
47
 
48
48
  ## Alternatives Considered
49
49
 
@@ -22,6 +22,18 @@ Journal version 6 introduced the required `userBindingsReusePolicy`: new cells w
22
22
 
23
23
  Namespace call names and selected export names must parse as exactly one Identifier whose AST name equals the complete supplied string. Whitespace, comments, binding patterns, extra declarations and escaped spellings cannot change identifier identity between validation, declarations, conflict checks, activation and recovery. Supported Unicode identifiers keep their exact spelling. Top-level entry display names remain separate from callable export names.
24
24
 
25
+ ## Supply, Adoption, Materialization and Proof
26
+
27
+ Global bindings answer four different questions, and each layer may report only its own answer. **Supply** is which entries the global configuration offers; `bindings.json` and each entry's `enabled` switch own it, and an entry disabled there is simply absent from the catalog. **Adoption** is which of those entries one session uses; the session owns it. **Materialization** is which adopted entries the worker actually installed; the activated set and its per-entry failures own it. **Proof** is what the model can check; the tool result, its journal and the diagnostics it names own that.
28
+
29
+ Adoption is a two-state decision per entry plus a default: a session uses an entry unless it declined it. "Adopted explicitly" and "never mentioned" therefore produce the same environment, so they are one state rather than two, and no sentinel value is needed. The capability pane's `automatic` is not borrowed here: that word means "the host derives this value", while a binding has no host-derived value to defer to.
30
+
31
+ Adoption can only withdraw a supplied entry. It cannot add, enable or revive one, because an entry the global configuration disabled does not exist in the catalog for a session to name; `/binding skip <id>` rejects an entry the catalog does not supply instead of recording a decision that would claim authority over supply. No rule forbids "an override revives a deleted entry", because the record has no encoding that could express it.
32
+
33
+ `/binding status|skip <id>|use <id>|reset` states the decision as one `plugin:ptc-plus` `binding-adoption` message, and the session-log projection reads the newest record back. A restart therefore reconstructs the same environment from the log; no second store or process-local map exists to migrate. A session that never states a decision receives exactly the supplied catalog, so the previous release's environment is unchanged.
34
+
35
+ Materialization remains a separate report. A declined entry is never offered to the worker, while an entry the worker could not install is named by its execution diagnostic (`PTC-R002`, and `PTC-R006` for a name that was live and then disappeared). An adoption receipt claims neither delivery nor initialization.
36
+
25
37
  ## Storage and Source Ownership
26
38
 
27
39
  The Host owns one document at `$DSH_HOME/ptc-plus/bindings.json`. The store serializes mutations, holds a file lock, compares the last-read disk bytes, requires an expected revision, and atomically replaces the file with owner-only permissions. The complete enabled declaration budget is validated before replacement; an externally written document that exceeds the same budget is treated as damaged storage. A malformed document remains an explicit error, and stale or external changes require reload instead of blind overwrite. A new Agent draft uses create-only persistence so a concurrent entry with the same ID cannot be replaced.
@@ -14,7 +14,7 @@ The session kernel runs inside a plugin-owned helper process that the host may k
14
14
 
15
15
  Caller and helper environment boundaries are explicit. Each creation site projects the host environment with `normalizeWorkerEnvironment`, which keeps the user-visible contract and strips host-only instrumentation such as `NODE_V8_COVERAGE`. `IsolatedWorker` snapshots that projected object before `child_process.fork`; the fork call may mutate its own env object by copying `NODE_V8_COVERAGE` into the helper, so the pre-fork snapshot is what `kernel-child.js` passes to `new Worker({ env: ... })`. When the Host executable is Electron, `IsolatedWorker` overlays `ELECTRON_RUN_AS_NODE=1` only on the fork environment so the packaged executable starts the helper module instead of reopening the desktop application. The inner user Worker and the child processes it creates still receive the original projected snapshot, without that helper-only override. An explicit transport/coverage test can opt into instrumentation to verify worker branches; production paths do not change the user environment to collect coverage.
16
16
 
17
- The helper answers an on-demand utilization-sample request from its own loop, and `IsolatedWorker` exposes `sampleUtilization()`. `SessionCellExecutor` starts the wall bound at request submission, awaits that fresh baseline, then starts the compute interval before it posts the prepare or run frame, so work completed before submission is excluded while prepare blocking remains counted. The exception owned by [ADR 0024](0024-repl-console-observation.md) is a confirmed unfinished observation on the same worker: the executor may post prepare before starting budgets so the observation can finish, and the matching observation-completion/readiness acknowledgement starts them. A sample that times out, cannot be sent, disconnects, or exits is a bounded worker-exit failure: pending requests are cleared and no prepare/run frame is dispatched. The ready handshake still contributes an additional startup sample, but it is not the per-cell baseline and it does not reset or defer budgets.
17
+ The helper answers an on-demand utilization-sample request from its own loop, and `IsolatedWorker` exposes `sampleUtilization()`. `SessionCellExecutor` starts the wall bound when the cell's budgets start, after preparation and worker acquisition, awaits that fresh baseline, then starts the compute interval before it posts the run frame, so work completed before that point is excluded while prepare blocking remains counted. The exception owned by [ADR 0024](0024-repl-console-observation.md) is a confirmed unfinished observation on the same worker: the executor may post prepare before starting budgets so the observation can finish, and the matching observation-completion/readiness acknowledgement starts them. The sampling bound is decided only after the helper's channel has been read out, not when the host's own clock reaches it: a blocked host runs expired timers before it reads its IPC pipe, Node delivers one helper message per event-loop turn, and the answer follows every message the helper wrote earlier, so the transport concludes only after a turn delivers nothing. A read-out bound ends a backlog at or above that many messages and reports only that no answer was observed. A sample that cannot be sent, disconnects or exits is a bounded worker-exit failure: pending requests are cleared, no run frame is dispatched, and the worker is reset through the owner's close path, which leaves a refused reclamation visible on the failed cell result. Any other sample the transport could not observe is reported as that fact alone and decided one level up, because the transport owns the observation and the kernel owns the worker: the cell settles under `budget-unavailable` with a no-op journal since nothing was dispatched, and the live worker keeps its timeline and bindings until the configured `unmeasuredCellLimit` consecutive cells reach the dropping threshold. The wall and compute bounds behave the same way when they expire before the run frame is posted, because the work they measured belongs to earlier cells; after that frame they keep their existing discard and reset route. One unmeasured cell is not evidence about the worker, and the configured `unmeasuredCellLimit` (default 2) is the consecutive cell that drops the live worker while keeping the proved frontier; the next cell replays that frontier. A stopped helper's exit is confirmed within `workerReclaimMs` (default 5000 ms) measured from the close start, with the release grace capped so the kill keeps a confirmation window; the output pipes then get the same bound from their own start. A replay pass that ends because a recorded cell could not be observed likewise keeps the frontier and retries, while a replay that fails for any other reason keeps the existing containment; the reset such a pass performs goes through the same reclamation owner, so a refused stop stays recorded, blocks later computation until it is handled, and is reported on the cell that ends. A cell blocked by that recorded failure is reported as blocked: it never contracts or blames the recorded frontier. The ready handshake still contributes an additional startup sample, but it is not the per-cell baseline and it does not reset or defer budgets.
18
18
 
19
19
  Helper stdout and stderr are part of cell settlement, not unowned process noise. For every run, the Host chooses a nonce and arms one output round before posting source. The kernel writes nonce-bearing start markers to both native descriptors and waits for the Host to observe both before executing the cell. It writes matching end markers after evaluation, pending program calls and value encoding settle, but before posting `done`. The Host releases `done` only after both end markers arrive. Bytes between those fences, including direct fd 1/2 writes and inherited child stdio, belong to that physical run and share its aggregate output budget. Structured console logs retain their own order; stdout and stderr each retain pipe order, but the merged presentation does not claim a total chronology across the independent control and descriptor channels.
20
20
 
@@ -32,6 +32,12 @@ Bytes before a new start fence prove that a settled run still produced descripto
32
32
 
33
33
  **Move all user computation into a general child-process sandbox or permission layer.** That would replace DSH authority and sandbox decisions with a second system, which the project explicitly does not own.
34
34
 
35
+ **Treat every unanswered sample as a worker loss.** An expired deadline only proves that the host was unable to observe an answer, and reclaiming the worker then destroys bindings the cell never touched. Reclamation stays limited to a helper that leaves consecutive samples unanswered with no successful sample between them.
36
+
37
+ **Read one already-queued message before concluding.** Node delivers one helper message per event-loop turn, so the answer can sit behind messages the helper wrote earlier; the transport therefore reads until a turn delivers nothing.
38
+
39
+ **Reuse the helper's last periodic sample as the baseline.** It can predate work that already finished before submission, so the first cell after a completed background callback would be charged for that earlier work, which `test/isolated-budget-baseline.test.js` covers.
40
+
35
41
  ## Consequences
36
42
 
37
43
  A synchronously stuck kernel is reclaimed by killing a plugin-owned helper process; DSH survives and observes a real child exit, output close, and cleanup outcome. This containment keeps session-worker teardown out of DSH, but it does not repair, characterize, or claim the same cause for every upstream Node teardown path.
@@ -39,3 +45,7 @@ A synchronously stuck kernel is reclaimed by killing a plugin-owned helper proce
39
45
  The transport gains one process and an IPC hop. Ports, shared memory, and transferred buffers cannot cross the process boundary, so they stay inside the helper or become plain data; output continues over the helper's stdio pipes. The helper's own `execArgv` is emptied so it does not inherit host startup arguments.
40
46
 
41
47
  Environment provenance is explicit in code: the user Worker receives the caller's projected environment, while coverage instrumentation and an Electron Host's Node startup override stay on the helper side. A reclamation refusal stays owned and observable instead of being reported as success or surfacing as an unhandled rejection.
48
+
49
+ An unmeasured cell keeps the state earlier cells proved. A derived `edit_run_code` execution that is never measured publishes an unexecuted settlement, which proves the accepted edit without claiming state: the enclosing root cell keeps its own journal and stays provable, the revision publishes no binding and no editable source, and recovery folds no derived state. Only the unmeasured route qualifies; a rejected source, an aborted cell or a lost helper keep the rule that a derived edit journal must record an execution.
50
+
51
+ The settlement suite proves the unmeasured routes, the consecutive keep/drop, the counter reset, the budget release, both cancellation-listener releases, the replay retry, the recovery notice of a replay pass that contracts a frontier and then ends early, and the no-op state effect. It does not separately prove the transport's handling of a message the parser still holds in parts ahead of an answer, nor a replay pass whose recovery cannot contract and is then followed by a blocked current cell; both are covered by their normal-path evidence and by inspection.
@@ -163,7 +163,7 @@ escalation it thinks it is offering.
163
163
  ## Consequences
164
164
 
165
165
  The default bundle and current official base profile use the `ptcRuntime`
166
- resolve/run shape, also exercised by the frozen Host graphs. Explicit
166
+ resolve/run shape, also exercised by the installed channel graphs. Explicit
167
167
  `codeRuntime` request-only compositions retain public Loader fixture coverage
168
168
  for request identity, replacement and bundle removal. The evidence boundary is
169
169
  recorded in [Installation](../installation.md#compatibility-evidence).
@@ -12,7 +12,7 @@ PTC Plus publishes its Remote services through DSH's TYPERT registry, and that r
12
12
 
13
13
  Each generation validates only the member it owns and ignores the other, so one published contract serves whichever shape the installed host implements. Compatibility follows the live acceptance behavior of the registry rather than a release version, and the decoder is published once, so a host that reads both members cannot obtain two different decoders for one wire member.
14
14
 
15
- CI registers the packed contract with the installed host's own registry from the frozen official `latest` and `next` graphs (`scripts/dsh-rpc-contract-smoke.mjs`). Both frozen graphs read `create()`; they do not exercise a schema-only Host. Unit fixtures cover `schema.parse`, not a complete historical Host installation. The evidence boundary is [Installation: Compatibility Evidence](../installation.md#compatibility-evidence); passing two graphs must not be reported as packaging verification of both codec shapes.
15
+ CI registers the packed contract with the installed host's own registry from the blocking host channels it resolves on demand from `compat/channels.json` (`scripts/dsh-rpc-contract-smoke.mjs`). Both installed channels read `create()`; they do not exercise a schema-only Host. Unit fixtures cover `schema.parse`, not a complete historical Host installation. The evidence boundary is [Installation: Compatibility Evidence](../installation.md#compatibility-evidence); passing two graphs must not be reported as packaging verification of both codec shapes.
16
16
 
17
17
  ## Alternatives Considered
18
18
 
@@ -24,4 +24,4 @@ CI registers the packed contract with the installed host's own registry from the
24
24
 
25
25
  ## Consequences
26
26
 
27
- One plugin build carries both the historical schema-based and factory-based codec shapes, and the compatibility rule lives in a single module with the decoder published once. A future generation that requires another member, or that rejects a codec carrying a member it does not know, needs this owner updated; the regression test is CI's registration against the installed registry, which fails on the frozen Host graph that changed instead of on the user's machine. The decoder keeps the project's maintained schema runtime, so a host that validates payloads itself still evaluates the same semantics the plugin's own tests exercise through both members.
27
+ One plugin build carries both the historical schema-based and factory-based codec shapes, and the compatibility rule lives in a single module with the decoder published once. A future generation that requires another member, or that rejects a codec carrying a member it does not know, needs this owner updated; the regression test is CI's registration against the installed registry, which fails on the installed channel host graph that changed instead of on the user's machine. The decoder keeps the project's maintained schema runtime, so a host that validates payloads itself still evaluates the same semantics the plugin's own tests exercise through both members.
@@ -16,12 +16,42 @@ The log-fact owner belongs to the plugin/controller lifecycle rather than an ena
16
16
 
17
17
  Result republication preserves the original settlement position and normalized evidence. A content-only prune clone, including a clone of another clone, is not a new execution, edit target, success or claim settlement. If the original result is absent, the original call position bounds the newly reconstructed chronology; later executions remain later. Tip identities retain their delivery positions so discovering an earlier success does not erase warnings delivered afterward.
18
18
 
19
- Projection checkpoints are fold shortcuts only. State version 8 retains each settlement's independent publication index, first-observed index, ordinary/reconstructed classification and normalized run evidence, including ordinary runs without journals. Ordinary-result deduplication retains the first observation's call, event sequence and timeline position; admission requires matching ordinary settlement evidence or an identity-conflict clear, and every ordinary settlement requires its observation. It also retains the processed timeline position and ordered selection clears for turn resets and invalidations. The current frontier must agree with that retained lifecycle history and remain within the processed cut. Latest and editable runs are derived from the newest current-scope publication beyond that frontier; the success index is derived from retained successful publications. Edit targets and claim eligibility at dispatch are reconstructed from first-observed settlements and clears, using the same selection and target capture rules as the live transition. Their source must match the eligible ordinary or derived publication, and both current claim tables must match the reconstructed lifecycle. A late prune reconstruction cannot establish a target for an earlier dispatch. Selected fields and redundant target copies cannot certify their own provenance or chronology, and clones cannot revive a selection across an invalidation frontier. Normalized event facts retain call identity/source, result identity digests and formal source/replacement relations, public prune and turn inputs, journal presence, normalized settlement and recovery-boundary outcomes, and validated rewrites. They retain no result content. Checkpoint admission reconstructs the complete timeline with the same single-event transition used by live folding, then compares every materialized field. Journal/derived/binding facts, ordinary observations, settlements, selection, claims, transcript counters, unavailable evidence, pending/seen identities and the active prune window therefore share one lifecycle interpretation. Typed payload validation and the existing journal/derived/boundary owners validate the retained normalized inputs. The projection also retains presentation input facts: public append/replacement intents, context-step source categories and recognized runtime-message protocol inputs. Ordinary user and assistant content is omitted. These facts align with timeline event identity and drive the same projection transition at admission. The complete reconstructed projection must agree with the checkpoint, including ordered surface nodes, visible records/facts, message indexes, context steps and notice history. A historical catalog record therefore cannot certify its own visibility, and removing all visibility caches cannot suppress an actual append. Prompt consumers continue reading incremental state without raw-log scans. Missing settlements or valid-shaped journal divergences cannot be hidden by deleting their dependent caches. The timeline representation owner validates these relations together with typed table payloads, call/entry identities, normalized journals, derived runs and transcript counts before projection admission. Obsolete v1–v7 state requires refolding the complete log; a nonzero-start suffix alone is rejected with a seq-0 reread diagnostic. The Host owns watermark validation, suffix restore, fork inheritance and refolding. Raw journal ancestry and exact model-visible source relations still own recovery evidence. UI facts, cached state and matching recorded completions are not substitutes.
19
+ Projection checkpoints are fold shortcuts only. State version 10 stores the executable call events exactly once: `executableCalls` owns the event, the ordered `calls` list keeps only canonical sequence numbers, and an ordinary-result observation keeps the call sequence plus a sha256 digest of that stored event instead of a second copy. A digest is compared where an inline copy used to be compared, so the admission decision is unchanged while the checkpoint no longer serializes each call once per referencing table. State version 9 and earlier refold from the session log, because the shape, not just the content, changed. State version 8 retains each settlement's independent publication index, first-observed index, ordinary/reconstructed classification and normalized run evidence, including ordinary runs without journals. Ordinary-result deduplication retains the first observation's call, event sequence and timeline position; admission requires matching ordinary settlement evidence or an identity-conflict clear, and every ordinary settlement requires its observation. It also retains the processed timeline position and ordered selection clears for turn resets and invalidations. The current frontier must agree with that retained lifecycle history and remain within the processed cut. Latest and editable runs are derived from the newest current-scope publication beyond that frontier; the success index is derived from retained successful publications. Edit targets and claim eligibility at dispatch are reconstructed from first-observed settlements and clears, using the same selection and target capture rules as the live transition. Their source must match the eligible ordinary or derived publication, and both current claim tables must match the reconstructed lifecycle. A late prune reconstruction cannot establish a target for an earlier dispatch. Selected fields and redundant target copies cannot certify their own provenance or chronology, and clones cannot revive a selection across an invalidation frontier. Normalized event facts retain call identity/source, result identity digests and formal source/replacement relations, public prune and turn inputs, journal presence, normalized settlement and recovery-boundary outcomes, and validated rewrites. They retain no result content. One limit remains: a settlement still carries the argument text of its own call, because the published run needs the value (its source) rather than a digest, and event facts for a tool call still carry that call payload so admission can replay the same transition. This change bounds the number of copies, not the per-call payload. Checkpoint admission reconstructs the timeline with the same single-event transition used by live folding while the retained payload stays inside a fixed budget, then compares every materialized field; beyond that budget it proves digest-bound continuity and frontier agreement instead of byte-for-byte replay equality of argument text. Every retained timeline fact carries a sha256 digest over its own serialized bytes, so a payload edit is rejected without folding the session a second time, and a checkpoint written before digests existed is still admissible (the host refolds it on the state-version mismatch). State version 11 adds that per-fact digest; version 10 stored the executable call events exactly once and kept canonical sequence numbers in calls. Journal/derived/binding facts, ordinary observations, settlements, selection, claims, transcript counters, unavailable evidence, pending/seen identities and the active prune window therefore share one lifecycle interpretation. Typed payload validation and the existing journal/derived/boundary owners validate the retained normalized inputs. The projection also retains presentation input facts: public append/replacement intents, context-step source categories and recognized runtime-message protocol inputs. Ordinary user and assistant content is omitted. These facts align with timeline event identity and drive the same projection transition at admission. The complete reconstructed projection must agree with the checkpoint, including ordered surface nodes, visible records/facts, message indexes, context steps and notice history. A historical catalog record therefore cannot certify its own visibility, and removing all visibility caches cannot suppress an actual append. Prompt consumers continue reading incremental state without raw-log scans. Missing settlements or valid-shaped journal divergences cannot be hidden by deleting their dependent caches. The timeline representation owner validates these relations together with typed table payloads, call/entry identities, normalized journals, derived runs and transcript counts before projection admission. Obsolete v1–v7 state requires refolding the complete log; a nonzero-start suffix alone is rejected with a seq-0 reread diagnostic. The Host owns watermark validation, suffix restore, fork inheritance and refolding. Raw journal ancestry and exact model-visible source relations still own recovery evidence. UI facts, cached state and matching recorded completions are not substitutes.
20
20
 
21
21
  Submitting a cell reserves its memory configuration with the SessionRuntime owner before the first asynchronous observation on either the run or edit entry. The bridge and derived edit carry that same submission and its captured cut into execution rather than creating a second admission after reading. Runtime or session disposal invalidates that preparation, and an invalidated preparation cannot create a kernel or worker after the reader resumes. Reader failure releases the reservation; a genuinely new submission can use a fresh environment. Caller cancellation with an identified cut is settled by the existing cell owner as a no-op without starting a worker; it retains the caller's reason and does not create an unexplained recovery boundary.
22
22
 
23
+ Projection payloads are retained by two cumulative bounds (`internal/projection-retention.js`): the newest `retentionPayloadRuns` settlements keep their payload while the retained payload total stays under `retentionPayloadBytes`, and a cluster beyond either bound keeps its identity — the settlement, its executable call, its ordinary observation, its normalized result and its call/result event facts — while the argument text of that cluster becomes digests (`argsDigest`/`argsBytes` in the journal call records, `argumentsDigest`/`argumentsBytes` in the call events). A single cell larger than the byte budget is digested the same way rather than kept whole, and the edit path refolds it from the log. Only a cluster beyond `retentionClusters` (default 8; follows `retentionPayloadRuns` when unset; `0` means unlimited) is folded away: the state then keeps one chained `retentionSummary {fromSeq, toSeq, count, chainDigest, entries}` (state version 13), where `entries` keeps one compact `{resultSeq, callSeq, argumentsDigest}` record per folded cluster. A per-settlement digest stub was the previous shape and is not enough: 450 stubs cost roughly 1.7 MB before any payload, so the checkpoint still grew with how many cells the session ran. The byte cap is cumulative over the retained set, which is a function of the window rather than of the session: a session writing 400 KB per cell keeps only as many clusters textual as the budget allows (8 MiB / 400 KB ≈ 20, fewer once the selected run's copies are counted), so the checkpoint stays in the same order of magnitude as the small-cell rows instead of growing with what the session wrote.
24
+
25
+ Recovery tips stay silent when the newest cell alone exceeds the byte budget and its payload is digested: a tip is generated from journal diagnostics, and a cell whose text was digested has none, so emitting one would be indistinguishable from a real failure. The runtime continuation feedback carries that notice instead (`最新单元格超出保留预算,已从检查点移除` / `The newest cell exceeded the retention budget and was removed from the checkpoint`, `HOT_CELL_DROPPED` in `internal/projection-retention.js`). Do not turn that silence into a tip.
26
+
27
+ Admission receives no session log, so it cannot recompute the summary from the events; it proves what the records can prove: no settlement exists at or below `toSeq`, every retained settlement keeps its call entry, the record list has one entry per counted cluster, is ordered by result sequence, spans exactly `fromSeq`…`toSeq`, and reproduces `chainDigest`. A rolling digest alone was forgeable — editing the digest, the count or the first sequence passed — and a test pins that it no longer does. The claim that a record still describes the cluster the log holds is checked where the two sides meet: when a dropped cell is refolded for an edit, `refoldEditableSettlement` compares the refolded settlement's record with the stored one and refuses the edit with `保留摘要与被重折的结算不一致` / `Retention summary disagrees with the refolded settlement` when they differ. That is the stated, weaker claim for compacted checkpoints — digest-bound continuity, frontier agreement and a record that matches the log at refold time, not byte-for-byte replay equality of the dropped payloads. Uncompacted checkpoints keep the full replay equality, and a checkpoint written before version 13 refolds from the log.
28
+
29
+ The newest entry is not exempt from the byte bound. A single cell larger than the whole byte budget is digested like any other: the bound wins over keeping the hot cell's text, so a `run_code` call carrying megabytes of code cannot re-inflate the checkpoint. Its cluster, identity and result metadata stay, the edit path refolds it from the log, and the cached `latestRun`/`editableRun` are cleared and `hotCellDropped` is set so no consumer claims to hold text the checkpoint no longer carries. Measured with the shipped defaults and a single 2 MB cell: the checkpoint stays inside the documented bound, `hotCellDropped` is set, and a full-retention fold recovers the arguments exactly.
30
+
31
+ Digesting a cluster's payload is not permanent, and folding one away is not either. `withFullRetention` refolds the same session-log slice with pruning disabled, and the edit path uses it to recover the text of a cell the window digested or folded, so an edit or republication that needs an old cell still succeeds while the log can supply it. Only when the log itself cannot supply the cell do consumers report `retention-pruned` with the edit or republication wording above.
32
+
33
+ The window is enabled by default and is user configuration: `retentionPayloadRuns` (8), `retentionPayloadBytes` (8 MiB, maximum 16 MiB) and `retentionClusters` (defaults to the retained-cells window, so 8; `0` means unlimited). A cluster beyond the count window or the payload budget keeps its identity and has its argument text digested; only a cluster beyond `retentionClusters` is folded into the summary, which is why the default keeps the checkpoint from growing with session length. The `DSH_PTC_RETENTION_*` environment levers may only tighten a configured value — they exist for tests and can never widen the bound.
34
+
35
+ The count and byte floors are `8` cells and `1 MiB`, because a window smaller than the cells a fold has in flight cannot keep the stored publication and the replay equal: at 2 cells the recovery suites reject their own checkpoint, at 8 every recovery suite passes at both ends of the range. A request outside the declared bounds is not clamped to the floor: it falls back to the shipped default, because a floor-sized window is itself unprovable and a default is the only value the recovery suites prove.
36
+
37
+ Measured with `G:\Temp\measure-projection.mjs` against this tree, with the harness's own settlement counts (`counts.settlements`, `counts.summaryCount`); the retained column is the payload window (`retentionPayloadRuns`, default 8), not a settlement count:
38
+
39
+ | Session (`RUNS` × `CODE_BYTES` + `WRITE_BYTES`) | Checkpoint | Retained clusters | Folded clusters |
40
+ | --- | --- | --- | --- |
41
+ | 450 × 20 KB code + 20 KB write | 622,989 B | 8 | 442 |
42
+ | 900 × 20 KB code + 20 KB write | 827,206 B | 8 | 892 |
43
+ | 450 × 400 KB code + 400 KB write | 4,222,998 B | 8 | 442 |
44
+
45
+ A retained cell's argument text exists more than once. `executableCalls` owns the call event, the `tool/call` event fact keeps only its digest and byte length, the settlement keeps its own journal text, and the selected run objects (`latestRun`, `editableRun`) embed the parsed arguments, the source and the journal of the cell they name. `retentionPayloadBytes` bounds the *retained payload* — the settlement and executable copies — not the serialized checkpoint: the run copies are stored per selection and are not charged to that budget, because charging them would have to happen at a prune point where the payload is not yet attached, and moving the prune later would digest the very cell the user is looking at. The serialized total can therefore approach roughly twice the budget plus the per-cluster metadata, and that is the shipped bound. Measured: the two rows above, plus 450 × 400 KB ≈ 4.2 MB with an 8 MiB budget and an 8-cluster window, and about 1.7 KB of per-cluster metadata beyond the window.
46
+
47
+ The byte budget binds before the count window does for large cells (8 MiB / 400 KB ≈ 20 clusters, and fewer once the selected run's copies are counted), so the checkpoint is a function of the window rather than of what the session wrote. The residue that still grows with session length is not payload: every folded cluster leaves one ~150 B summary record (442 of them at 450 cells, 892 at 900, which is 67 KB → 136 KB) and the per-event identity tables grow with the event count (`seenCallIds` 21 KB → 42 KB over the same range), which is why 900 cells cost 33% more than 450 rather than twice as much. `retained + summaryCount == settlements` is expressed by the digest-in-place policy: a cluster inside the window keeps its records, a folded one keeps only its summary record, and no settlement is silently dropped without either.
48
+
23
49
  ## Consequences
24
50
 
25
51
  pre-step and assembly read current projected facts rather than materializing history. Recovery and authoring receipt operations are explicit asynchronous reader consumers. Honest checkpoint restore and fork must preserve the same timeline, edit and runtime-message decisions as batch replay. Tests cover serialized transition boundaries, duplicate identities, prune replacement, observation/surface races, real SessionStore/query/registry composition and long unrelated histories.
26
52
 
27
53
  This migration does not change tool names, journal generations, Host policy authority, output governance or the selected PTC language contract. Public execution-provider composition is owned by [ADR 0028](0028-attach-to-the-host-ptc-execution-seam.md), not this projection. Successful-result metadata and two-tool collapse integration remain separate boundaries.
54
+
55
+ ## Release coverage exemptions (0.5.2)
56
+
57
+ Four recovery arms in `internal/session-runtime.js` carry `c8 ignore` directives so the release gate keeps its 100% lines and functions rule for every other line: the `recoverJournal` catch body, the volatile-suffix notice, the first-fold recovery boundary, the `alreadyReported` notice arm and the `droppedNames` notice arm. Each directive names the condition that keeps it out of a fixture today. Exit condition for 0.5.3: either a fixture reaches the arm, or its unreachability is proven by the fold arithmetic and the arm is deleted with that proof. Non-deterministic residual gaps observed across two identical full-coverage runs mean a global 100% floor cannot be a release stopping point until those long-standing error branches are addressed; the exemptions are recorded per line rather than by lowering the threshold.
@@ -6,15 +6,15 @@ DSH added an opt-in official Node execution mode (`executionMode: persistent-in-
6
6
 
7
7
  ## Decision
8
8
 
9
- `internal/execution-seam-compat.js` and `internal/execution-provider-owner.js` forward `programFormat` to the live original generation and `release(owner)` to the originals that served that owner. These describe the provider that actually performs an execution: owner-keyed retained environments and the executed source format belong to the original, which still serves non-session consumers while the plugin owns session cells, and a bounded generation that lacks the members falls back to `'function-body'` and a resolved no-op. Execution retirement does not retire cleanup: the composition retains weak owner-to-original provenance for dispatched `stateOwner` programs, so Agent cleanup through a captured adapter still reaches a surviving original after bundle removal or source replacement. Successful cleanup removes that original from the owner's pending set; failed cleanup remains available for explicit retry, with synchronous and asynchronous failures observable. An owner without recorded dispatch delegates to the live original, or receives a resolved no-op if execution is unavailable. `executionInstructions`, `sandboxMode` and `timeout` keep their existing withholding policy while PTC Plus owns session execution, because those describe the session kernel ([ADR 0028](0028-attach-to-the-host-ptc-execution-seam.md)).
9
+ `internal/execution-seam-compat.js` and `internal/execution-provider-owner.js` forward `programFormat` and the `retainsState` declaration to the live original generation and `release(owner)` to the originals that served that owner. The composed provider answers `retainsState` as the fact about itself: while an installed adapter runs the program it retains a per-session environment, so it reports `true` whatever the original would do; `seam.originalRetainsState` carries the original declaration for the ownership decision. These describe the provider that actually performs an execution: owner-keyed retained environments and the executed source format belong to the original, which still serves non-session consumers while the plugin owns session cells, and a bounded generation that lacks the members falls back to `'function-body'` and a resolved no-op. Execution retirement does not retire cleanup: the composition retains weak owner-to-original provenance for dispatched `stateOwner` programs, so Agent cleanup through a captured adapter still reaches a surviving original after bundle removal or source replacement. Successful cleanup removes that original from the owner's pending set; failed cleanup remains available for explicit retry, with synchronous and asynchronous failures observable. An owner without recorded dispatch delegates to the live original, or receives a resolved no-op if execution is unavailable. `executionInstructions`, `sandboxMode` and `timeout` keep their existing withholding policy while PTC Plus owns session execution, because those describe the session kernel ([ADR 0028](0028-attach-to-the-host-ptc-execution-seam.md)).
10
10
 
11
- `officialReplPriority` (default `false`, State and recovery) selects who owns PTC session execution when the official persistent REPL is live. The decision reads the live capability — `provider.programFormat === 'repl'` — rather than a release number, and is re-evaluated on every configuration and host source change. Execution ownership is an attachment shape: `owned` installs the session runtime, `deferred` installs `internal/official-repl-priority.js`'s runtime record, which owns no execution resources and answers the attachment contract with empty contexts. Changing the shape retires the current attachment, disposing its live kernels, and installs the other shape before later cells; no committed cell mixes the two contracts. While deferred, `run_code` and its descriptors stay with the official provider and the plugin's REPL guidance, session-state semantics, recovery, editor tool, capability SDK, REPL observation and binding workbench are suspended with the attachment, while the settings card, enablement and stored global bindings stay available so the switch can be reversed.
11
+ `officialReplPriority` (default `false`, State and recovery) selects who owns PTC session execution when the official provider retains a program environment per state owner. The decision reads the live capability — the provider's `retainsState` declaration, or the retained source form (`programFormat === 'repl'`) on a generation that predates that declaration — rather than a release number, and is re-evaluated on every configuration and host source change. Retention, not the source form, answers the question: one host line retains state for `script` programs and another runs the same form in a fresh process. Execution ownership is an attachment shape: `owned` installs the session runtime, `deferred` installs `internal/official-repl-priority.js`'s runtime record, which owns no execution resources and answers the attachment contract with empty contexts. Changing the shape retires the current attachment, disposing its live kernels, and installs the other shape before later cells; no committed cell mixes the two contracts. While deferred, `run_code` and its descriptors stay with the official provider and the plugin's REPL guidance, session-state semantics, recovery, editor tool, capability SDK, REPL observation and binding workbench are suspended with the attachment, while the settings card, enablement and stored global bindings stay available so the switch can be reversed.
12
12
 
13
13
  Cells executed under the official mode produce no plugin journal records. They are an unrecorded execution interval: recovery never replays them, and they do not extend plugin state as recovery evidence.
14
14
 
15
15
  ## Alternatives considered
16
16
 
17
- **Gate on the DSH release or on reading the original row's `executionMode` configuration.** The mode is a live runtime capability and a configuration fact outside the plugin's public contract; `programFormat` is the provider-owned proof that a REPL actually retains state, and it degrades safely on older generations that lack it.
17
+ **Gate on the DSH release or on reading the original row's `executionMode` configuration.** The mode is a live runtime capability and a configuration fact outside the plugin's public contract; the provider's own retention declaration is proof that its environment survives between runs, and on older generations that lack the declaration the retained source form is the only evidence they publish.
18
18
 
19
19
  **Keep owning execution unconditionally and only add the missing seam members.** That repairs disposal, but it silently ignores an explicit official mode, cannot honor a user's choice to run the official semantics, and keeps two competing REPL contracts alive for the same session surface.
20
20
 
@@ -26,7 +26,7 @@ Cells executed under the official mode produce no plugin journal records. They a
26
26
 
27
27
  ## Consequences
28
28
 
29
- Agent disposal on the current official host releases cleanly, and both the format and the owner release reach the generation that owns the environment.
29
+ Agent disposal on the current official host releases cleanly, and the source form, the retention declaration and the owner release reach the generation that owns the environment.
30
30
 
31
31
  Leaving plugin ownership disposes its live kernels, and plugin-mode state contraction follows the existing disposal rules. Official in-memory state belongs to DSH's Agent/provider lifecycle and may remain available when priority is switched back to the official provider; the deferred attachment does not clear it. State built during an official interlude is never replayed or claimed by the plugin. After returning to plugin ownership, the session continues from plugin-verifiable evidence only.
32
32
 
@@ -0,0 +1,38 @@
1
+ # 0045 Customize model-visible prompt text through one registry
2
+
3
+ ## Problem
4
+
5
+ The plugin authors model-visible text in a dozen places: the stable REPL guidance, the `run_code` description, the `edit_run_code` schema, the capability plan's navigation and category lines, the recovery and rewrite notices, the binding catalog, and the budget and diagnostic wording. Every one of them is behaviourally load-bearing, yet users can only choose policy modes in 能力与上下文; they cannot reword a single sentence. Requests to add project conventions, shorten recovery prose, or translate an instruction therefore end in forking the plugin.
6
+
7
+ Two naive fixes are refused. Replacing the whole system prompt removes the sentences that keep the tool contract executable and makes a failure impossible to attribute. Letting users edit diagnostics or message envelopes breaks the recognizers that decide which notice to deliver and which snapshot to reconcile.
8
+
9
+ ## Decision
10
+
11
+ One registry of anchors and slots, one settings document, one total resolver.
12
+
13
+ - `internal/prompt-text.js` owns every anchor: an id, a delivery channel, a presence condition, an owner class and its slots. An anchor is what a composer delivers; a slot is what a user edits, and it carries a kind (`literal`, `aggregate` or `projected`), an owner (`A` editable, `B` code-owned, `C` owned by another surface), a reason code when read-only, an `omittable` flag and the byte-identical default literal. Composers import their default from this registry instead of declaring it, so the default text has one owner.
14
+ - An `aggregate` slot is a frozen item set: its container owns no bytes and holds the item slots, so item text stays editable while adding or removing an item — which would move the neighbours' bytes and connectors — does not exist as an operation.
15
+ - The `promptText` setting is a `ptc-prompt-text/v1` JSON document mapping slot id to replacement text. An absent id keeps the built-in text; an empty value omits an `omittable` slot and is rejected for a required one.
16
+ - Limits are measured in effective UTF-8 bytes: 4096 per slot, 16384 per anchor and 65536 for the document. They are enforced twice. `validatePromptTextDocument` runs on the save path and in `resolveConfig` and rejects the offending slot by id; `resolvePromptText` never throws, re-measures the same ceilings on the effective bytes of a document that never passed validation (the settings draft is such a caller), drops an offending override back to its default and reports `slot-exceeds-limit`, `anchor-exceeds-limit`, `document-exceeds-limit`, `missing-hole`, `not-writable` or `required-slot-empty`. A projected slot must keep exactly one of each registered hole at render time as well.
17
+ - The six read-only reason codes are `fact-derived`, `aggregate-frozen`, `protocol-exact-text`, `protocol-structural`, `shadows-host` and `other-owner`. An override of a read-only slot is refused rather than silently ignored.
18
+ - An unknown slot id stays in the document, is excluded from rendering and is reported, so a document written by another plugin version still loads.
19
+ - Five channels are wired: `prompt` (stable guidance and the `run_code` schema, rebuilt on the next request), `install` (`edit_run_code`, resolved when an agent installs the tool surface), `plan` (navigation, five category lines and the overage sentence, delivered to a new session or an explicit refresh and recorded by value as `ptc-capability-environment/v5`), `runtime` (binding catalog, recovery snapshot prefixes, tip bodies) and `result` (rewrite descriptions, diagnostics prose, the output-limit envelope).
20
+ - Recognition never depends on the sentence being recognized. A catalog message records whether it delivered an empty catalog in its own source, and a record without that flag is compared against frozen historical constants, so `catalog.empty` is editable without making a customized sentence unrecognizable. Snapshot prefixes and message identities stay code-owned.
21
+ - Derived policy sentences (variable redeclaration, module syntax, replay, Cordis recovery) are generated by code, marked `fact-derived` and are not editable. Diagnostics codes and their structure, tool names, `required` lists and enums stay protocol-owned.
22
+
23
+ ## Consequences
24
+
25
+ An empty or absent override reproduces today's prompt byte for byte, so the existing guidance contract and keyless prefix tests remain the regression oracle. An override changes the cached prompt prefix from its first changed token on the next request; that is the accepted cost of a deliberate configuration change, not a defect.
26
+
27
+ Each channel's default is delivered byte-identically when the document is empty, and every channel has a test that pins that equality. `edit_run_code` schema text resolves at install time, so its overrides apply to a new session and never mutate a live schema mid-session. The plan text is recorded by value in the capability environment, so replay never depends on current settings.
28
+
29
+ User text is unvalidated prose. It can claim a capability the profile closed or describe the wrong execution world, and the panel can only warn. The bound is structural: read-only slots stay owned by code, the surface gate fails when a captured request contains text no anchor claims, and the coverage test fails when a delivered literal is missing from the registry, so "every fixed prompt is customizable" stays checkable.
30
+
31
+ ## Alternatives considered
32
+
33
+ - Replace the whole system prompt. Rejected: it discards the sentences that keep `run_code` calls legal and destroys failure attribution.
34
+ - Flat per-asset rows with one ceiling each. Rejected: a single delivered paragraph is composed of several slots, so a per-asset ceiling cannot bound what the model receives, and a mixed anchor has no way to say which part is editable.
35
+ - Per-anchor slots with append/inherit modes. Rejected: append needs a placement rule relative to the existing paragraph order, and inherit gives two owners to one span.
36
+ - Typed holes (`{cellConventions}`) inside editable prose. Rejected: a second input path whose result cannot be validated against the configuration that generates the clause.
37
+ - A revision counter beside the recorded text. Rejected: a second source of truth that can disagree with the bytes actually delivered.
38
+ - Carrying prompt text inside named capability profiles. Rejected: switching a disclosure profile would silently rewrite the prompt prefix.
@@ -0,0 +1,35 @@
1
+ # Gate the Commit With the Semantic-Evidence Tier
2
+
3
+ ## Problem
4
+
5
+ `npm run check` is the pre-commit gate: it classifies the candidate, runs the appropriate verification, and binds a source proof to the tree. Its full branch ran two complete inventories at once — the whole ordinary backend suite (every root `test/*.test.js`, one Node process per file) plus the complete browser visual suite. On a 24-CPU Windows workstation the backend inventory alone measured 880–908 s, so the gate took about 15 minutes before any failure surfaced, and it was not reliable there: two backend cases hard-coded a POSIX workspace cwd, which makes the kernel worker reject the derived filename on Windows, so the gate could never pass on that platform. A gate that is both slow and platform-red is not used as a gate: contributors either skip it or run it after the change is already pushed.
6
+
7
+ ## Decision
8
+
9
+ The commit gate runs the static prerequisites plus the **semantic-evidence backend tier**; the complete behavioral inventories stay behind `npm run verify`.
10
+
11
+ - `npm run check` records its proof from `npm run verify:check`: `review:validate`, `host:channels:verify`, `host:shape-check`, `semantic:check`, `build:check`, the syntax checks and `deterministic-test-groups.mjs --check`.
12
+ - `--check` selects `test:client` plus `test:backend:fast`. The Client units stay in the gate because a change can be covered only by them; leaving them out let a red Client suite pass, which is how the copy-key and layout regressions were first caught. The backend tier is the test files that `semantic-obligations.json` names as evidence for at least one obligation, minus coverage-only paths and the verification-tooling probes that fail by construction outside their own harness. Each obligation keeps at least one executable owner, and the fast tier is derived from the ownership graph rather than a hand-maintained list.
13
+ - `npm run verify` keeps the complete gate: `test:client:visual` plus the whole ordinary backend inventory. CI runs `verify:ci`, release preparation runs `verify` (and coverage), and any change that touches a semantic owner missing from the graph must extend the graph — which extends the fast tier at the same time.
14
+ - Browser suites are no longer part of the commit gate. They remain the complete visual contract and run in `verify`, in CI, and on demand.
15
+ - The backend runner rejects `--file` together with `--fast`, so a caller cannot mistake a focused selection for the tier.
16
+
17
+ Measured on the same Windows workstation (24 logical CPUs): `verify:check` completes in about 164 s wall clock once the Client units are included, because the Client and backend groups run in parallel (serial estimate ~231 s: Client ~128 s, fast tier ~96 s, static prerequisites ~7 s); the complete gate remains ~900 s for the backend inventory plus ~186 s of browser suites. Linux/WSL runs the complete backend inventory in 376 s at `DSH_PTC_TEST_CONCURRENCY=2`, so the tier split is a Windows-scale decision, not a suite that is inherently slow.
18
+
19
+ Two portability defects surfaced while making the gate reliable and are part of this decision's evidence: the two `plugin-settings` fixtures used `cwd: '/workspace'`, which produced a `file:///workspace/repl` worker filename that Windows rejects; they now use a platform-neutral cwd. The failing assertion also prints the runtime's own diagnostic instead of a bare boolean.
20
+
21
+ ## Alternatives considered
22
+
23
+ **Keep the complete inventories in the commit gate.** Rejected: 15 minutes on the target workstation, and the same gate cannot be green there at all while a fixture is platform-specific. Contributors route around a gate they cannot run.
24
+
25
+ **Skip the two Windows-failing cases with a platform reason.** Rejected: the failures were a fixture defect, not a platform-specific behaviour. Fixing the fixture keeps the protection and removes the red.
26
+
27
+ **Run the whole backend inventory in one process (`--test-isolation=none`).** Rejected after measurement: the suite hung when files that own workers and kernels shared one process, so isolation is load-bearing.
28
+
29
+ **Move only the browser suite out.** Rejected after measurement: browsers cost 186 s, the backend inventory costs ~900 s. Moving browsers alone leaves the gate far outside its budget.
30
+
31
+ **Keep a hand-maintained fast list.** Rejected: it drifts from the ownership graph and silently stops covering new owners. Deriving the tier from `semantic-obligations.json` makes coverage and the gate the same fact.
32
+
33
+ ## Consequences
34
+
35
+ The commit gate proves the static contract and the semantic-evidence tier, not the complete inventories; complete behavioral validation moves to `verify`, CI and release preparation. A regression that only a non-evidence test file can detect now surfaces there rather than at the commit gate. Adding an obligation or an evidence file widens the fast tier, so expanding protection remains a one-line graph change. [ADR 0036](0036-separate-focused-development-from-delivery-verification.md) still owns scheduling, [ADR 0033](0033-gate-client-visual-usability-in-a-browser.md) still owns complete browser validation, and [ADR 0035](0035-run-coverage-only-before-a-new-release.md) still owns release coverage.
@@ -0,0 +1,88 @@
1
+ # Store journal argument digests in the projection
2
+
3
+ ## Status
4
+
5
+ Accepted.
6
+
7
+ ## Problem
8
+
9
+ `ptcPlusSessionLog` keeps one settlement cluster per settled cell. The cluster
10
+ carries the cell's call event, its normalized result and its publication, and each
11
+ of those carries the cell's journal. A journal's call records hold the argument
12
+ wire produced by `encodeValue`, which for a cell that writes a file or assembles a
13
+ large value is tens of kilobytes — the same order as the cell source itself. At
14
+ 450 settled cells a single journal copy is about 20 KB per cell, so keeping every
15
+ cluster with its journal would put 9 MB back into the checkpoint at 450 cells and
16
+ roughly 40 MB at 2000, which is the growth issue #10 fixed.
17
+
18
+ The retention window exists for that reason. While it removed whole clusters, it
19
+ also removed the recovery evidence those clusters held: a cell beyond the window
20
+ could only be restored by refolding the session log, and when the log could not
21
+ supply it the cell became unavailable. Recovery therefore depended on a cache
22
+ setting.
23
+
24
+ ## Decision
25
+
26
+ A cluster outside the payload window keeps its identity, its observation, its call
27
+ entry and its result, and replaces the payload text with digests:
28
+
29
+ - the call event's `data.arguments` becomes `{ argumentsDigest, argumentsBytes }`;
30
+ - the settlement's `call` carries the same digest pair;
31
+ - each journal call record replaces `args` with `{ argsDigest, argsBytes }`, and
32
+ keeps `global`, `member`, `ok`, `settle`, `value` and `error`, so status,
33
+ operations, user binding evidence, confirms and completion stay readable;
34
+ - a derived record replaces `code` with `{ codeDigest, codeBytes }` and keeps
35
+ `description`, `targetCallSeq` and the rewrite metadata.
36
+
37
+ A record carries either the text or its digest, never both. Digests are SHA-256
38
+ over the same wire bytes the text form holds, and byte counts are canonical
39
+ non-negative integers. A mixed checkpoint — recent clusters in text, older ones in
40
+ digests — is the normal shape.
41
+
42
+ ## What admission proves
43
+
44
+ Admission no longer replays the whole log, so it proves the properties the shapes
45
+ themselves carry:
46
+
47
+ - every retained cluster is complete: call entry, observation, result and
48
+ positions agree;
49
+ - a cluster that carries digests carries digests on both sides of each relation
50
+ (settlement call and executable entry, publication journal and normalized
51
+ journal), and the digest pair is well formed;
52
+ - the interval summary, when present, reproduces its own chain and its range
53
+ matches the clusters it replaces;
54
+ - the frontier and selection history agree with the retained positions.
55
+
56
+ It does not prove that a digested record's digest describes the text the log
57
+ holds. That claim is checked where the two sides meet: refolding a dropped or
58
+ digested cluster for an edit or for recovery compares the refolded record with the
59
+ stored one, and rejects a mismatch.
60
+
61
+ ## Recovery and refolding
62
+
63
+ Recovery rebuilds bindings from cell sources and journals. When the checkpoint
64
+ holds digests, `recoverJournal` repeats the fold with retention disabled on the
65
+ same event slice and reads the text from the log. The checkpoint stays bounded;
66
+ the log is the authority. The extra fold happens only when a digested cluster is
67
+ present.
68
+
69
+ ## What is lost
70
+
71
+ - Byte-for-byte equality between the checkpoint's payload and the log's payload:
72
+ the checkpoint keeps the digest, not the text.
73
+ - A cell whose text cannot be refolded — because the log does not cover that range
74
+ — is unavailable rather than silently different. Consumers report
75
+ `该单元格超出保留窗口,已不可编辑` / `This cell is outside the retention window and
76
+ can no longer be edited`, and the generic `retention-pruned` for any other read.
77
+
78
+ ## Consequences
79
+
80
+ The checkpoint's size is a function of the window and of the number of clusters,
81
+ not of how many bytes the session wrote. Folding a cluster into the interval
82
+ summary remains available through the `retentionClusters` configuration, but it is
83
+ no longer the only way to bound the checkpoint, so its default can be unlimited.
84
+
85
+ ## Related
86
+
87
+ - ADR 0032 owns the projection and its admission claim.
88
+ - ADR 0045 owns the model-visible prompt text customization.
@@ -0,0 +1,48 @@
1
+ # Supersede a rewritten cell
2
+
3
+ ## Status
4
+
5
+ Accepted (0.5.3).
6
+
7
+ ## Problem
8
+
9
+ An accepted `edit_run_code` receipt schedules a derived execution: after the outer cell settles, the whole target source runs again in its revised form and is recorded as one more cell. The 0.5.2 fold treated both records as environment members, so "append" became the meaning: the edited cell kept building the environment and the revision stacked on top of it.
10
+
11
+ Appending is invisible in the common case, because the revision declares the same names and overwrites the old values. It leaves one state the plugin cannot state: a name the target declared and the revision no longer declares still exists in the environment, while no cell in the log declares it any more. The plugin then has no producible evidence for a name it reports to the model: the source no longer contains that declaration, and a restart removes it by a different rule.
12
+
13
+ ## Decision
14
+
15
+ The edited cell and its revision are related by **supersession**, not by deletion.
16
+
17
+ - The log does not change: the edited cell and the revision both stay recorded, each with its own journal and source.
18
+ - The fold decides the environment: with `editRewritesHistory` on, the revision folds as an ordinary recorded cell and **takes the edited cell's place** — it continues from that cell's parent, and the cells recorded after the edited cell continue from the revision. The superseded cell keeps its node but leaves the head's parent path; the head stays the latest recorded cell.
19
+ - The relation does not depend on how the revision itself settled: a revision that settled volatile is installed from its recorded values and still replaces its target; a revision the fold contracts (its recorded values cannot be installed) is absent from the environment, and the relation the record states still holds, so the target's provable names are still reported as superseded. A contracted revision has no node to continue from, so its successors continue from the superseded cell's parent. A `discarded` settlement is no evidence that the revision ran, so it states no relation and keeps append semantics; a `noop` derived settlement never becomes a record and states none either.
20
+ - The relation is reconstructable from the log: the fold reports `supersessions` (`supersededCallSeq` / `supersedingCallSeq`) and `supersededBindings`, attached to the recovery result like `deletedBindings` and without changing the enumerable shape of §history.
21
+ - Reporting: one relation states `PTC-R007` once per runtime, naming which cell replaced which, together with the names the log can prove and the revision no longer supplies.
22
+ - Off by default. When it is off, the fold keeps 0.5.2's append semantics exactly, so neither existing cases nor the reading of earlier session logs changes.
23
+
24
+ ## What it claims and what it refuses to claim
25
+
26
+ `supersededBindings` contains only names the log can prove: the `values` a volatile settlement recorded, `unrecordedBindings`, and `binding` save operations. A durable cell's declared names are not in its journal — replay produces them by executing the source — so the fold never guesses a name for the model, and `PTC-R007` states only the relation when there is no name to report.
27
+
28
+ The live worker and the durable fold keep their existing division of labour: the environment recovery replays is the environment the supersession applies to, while the live cache keeps the values it executed. This matches how `PTC-R005` (a recorded rewind ended these bindings) is handled: the record states the ending, the notice names it, and the live cache is not described as already emptied.
29
+
30
+ ## Alternatives considered
31
+
32
+ - **Keep append semantics and report the leftover names as lost.** Rejected: the log does contain a cell that declared them, so "lost" would be false, and the model would be told to redeclare a name whose replacement it never saw.
33
+ - **Delete the superseded cell from the log.** Rejected: the session log is append-only evidence, and history replacement belongs to DSH. Supersession is a fold-time relation, and the record has to keep proving what happened.
34
+ - **Refuse the edit unless the revision redeclares every name the target declared.** Rejected: an accepted edit is the user's decision about the cell's source, and a revision that intentionally drops a name is legal JavaScript. Refusing it would make the transport decide the source.
35
+ - **Report the relation on every later fold instead of once per runtime.** Rejected: the relation is a durable fact of the log, not a per-cell event; repeating it on every recovery is noise the model cannot act on twice, and the names it withdraws are reported by their own diagnostics.
36
+ - **Adopt the relation for a `discarded` settlement too.** Rejected: a discarded settlement is not evidence that the revision ran, so claiming it replaced the target would state an execution the log denies.
37
+
38
+ ## Consequences
39
+
40
+ - With the switch on, the set of cells in the environment no longer equals the set of cells in the log; the difference is the supersession relation, not a loss.
41
+ - Later cells read the revised value, and so do they after recovery.
42
+ - A session log written while the switch was off folds under the same relation once it is on, because the relation lives in the log rather than in the configuration.
43
+ - A runtime that supersedes a cell reports `PTC-R007` beside any restore notice the same recovery produces, and each consecutive recovery states the facts it replayed.
44
+
45
+ ## Related
46
+
47
+ - ADR 0025 owns logical binding identity and language generations.
48
+ - [durability-design.md](../durability-design.md) §「日志折叠」 and §「恢复通知」 describe the fold order and the diagnostics.
@@ -0,0 +1,53 @@
1
+ # Prove a settlement against the record it published
2
+
3
+ ## Status
4
+
5
+ Accepted (0.5.3).
6
+
7
+ ## Problem
8
+
9
+ Recovery reads a journal record as evidence for the bindings its cell declared, and the record is the only account of its own cell. Nothing inside it distinguishes "this is what happened" from "this is what someone wrote afterwards".
10
+
11
+ 0.5.2 already checks two things against each other: `completion.error.message` is rendered from the first diagnostic, so a completion that no longer renders from its diagnostic is refused, and `dshPtcPlusDiagnosticWording.digest` covers the facts those diagnostics state. Together they show why internal consistency is not enough. Edit a diagnostic and re-render the completion and the record is consistent again; and the completion's kind and value, and the cell's status, have no second statement anywhere in the record.
12
+
13
+ ## Decision
14
+
15
+ A settlement leaves a digest of its own statement at publication, and the fold recomputes it before treating the record as evidence:
16
+
17
+ - `dshPtcPlusSettlementProof = { version, recordedMessage }`, where `recordedMessage` covers the status, the completion (kind, `hasValue`, the value digest, the error kind and message) and the facts each diagnostic states.
18
+ - `help` and `source` stay out of it because they are presentation: a historical record may be read through an older generation's help wording, and a caret position is re-derived when the diagnostic is rendered.
19
+ - A mismatch means the cell cannot prove its bindings. It establishes a contraction point (`PTC-R002`) beside every other damaged-metadata case. It is not a throw and not a retry: the refusal is the fold's terminal state.
20
+ - A record without the field predates this proof and keeps the 0.5.2 evidence rules. Migration is by absence, and the journal schema version does not change, because the proof is a sibling key rather than a journal field.
21
+
22
+ Alongside it, the Host's `isError` on the same `tool/result` event is a copy this plugin cannot recompute: it comes from what the tool returned, not from the journal. When it is present, the fold requires
23
+
24
+ ```text
25
+ isError === (status === "discarded" || completion.kind === "throw")
26
+ ```
27
+
28
+ which is the relation a nested edit already proves before it publishes a derived settlement. An editor who repairs the proof from their own edited journal therefore still has to agree with the Host's copy of the outcome.
29
+
30
+ ## What the proof does not claim
31
+
32
+ - It is not an unforgeable boundary. Someone who rewrites both the facts and the proof is equivalent to a re-publication; what the proof establishes is that the record was not partially rewritten.
33
+ - The plugin does not own the Host's model-visible text, so it does not compare against it. That text is a projection this plugin cannot reproduce, and treating it as evidence would call a presentation difference tampering, a far more common event than the damage the proof guards against.
34
+ - `dshPtcPlusDiagnosticWording` remains the reading path for records that carry it; a new publication writes one proof rather than two names for one relation.
35
+
36
+ ## Alternatives considered
37
+
38
+ - **Compare against the retained model-visible message text.** Rejected: the plugin cannot recompute the Host's projection, so a renderer or locale change would read as tampering, and every failing cell in a host that projects differently would lose its bindings.
39
+ - **Bump the journal schema version and migrate old records.** Rejected: absence already carries the same information without rewriting historical metadata, and a version bump would make every pre-0.5.3 record unreadable to a generation that has no better evidence to offer.
40
+ - **Keep only the diagnostic wording digest and add a separate completion digest.** Rejected: two digests over one settlement state the same relation twice, and the fold would have to decide which of them wins when they disagree.
41
+ - **Reject a mismatched record by throwing from the fold.** Rejected: a throw turns every retry into the same throw, which is the loop that hung an earlier attempt. The fold registers an unavailable result instead.
42
+
43
+ ## Consequences
44
+
45
+ - An edited record no longer proves its cell's bindings. The fold contracts at that cell, the model reads `PTC-R002` naming the skipped cell, and the source stays in the session log.
46
+ - Both publication paths write the proof: `withJournal` for a `run_code` settlement and the derived edit settlement's metadata.
47
+ - Session logs need no migration: old records lack the field, new records carry it, and the reader branches on its presence.
48
+ - The `isError` agreement only applies where the Host records it. A log without that copy keeps the weaker evidence rather than inventing a claim.
49
+
50
+ ## Related
51
+
52
+ - ADR 0025 owns logical binding identity and language generations.
53
+ - [durability-design.md](../durability-design.md) §「Completion 校验」 describes how the fold uses the proof.