dsh-ptc-plus 0.5.0 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +15 -3
- package/README.md +15 -3
- package/client.cjs +1983 -234
- package/compiler-core.cjs +132 -132
- package/compiler-core.cjs.map +3 -3
- package/docs/adr/0005-temporary-rejected-cell-edit-transport.md +2 -0
- package/docs/adr/0017-track-the-latest-dsh-public-surface.md +2 -2
- package/docs/adr/0023-global-user-bindings.md +1 -1
- package/docs/adr/0027-run-the-session-kernel-in-a-killable-helper-process.md +11 -1
- package/docs/adr/0032-own-session-log-facts-in-a-host-only-projection.md +31 -1
- package/docs/adr/0038-freeze-session-capability-plans.md +1 -1
- package/docs/adr/0044-coexist-with-the-official-persistent-repl.md +35 -0
- package/docs/adr/0045-customize-model-visible-prompt-text.md +38 -0
- package/docs/adr/0046-gate-the-commit-with-the-semantic-evidence-tier.md +35 -0
- package/docs/adr/0047-store-journal-argument-digests-in-the-projection.md +88 -0
- package/docs/adr/README.md +2 -0
- package/docs/architecture.md +2 -2
- package/docs/capability-planning-components.md +2 -2
- package/docs/client-ui.md +13 -10
- package/docs/client-visual-contract.md +1 -1
- package/docs/durability-design.md +7 -1
- package/docs/installation.md +5 -3
- package/docs/publishing.md +2 -2
- package/docs/releases/v0.5.2.md +79 -0
- package/docs/runtime-reference.md +42 -3
- package/docs/unmeasured-cell-recovery-repair-proposal.md +387 -0
- package/docs/verification.md +6 -2
- package/index.js +63 -19
- package/internal/bounded-literal.js +85 -0
- package/internal/capability-command-receipt.js +3 -1
- package/internal/capability-environment.js +171 -13
- package/internal/capability-plan.js +11 -8
- package/internal/capability-statistics.js +6 -2
- package/internal/cell-error.js +13 -0
- package/internal/config-spec.js +122 -2
- package/internal/direct-surface-owner.js +14 -10
- package/internal/edit-transport-owner.js +96 -17
- package/internal/execution-provider-owner.js +25 -0
- package/internal/execution-seam-compat.js +18 -0
- package/internal/isolated-worker.js +134 -17
- package/internal/kernel-worker.js +167 -3
- package/internal/legacy-cell-analysis.js +14 -2
- package/internal/legacy-repl-convenience.js +16 -3
- package/internal/official-repl-priority.js +51 -0
- package/internal/projection-retention.js +238 -0
- package/internal/prompt-text.js +858 -0
- package/internal/recovery-tips.js +50 -11
- package/internal/rejected-cell-editor.js +3 -2
- package/internal/runtime-bridge-owner.js +11 -8
- package/internal/runtime-config.js +2 -0
- package/internal/runtime-contexts.js +14 -5
- package/internal/runtime-messages.js +43 -15
- package/internal/sdk-projection.js +2 -2
- package/internal/session-cell-executor.js +132 -11
- package/internal/session-journal-recovery.js +1009 -99
- package/internal/session-journal-schema.js +7 -5
- package/internal/session-journal.js +177 -14
- package/internal/session-log-view.js +111 -24
- package/internal/session-runtime.js +674 -33
- package/internal/session-state.js +27 -0
- package/internal/stateful-root-compiler.js +11 -0
- package/internal/stateful-root-runtime.js +17 -0
- package/internal/user-binding-console.js +7 -0
- package/internal/user-bindings-owner.js +8 -27
- package/internal/user-bindings.js +2 -2
- package/internal/validated-parse-repair.js +6 -1
- package/internal/worker-client.js +27 -4
- package/package.json +5 -2
- package/src/client-activity.js +27 -6
- package/src/client-capabilities-view.js +774 -125
- package/src/client-copy.js +231 -18
- package/src/client-repl-view.js +6 -1
- package/src/client-styles.js +68 -2
- package/src/client-tool-view.js +9 -2
|
@@ -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
|
|
@@ -26,7 +26,7 @@ Compatibility decisions have one owner per public contract and execution
|
|
|
26
26
|
environment. `src/client-host-compat.js` owns Client preset reads and subscriptions
|
|
27
27
|
and composer compatibility. UI consumers own feature eligibility and slot
|
|
28
28
|
registration. `scripts/dsh-host-contract.mjs` owns selected-installation package
|
|
29
|
-
resolution, tools presentation and
|
|
29
|
+
resolution, tools presentation, persona fields and public persistence plugin selection for development and
|
|
30
30
|
acceptance commands. Host runtime contracts remain in their existing focused
|
|
31
31
|
owners, including `internal/session-events.js`, `internal/settings-compat.js`, and
|
|
32
32
|
`internal/execution-seam-compat.js`, which owns which program-execution service the
|
|
@@ -63,7 +63,7 @@ capability, storage and observation rules independently of transport.
|
|
|
63
63
|
|
|
64
64
|
**Pin one verified DSH release.** A pin makes one historical environment reproducible but turns normal upstream progress into an artificial incompatibility and allows the implementation to drift from the release users actually install.
|
|
65
65
|
|
|
66
|
-
**Maintain a supported-version range.** A range communicates broader intent, but semver cannot prove compatibility with evolving extension contracts. Maintaining the range duplicates evidence already produced by contract and end-to-end tests while still requiring every new release to be evaluated.
|
|
66
|
+
**Maintain a supported-version range.** A range communicates broader intent, but semver cannot prove compatibility with evolving extension contracts. Maintaining the range duplicates evidence already produced by contract and end-to-end tests while still requiring every new release to be evaluated. The package therefore declares `engines.dsh: "*"` for the Host-owned manifest preflight, preserving capability-based admission without a release allowlist. The packed contract suite runs the selected Host's public `evaluatePluginCompatibility` before importing plugin code; required service peers still establish package resolution.
|
|
67
67
|
|
|
68
68
|
**Treat the upstream default tool projection as an immutable plugin limit.** This avoids scoped presentation work, but discards capabilities that the public registry supports and can force valid model calls through lossy aliases. Defaults describe the host's built-in product choice; public extension contracts define what a plugin may add. Invalid out-of-surface calls may still be normalized when their intended native target is provable.
|
|
69
69
|
|
|
@@ -77,7 +77,7 @@ The existing REPL supplies execution feedback; the handoff accepts final source
|
|
|
77
77
|
|
|
78
78
|
The first validated candidate becomes one disabled memory draft. Private `dshPtcPlusBindingDraft` metadata version 2 contains the generation, opaque locator, and the accepted candidate with request ID, command ID, version, mode, and complete source-owned entry. Later results carry `candidate: null`; version 1 remains readable. The `ptcPlusBindingDraft` projection, state version 4, pairs command and turn lifecycle with acceptance, retains read-only history across seeds and owner generations, and never revives a historical locator. Malformed historical presentation cannot block subsequent valid execution. The management Remote uses the authenticated Host Gateway and provides no caller/session identity, so draft operations require the locator rather than a payload session ID. The `draft-review` endpoint serves the accepted source and any completed action while its owning lifecycle remains available.
|
|
79
79
|
|
|
80
|
-
Confirmed enable/disable of a saved binding
|
|
80
|
+
Confirmed enable/disable of a saved binding appends a separate `ptc-binding-toggle/v1` receipt through public `Session.append('user/message', message, {surfaceOp: 'append'})` only after the Session has its Host-owned first accepted system head. Before that boundary, management changes initial configuration and supplies immediate UI feedback without appending or queueing session receipts. The first accepted request receives only the current configured API catalog. The receipt describes configuration persistence, never catalog delivery or worker initialization, and never wakes an Agent. Storage failure emits no success receipt. Existing historical receipts remain readable. Since there is no pending receipt queue, later acceptance or compaction cannot resurrect pre-session toggle history.
|
|
81
81
|
|
|
82
82
|
After a user save or discard completes, the Host consumes the writable locator and appends a structured plugin notice through public `Session.append('user/message', ..., {surfaceOp: 'append', sourceEventSeqs: [acceptingResultSeq]})`. The notice contains only request ID, entry ID, saved/discarded state, and enabled state. It asserts persistence, never session activation, and contains no source or locator. The projection accepts it only when identity and the accepting source relation match. Card remounts use this historical source and action, never a later store entry with the same ID. A failed notice append cannot undo the write or restore save eligibility; missing durable evidence remains unknown after restart. In-flight storage settlement cannot resurrect a locator already revoked by lifecycle cleanup. Clearing an unsaved draft alone does not append a persistence notice.
|
|
83
83
|
|
|
@@ -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
|
|
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.
|
|
@@ -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
|
|
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.
|
|
@@ -28,7 +28,7 @@ A session capability plan records the effective enablement and disclosure choice
|
|
|
28
28
|
|
|
29
29
|
Explicit session choices and saved choices have separate owners. `/capabilities set session <encoded-id> <full|on-demand|closed|automatic|inherit>` changes only the current session. `automatic` explicitly removes that identity's saved manual override from the effective input; `inherit` removes its temporary override and resumes the adopted source choice. Ordinary `/capabilities refresh` adopts current saved configuration while retaining temporary choices. `/capabilities set global <encoded-id> <full|on-demand|closed|automatic>` saves through the public revision-checked settings writer and updates this session in the same Host maintenance operation, clearing only that identity's temporary choice. Other existing sessions remain frozen. Server choices allow only `closed`, `automatic` and session `inherit`; their closure precedes child choices, and reopening preserves children.
|
|
30
30
|
|
|
31
|
-
An initialized session publishes the complete new snapshot through its real append-only plan message. Before the protected system head exists, the formal `/capabilities` command returns its validated pending proposal in the structured receipt inside `CommandResult.text`; the Host alone records the paired `command/run` and successful `command/done`. These log-only command facts are not model knowledge. The first accepted step publishes
|
|
31
|
+
An initialized session publishes the complete new snapshot through its real append-only plan message. Before the protected system head exists, the formal `/capabilities` command returns its validated pending proposal in the structured receipt inside `CommandResult.text`; the Host alone records the paired `command/run` and successful `command/done`. These log-only command facts are not model knowledge. The first accepted step publishes only the final initial plan to the model, without refresh/replacement wording for an environment the model has never received. Repeated preparation or explicit choices replace that uncommitted initial proposal; they do not append intermediate change notifications. Restoration reads successful receipts with exact command identity and raw input pairing, and plan messages, in order after the most recent explicit provider disablement; a routine provider release (`unloaded`) is an infrastructure fact and keeps the recorded plan as the session's restored baseline, so saved configuration still does not redistribute a resumed session. Restoration validates source/effective equality and the planner result, and does not repeat a global write, load a Skill body or execute a historical effect. Failed, cancelled, unpaired or damaged command facts cannot prove a choice; an unconfirmed live proposal is discarded before a later accepted step, and a late handler result cannot revive it. External plugins must not call `appendPluginRecord`, even though it is exported. History observations use the public `sessionQuery` cut; new production reads do not use deprecated synchronous Session readers.
|
|
32
32
|
|
|
33
33
|
All mutations use public `agent.runMaintenance`, which may reject a busy Agent. The plugin does not queue another session scheduler. Identity, inventory and management preflight precede a global write; the write uses the settings entry's actual namespace and revision. Global saving and session publication are different transactions: a saved choice remains saved if subsequent publication is refused or cancelled, while the old effective environment is retained and the receipt reports saved-but-not-applied. No automatic rollback overwrites a later settings writer. A committed operation followed by lost UI connectivity has an unknown UI outcome, so users inspect the actual plan before retrying. Target-specific read-only catalog commands include closed items for user re-enablement without publishing them to model discovery or counting them as use.
|
|
34
34
|
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Coexist With The Official Persistent REPL
|
|
2
|
+
|
|
3
|
+
## Problem
|
|
4
|
+
|
|
5
|
+
DSH added an opt-in official Node execution mode (`executionMode: persistent-in-memory`) that retains a confined REPL per Agent, and extended the `ptcRuntime` seam with `PtcRunRequest.stateOwner`, `PtcRuntime.programFormat` and `PtcRuntime.release(owner)`. The host `run_code` consumer registers `runtime.release(agent)` at Agent disposal for every provider, passes `stateOwner` on requests, and program-format consumers (the workflow adapter, cua) wrap programs according to `programFormat`. PTC Plus owns the same seam: without a policy, its provider surface lacked the new members so Agent disposal could fail, non-session consumers could not see the format of the provider that actually executes their programs, and an explicitly enabled official mode stayed silently inert for session cells while the plugin kept owning them.
|
|
6
|
+
|
|
7
|
+
## Decision
|
|
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)).
|
|
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.
|
|
12
|
+
|
|
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
|
+
|
|
15
|
+
## Alternatives considered
|
|
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.
|
|
18
|
+
|
|
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
|
+
|
|
21
|
+
**Suspend execution in place inside the installed runtime.** Toggling individual owners (prompt, tool bridge, plans, projections) mixes the two execution contracts in one attachment and creates states no single owner can prove; the installation shape is the granularity the plugin already uses for language retirement.
|
|
22
|
+
|
|
23
|
+
**Keep management owners (global binding workbench, capability workbench) outside the deferred attachment.** Splitting the management surface from the execution runtime is a larger refactor that would change ownership for every state, not only deferral; it remains possible future work instead of being smuggled into this decision.
|
|
24
|
+
|
|
25
|
+
**Forward `release(owner)` only while delegating execution.** Non-session consumers keep running through the original provider even while PTC Plus owns session cells, so owner-keyed environments can exist in every state; a delegation-only forward would leave those environments unreleased and the registered provider contract incomplete.
|
|
26
|
+
|
|
27
|
+
## Consequences
|
|
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.
|
|
30
|
+
|
|
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
|
+
|
|
33
|
+
Deferred mode intentionally reduces the plugin's computation surface (no REPL guidance, session semantics, recovery, editor, capability SDK, REPL observation or binding workbench for that attachment); the setting copy and README state this so the user's choice is informed.
|
|
34
|
+
|
|
35
|
+
The plugin keeps withholding `sandboxMode` while it owns execution, so deployments that require file confinement still need another PTC execution environment — unchanged by this decision, and now also visible in the workflow adapter's explicit refusal.
|
|
@@ -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:baseline:verify`, `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.
|
package/docs/adr/README.md
CHANGED
|
@@ -22,3 +22,5 @@ This directory records stable design constraints for PTC Plus. An ADR states a d
|
|
|
22
22
|
- [0042: Persist Provider Availability And Handback](0042-persist-provider-availability-and-handback.md) — accepted-step availability, public persistent release and separately sourced configuration receipts.
|
|
23
23
|
|
|
24
24
|
- [0043: Compose Capability and Context Policies](0043-compose-capability-and-context-policies.md) — independent availability, disclosure, allocation, collection, recovery and presentation inputs with historical replay generations.
|
|
25
|
+
|
|
26
|
+
- [0044: Coexist With The Official Persistent REPL](0044-coexist-with-the-official-persistent-repl.md) — forwarded owner release and program format, plus live-capability ownership selection between PTC Plus and the official persistent REPL.
|
package/docs/architecture.md
CHANGED
|
@@ -13,7 +13,7 @@ PTC Plus 把 DSH PTC 模式的顶层 `run_code` 变成与 session 绑定的连
|
|
|
13
13
|
| Journal / replay | PTC Plus + session log | 记录 call transcript、settlement、completion 和恢复边界 |
|
|
14
14
|
| Presentation | DSH + PTC Plus | 保留 native guidance,并追加 REPL 指引与最小 explorer 声明 |
|
|
15
15
|
|
|
16
|
-
求值层由插件自有的 helper 进程承载:Host 只持有 `IsolatedWorker` transport,真实 `node:worker_threads` session worker 与其私有 `MessagePort` 都留在 helper 内,IPC 只传输 plain data。宿主回收卡住的计算时只强杀自己的 helper process,DSH 进程内没有需要终止的 session worker,因此不会触达已测得会落入 Node 24/26 cppgc teardown fault 的 in-host worker 强杀路径;`IsolatedOwner` 只在该实例的 real exit、output close 和 cleanup 都完成后释放。kernel、console、candidate 三个入口各自拥有 owner。helper 的 `init` 携带 fork 前 snapshot 的投影环境,user Worker 及其子进程保持原 env 契约,不会继承 `NODE_V8_COVERAGE` 等宿主 instrumentation;Electron Host 只在 helper fork 环境覆盖 `ELECTRON_RUN_AS_NODE=1`,该启动变量不进入 user Worker。inner Worker 的 compute budget 在每次请求提交时通过 helper 的 on-demand sample 取新鲜 `eventLoopUtilization` baseline,ready handshake
|
|
16
|
+
求值层由插件自有的 helper 进程承载:Host 只持有 `IsolatedWorker` transport,真实 `node:worker_threads` session worker 与其私有 `MessagePort` 都留在 helper 内,IPC 只传输 plain data。宿主回收卡住的计算时只强杀自己的 helper process,DSH 进程内没有需要终止的 session worker,因此不会触达已测得会落入 Node 24/26 cppgc teardown fault 的 in-host worker 强杀路径;`IsolatedOwner` 只在该实例的 real exit、output close 和 cleanup 都完成后释放。kernel、console、candidate 三个入口各自拥有 owner。helper 的 `init` 携带 fork 前 snapshot 的投影环境,user Worker 及其子进程保持原 env 契约,不会继承 `NODE_V8_COVERAGE` 等宿主 instrumentation;Electron Host 只在 helper fork 环境覆盖 `ELECTRON_RUN_AS_NODE=1`,该启动变量不进入 user Worker。inner Worker 的 compute budget 在每次请求提交时通过 helper 的 on-demand sample 取新鲜 `eventLoopUtilization` baseline,ready handshake 另有启动样本;采样等待(`sampleBoundMs`,默认 1000ms)在把 helper 通道读空之后才下结论,阻塞期间写入通道的答复即使排在此前消息之后也不会因过期定时器被判缺失,读数上限只报告"没有观察到答复";helper 退出、断连或无法接收请求仍按 worker-exit 有界失败并重置 worker,其他无法观察到答复的采样、以及 run 帧发出之前到达的墙钟或 compute 上限,都按未测量的 no-op 结算:默认 2 时在首次保留 worker 与既有 binding,连续达到 `unmeasuredCellLimit` 个则丢弃 live worker、保留已证明 frontier 由下一 cell 重放;历史重放中无法观察的 cell 同样保留 frontier 并重试。唯一例外由 [ADR 0024](adr/0024-repl-console-observation.md) 所有:同一 worker 已确认存在未完成的 observation 时,prepare 可先于预算启动,确认 observation 完成后才开始计时。
|
|
17
17
|
|
|
18
18
|
主入口接管模型直接发起的顶层 `run_code`,并通过 DSH tool registry 真实注册 `edit_run_code`。
|
|
19
19
|
使用 code-only direct-tool projection 的 PTC request 只暴露 `[run_code]`;插件用 agent scope 的
|
|
@@ -52,7 +52,7 @@ Client half 在侧栏 **插件 → 已安装 → dsh-ptc-plus** 的插件行配
|
|
|
52
52
|
总开关关闭时不注册 runtime、执行 hook、tool surface 或 system prompt section,也不读取 binding 存储;`ptc` 与兼容 `code` preset 会话在头部单独显示 `PTC Plus` 指示器。设置卡片显示“已启用/已停用”,稳定指引不包含 UI 品牌名。
|
|
53
53
|
`Config` 的字段形态由 `internal/settings-compat.js` 按已安装代际的公共能力决定:仍提供 section installer 的代际保留普通描述字段,由自身文档路径提交校验后的具体默认值;只有自管设置文档的代际才加 volatile 标记,字段构建器不具备该能力的代际保持可加载。版本号不参与该判断。
|
|
54
54
|
`autoDescribeRunCode` 默认开启且只控制请求绑定的本地执行策略。开启时,缺少外层 `run_code.description` 的调用使用派生参数通过本地 DSH 校验,备用摘要仅进入 presentation metadata;关闭时由 DSH 校验原始参数。两种状态的模型可见 tool schema、tool order 和 system sections 保持字节稳定并包含 required `description`;原始调用参数、cell 与嵌套 native 参数保持不变。
|
|
55
|
-
在 section-installer
|
|
55
|
+
在 section-installer 代际,除只由插件激活期读取的 `executionSeamTimeoutMs` 外,所有字段都由 settings watch 即时交给各自 owner,且不替换已有 session-bound binding;在 host-owned 代际,宿主把 volatile-only 变更提交进导出 `Config` 的 retained handles,运行中的 fiber 经公开的 `loader/volatile-update` 事件就地 reconcile,同样保留已有 session-bound REPL、binding 与 journal。每个已提交 cell 的预算与策略固定其提交时的配置代际,timer、worker 消息、program binding bridge、结果校验和诊断共同消费该快照;重配置更新随后提交 cell 的默认值。worker 生命周期上界(`maxOldGenerationSizeMb`、`workerReclaimMs`、`startupProbeMs`)在 worker 创建或关闭时从当前配置读取。`maxOldGenerationSizeMb` 在活动 worker 存在时因 Node 的创建期限制而拒绝并回滚。settings 服务缺失时 Host 回退到 composition config,并保持相同的运行时语义。
|
|
56
56
|
|
|
57
57
|
`bindingUpdates` 是新配置的统一入口,默认 `stateful`,也可选择 `protected`。Host schema 保留该字段原始缺省状态,再由 `runtime-config.js` 把完整旧策略映射到相应新策略;混合旧开关保留显式 `legacyBindingSettings` 兼容态。Client 复用同一 resolver,仅在兼容态展示旧开关;用户选择统一开关时,一次 settings update 同时写入新策略并清除兼容标记。
|
|
58
58
|
|
|
@@ -50,7 +50,7 @@ capabilityCoverage(state, { userId, workspaceId, id, revision })
|
|
|
50
50
|
|
|
51
51
|
同一家族、能力、范围只贡献一个使用位。工作区观察和全局观察分别归约,跨工作区后代不增加全局权重。每项能力和范围使用最近 N 个合格家族;续接修改已有身份的使用事实,不改变原资格顺序。正在运行或结算未知的家族不形成负样本。淘汰前沿阻止旧观察重读后重新进入已有窗口;不使用自然时间衰减。
|
|
52
52
|
|
|
53
|
-
`capabilityCoverage`
|
|
53
|
+
`capabilityCoverage` 返回样本量、使用量、窗口和覆盖率;没有可用样本时覆盖率为 `null`。尚无任何相关记录时状态为 `no-samples`,窗口内已有记录但家族当前未结算时状态为 `pending-settlement`,两者样本量、使用量和覆盖率均为 `null`,界面显示破折号和证据状态而不是零。窗口已淘汰旧证据后,近期家族恢复为运行中或结算未知,若剩余已结算样本不足 N,状态为 `incomplete-window`,样本量、使用量和覆盖率均为 `null`;不能把无法重建的窗口子集用于自动分配。再次具有 N 个已结算样本时恢复有效。超过显式容量上限后状态为 `capacity-exceeded`,覆盖依据为 `null`。这些不可用证据均回退手动基线;未淘汰证据的正常冷启动仍可返回实际样本数。结构损坏由验证器拒绝,存储适配器将读取失败转为统计不可用诊断,保留手动配置并继续合法计算。
|
|
54
54
|
|
|
55
55
|
组件状态是可验证的 JSON 数据。存储适配器使用宿主 home 路径下的 `ptc-plus/capability-statistics.json`,以 `withFileLock` 和 `writeFileAtomic` 串行化跨实例更新,仅保存身份、资格、使用和结算元数据。续接修订原家族使用位,资格顺序保持不变。公开家族 census 不完整、未进入 step 或 turn 非正常完成时不产生负样本。
|
|
56
56
|
|
|
@@ -86,7 +86,7 @@ Skill 目录的 `snapshot.complete` 由公共 registry 拥有,表示本次 pro
|
|
|
86
86
|
|
|
87
87
|
设置刷新按钮通过只读 `targets` 获取活跃 PTC 会话身份,并要求明确选择目标;星光菜单从公共 slot 读取当前会话的命令可用性,与绑定编写共用输入框保护,将 `/capabilities refresh` 预填到空输入框并关闭菜单,等待用户发送。设置按钮由 `CapabilityRefreshButton` 通过 authenticated `commands.execute` 直接执行;最终都委托同一 Host 刷新 owner。身份观察不读取或准备冻结方案,管理关闭时仍可主动同步关闭配置。目录/方案重读保持只读。Client 在取消、断线、preset 改变、会话切换、卸载或插件关闭后丢弃迟到回执;停止等待不表示撤销已提交的 Host 效果。
|
|
88
88
|
|
|
89
|
-
管理默认关闭。设置工作台分为能力、上下文、统计、配置四个页签。能力页默认直达清单,名称下展示精确 ID,类型和来源独立显示;行内启停只修改可用性,上下文选择、常用集与继承规则在单项详情中独立编辑。筛选在独立小窗中按需打开,提供类型、来源、规则、常用与可用状态筛选;五列表头支持升序、降序、默认三态排序,会话列表复用同一展示排序 owner
|
|
89
|
+
管理默认关闭。设置工作台分为能力、上下文、统计、配置四个页签。能力页默认直达清单,名称下展示精确 ID,类型和来源独立显示;行内启停只修改可用性,上下文选择、常用集与继承规则在单项详情中独立编辑。筛选在独立小窗中按需打开,提供类型、来源、规则、常用与可用状态筛选;五列表头支持升序、降序、默认三态排序,会话列表复用同一展示排序 owner。清单支持勾选多个条目(表头复选框选择当前筛选结果);批量设置打开一次性小弹窗,将所选真实可用目标固化为单项变更,确认才写入,会话工作台以同样的选择方式把批量选择暂存为待应用更改,由底部按钮决定应用到会话或全局;默认、类别和来源的持久继承规则使用独立入口。类型切换清除不兼容来源,Tools 与 MCP 工具的浏览入口区分,来源规则只使用已证明的 server 身份。详情区分保存值与明确选定会话的结果。上下文页以能力说明、说明恢复、结果与日志三个固定目的地组织双栏设置,各项开关及额度独立,关闭保留参数;预算预览位于能力说明详情。全部开关使用官方 capsule track、圆形 thumb、尺寸和主题 token。统计页可切换查看所选活跃 PTC 会话的当前工作区或全局使用数、合格家族样本数和覆盖率;不完整窗口或不可用证据保持明确状态。能力说明的预算预览从该会话当前 SDK/Skill/MCP 清单、已保存配置与实际统计调用同一纯方案 owner,显示完整/按需/关闭选择、原因、正文大小、包含共享依赖的总预算和超额。观察通过 `allocation` RPC,使用只读 assembly observation,不准备或发布会话方案,不登记样本、读取 Skill 正文、调用工具或请求模型;目标退出和迟到回复会失效。配置序列化由 Client 完成,用户无需编写 JSON;最低样本数超过窗口等无效输入不提交。已有配置中暂不可用的能力选择保留并可撤销。配置页以左侧命名配置库、右侧所选详情组织;唯一保存当前设置、导入和更多动作在列表下方,会话观察另行展开,与已保存配置分开。`capabilityProfiles` 的 `ptc-capability-profiles/v1` 文档保存最多 100 份具名能力设置,支持命名保存、重命名、删除、文件导入导出和一次公共设置 mutation 切换;导入不自动应用,同名导入生成新名称。会话观察同时提供源设置,用户可读取并命名保存,不复制统计证据或计算状态。切换配置不刷新旧会话,明确选定目标的正式 Host 命令才提交刷新;原冻结方案继续展示选择、原因、覆盖率、显式超额和待刷新状态。格式与边界见 [ADR 0040](adr/0040-separate-capability-configurations-from-session-observations.md)。有界 `capabilities.search` 的原接口保持兼容,见[能力投影](capability-projection.md)。大结果和日志使用宿主 spill 服务保留完整展示,存储失败回退原文;canonical 程序值不受预览影响。
|
|
90
90
|
|
|
91
91
|
组件和接入证据见 `test/capability-plan.test.js`、`test/capability-statistics.test.js`、`test/capability-environment.test.js`、`test/plugin-program-bindings.test.js`、`test/context-result-preview.test.js` 与能力设置 Client 用例。固定 7 项普通任务加 1 项 canary 的 Command Goat / DeepSeek V4.1 Flash 付费配对已运行并完成 16 份盲评。插件侧盲评总分为 correctness 22/24、efficiency 15/24、clarity 21/24,原生基线为 22/24、10/24、21/24;机器验收因原生基线 3 项超过预设调用或 token 上限而阻止。全任务 traffic 为插件 132,332、基线 340,712(普通任务排除 canary 后为 122,933 对 325,862),这是一轮单 replicate 结果,不构成普遍收益保证。
|
|
92
92
|
|