# Architecture Context Guard separates DSH-owned execution from Guard-owned certification. ## Responsibility boundary | Module | Owns | | --- | --- | | DSH Goal | Persisted objective, automatic goal rounds, pause/resume/block/complete | | DSH Todo | Lightweight current plan display | | DSH Compaction | Model-visible history reduction without corrupting tool-call structure | | Context Guard | Per-item task contract, revisions, evidence binding, and completion certificate | Context Guard does not own Goal, Todo, Compaction, or continuation. It intervenes when completion is claimed without a valid certificate. ## Durable state model The effective plugin configuration and the DSH Session append-only log are the inputs to the rebuildable Guard projection. Context Guard **appends no custom session event types**: the persisted event vocabulary is harness-owned and the current persistence layer refuses unknown event types. The `activation` configuration supplies the initial enablement state, while all later session state is derived from the natively persisted events DSH already writes: - effective plugin configuration — initial enablement (`opt-in` starts disabled; `always` starts enabled before log replay); - `command/run` — later enablement (`/context-guard on|off|clear`) and epoch transitions; - `user/message` — captured contract clauses; - `tool/call` + `tool/result` — bounded evidence and completion-certificate attempts; - `tool/ptc-dispatch-start` + `tool/ptc-dispatch` — evidence for sub-calls dispatched from a `run_code` program. These are the current names; the retired `tool/code-dispatch*` vocabulary is ignored and mints nothing; - `compaction/summary` — the compaction cut that re-arms the recovery reminder; - `goal/change` — the recorded Goal reference, revision, and phase used by the completion gate. Everything else in the DSH log is deliberately ignored. In particular a V3 `system/message` is a plugin-sourced surface node rather than root authority, a compaction checkpoint `user/message` stays plugin context, and `request/header`, `request/context`, `assistant/attempt`, `session/end-seed` and raw `assistant` streams carry no contract state. The in-memory `GuardProjection` is a rebuildable cache: `deriveProjection` applies the effective activation configuration, replays the native log deterministically, recomputes every contract, evidence, and certificate, and flags `corrupt` when a recorded certificate no longer re-derives from the evidence in the log. The projection and its evidence are session-scoped: a new DSH session starts a new projection and cannot import, look up, or certify evidence IDs from another session. A completed workflow that needs a certificate must therefore produce its evidence and call `context_guard_checkpoint` in the same session. Under `always`, replay begins enabled; changing an existing profile from `opt-in` to `always` can therefore bring earlier persisted user messages into the derived contract. A recorded `/context-guard off` disables capture from that point until a later `on`. A recorded `/context-guard clear` supersedes every pending requirement and acceptance under a `CLEAR:` sentinel (prohibitions are retained) and bumps the contract revision, so a fresh empty-binding checkpoint can certify while the guard stays enabled. Captured contracts always carry a concrete subject/surface, so no unrelated evidence can close a requirement. ## Session lifecycle: silent start, first-message activation The runtime separates what it injects from what it certifies. An internal lifecycle phase — `armed` (protection enabled, waiting for the first real root input), `active` (real input entered a step), `disabled` (explicitly off) — expresses the startup strategy only; certification still depends solely on durable events, the current contract, and the evidence chain. Session start (`agent/session-start`) registers tools and the runtime, reads history, and arms recovery for resume/compact. It appends nothing, so a fresh session stays blank (`seq === 0`) and a DSH preset can still be selected while the session is new. The Web surface treats a session as blank until a `turn/start` (server preset lock) and falls back to `seq === 0` for cold list entries; Guard writes no message at T0, so both stay satisfiable. Guard reads history only through the DSH Session V3 `snapshotEvents()` API and refuses a session that does not expose it, so no V2 log can be projected as if it had V3 semantics. At `agent/pre-step` the loop has CLAIMED this step's input but has not persisted it yet. A pure preview reads only the validated claim: under `always`, the first claimed batch that carries a real root user message (non-empty text, or any image/attachment part) injects the `Context Guard protocol boundary: v4.0.0` notice and a compact first-step guidance message ahead of the claimed messages, inside the same step batch. The formal projection never derives from the preview — the boundary and the user message become durable events when the loop appends the batch, and the contract is re-derived from them exactly as from any other log. `opt-in` never auto-injects; delegated/subagent sessions receive no boundary, guidance, or activation (their scope arrives through the parent's delegation prompt). Rejection, cancellation, or a filtered batch injects nothing and retries on the next legal input. Recovery packets keep the existing content-dedup and ride the next entered step. The first 0.5 write into a pre-0.5 session carries the v4 boundary with it — that message is the explicit version cut: obligations and certificates recorded before it keep the frozen 0.4 rules (including exact-shape rebind replay), while later messages use the 0.5 confirmation grammar. ## Unified diagnosis and preparation `deriveItemDiagnosis` is the single pure judge shared by checkpoint, recovery, the rebind item query, `context_guard_prepare`, and `/context-guard status`. Per item it reports a task kind (`inquiry`/`action`/`constraint`/…), certification support (`unsupported`/`needs_target`/`needs_evidence`/`unavailable`/`supported`), repairability (agent-repairable, needs user input, unsupported, historical gap, none), the exact missing target fields and evidence facets, and one concrete next action with a resume condition and a stable attempt fingerprint. Inquiries stay captured obligations whose honest next step is to deliver the answer; they are never prescribed a rebind. An effect recorded without its resolution prestate is a historical gap: read back observed state, never re-execute to mint evidence. 0.6.2 adds the shared capability projection beside that verdict (`src/domain/capability-semantics.ts`). Every lane declares one gap kind — unknown interpretation, missing target, missing adapter, legacy migration, missing historical pre-evidence, unattributable operation, pending condition, pending delivery, unavailable host, or standing constraint — and exactly one reachable remedy. Every consumer reads that projection instead of inferring a root cause from one enum, which is what stops a capability this build lacks from being reported as a user-authorization gap. Shell facts additionally carry a layered reading (`processFacts`): the host tool's own return, the console's declared exit status or `unknown`, how far the effect was attributed to this obligation's operation, and the resulting business outcome. Those fields are derived at replay, are excluded from every digest and certificate domain, and never rewrite a historical `outcome` or `parseStatus`. `context_guard_prepare` is read-only. Before a stateful action it renders the supported command shape from the same audited parser the executor uses, the required resolution/effect/state order, reusable evidence references, the host capability verdict, and the exact missing target fields. It performs no action and never turns a guessed default into user authority. 0.6.3 adds one shared compatibility judgement (`src/domain/compatibility.ts`) that both preparation and the mutation gate call for the same item, action, revision and target. It separates four different facts — semantic compatibility (does the action belong to the item's reading?), target compatibility (is the resolved target the one the obligation selected?), execution readiness (pending, authorized, free of an outstanding condition or constraint), and adapter capability — and returns `compatible`, `incompatible` (the assumption is not what the obligation records, with the item's own action named) or `blocked` (the assumption matches but this snapshot cannot execute yet). A caller-supplied action manual is returned as `recipe_only`, and a caller-supplied target the obligation did not select is reported as a proposal, never as authority; the side-effect entry still re-reads every fact, evidence, host lock and identity before acting. The same release adds a pre-terminal eligibility pass: before any terminal filtering, records that earlier versions closed as answered are re-read, and one whose own text still orders work or whose git target has no auditable source is marked `needs_review`. That mark preserves the historical status byte for byte, re-executes nothing, and blocks a new certificate and Goal completion instead of warning about them. Requested-target provenance (`targetSource`) is recorded beside the target itself: an explicit name or path, a phrase that selects the current repository, a trusted host selection, a unique selection inherited from another obligation of the same work unit, or the environment default. Only the first four are user selections; the environment default is context that can resolve an allowed object but can never authorize one. A log-derived retry ledger keys each rejected rebind attempt by its stable inputs and outcome. An identical second attempt returns `unchanged` with the resume condition instead of a fresh rejection; new related evidence, a new root instruction, or a changed target produces a new key and re-opens evaluation. ## Synchronization The runtime rebuilds the projection from the log before each step. Before evidence is produced, the runtime awaits `ctx.sessions.flush(session)`; if no durability listener participated, evidence is marked `durability-unknown`, which fails closed. The host lock uses `dsh-core/v1`: one exact 33-package DSH core, independent of optional market versions. The resolver walks all reachable runtime/profile dependencies, including those introduced by plugins, and checks installed manifests against lock identities. Missing, duplicated, mixed or changed core rows fail closed. Every replay rechecks the configured graph sources against the injected core identity before deriving certificate authority. Core manifest version 2 and its policy/cohort values change the digest without changing the shared digest-v3 encoder. Legacy injected configuration requires explicit inspection and migration; historical records are never relabelled. Action capabilities are evaluated only after this core contract succeeds. Market restart additionally requires a trusted loaded-instance verifier. Current DSH supplies none, so restart is unavailable while unrelated guarded work remains supported. A requested restart stays pending. The version-2 service adapter distinguishes ordinary provider/instance drift from a persisted, target-bound restart handoff. Evidence is produced only from persisted `tool/call` + `tool/result` pairs. Guard never inserts context between a Code Mode sub-call and its durable result. ## Domain pipeline 1. `classifyUserInteraction` drops session-layer utterances before capture: bare progression/acknowledgement phrases (`继续`, `continue`), meta questions (`这个收尾具体要做什么`, `是不是bug`), and meta comments/objections. The classifier fails closed — an artifact path, an explicit method, or a non-negated operation verb always keeps the message (or the individual clause inside a mixed message) a captured instruction. 2. `classifyClause` / `captureClause` classify the remaining direct human message into requirement, acceptance, or prohibition. 3. `segmentAuthorityBlocks` keeps quoted/code/reference material outside the contract, supports explicit section adoption, and captures uncertain material fail-closed. 4. `context_guard_evidence` produces read-only resolution/state facts and validates already persisted effects; `context_guard_action` owns the explicitly mutating install/apply/restart/publish and Git paths. Before any executable, command, HTTP request, or durable intent, the action tool flushes and replays the resolution/contract chain, then runtime authorization binds the action to one exact pending root-owned requirement/revision (never a prohibition or acceptance), rejects any matching pending root prohibition, and rechecks Guard integrity, host identity, semantic action, target-capture status, and every action-specific requested/resolved identity field. Exact package version/profile/registry and explicit Git remote/full refspec are required; resolution-only integrity, boot generation, and Git OIDs remain bound by the target digest and live prestate. `evidenceFromPersistedToolResult` maps persisted results to versioned semantic actions, evidence roles, exact resolved targets, observed state, executable identity, immutable resolution-time expected transition, and bounded diagnostics. Only exact adapter and command-manifest IDs/versions in the shipped manifests may supply structured state facts. On Windows, audited `.cmd`/`.bat` shims are version-probed through a closed invocation whose interpreter is pinned to the canonical `SystemRoot\\System32\\cmd.exe` realpath and version. Resolution and effect bind both identities; execution reuses the revalidated paths and rejects shell-control or expansion characters rather than re-resolving the logical name through `PATH` or trusting a changed `ComSpec`. Modify transition construction first re-hashes the current source bytes against the resolved pre-digest, then applies the pinned unique UTF-8 replacement. Commit readback parses the raw post-commit parent tuple and accepts exactly one parent equal to the resolved pre-effect HEAD; fetch freezes that HEAD and requires the post-effect HEAD to remain identical. Pre-execute revalidation is a correctness gate, not isolation from a concurrently malicious process running as the same user; a mismatching post-action readback remains non-certifiable. 5. `certifyCheckpoint` flushes and resynchronizes first. Non-stateful actions require an action-compatible minimal facet; every action in the ten-member `STATEFUL_ACTIONS` set requires distinct resolution, effect, and independent state evidence, one shared resolved target, non-overlapping state observations, and an expected-transition predicate frozen and digested by the resolution fact before effect. The binding must reproduce that payload exactly before observed state is compared. 6. A certificate freezes protocol/certificate versions, epoch, `sessionRefDigest`, `hostLockDigest`, contract revision/hash, explicit null-or-current Goal ref, open/evidence/binding digests, and the final certification digest. The tool computes a candidate without mutating runtime authority; only a persisted tool result that re-derives byte-for-byte becomes current authority. 7. `goalCompletionDenial` rejects the Guard-owned `update_goal(action=complete)` model-tool path before the Goal mutation unless that certificate is current. A trusted in-process producer can bypass the tool guard; replay detects the durable `goal/change complete` without a certificate and records an integrity violation, but the plugin does not claim to prevent or roll it back. 8. `context_guard_boundary` persists only qualified `user_wait`, `external_wait`, or `deferred` candidates. After the accepted tool result is flushed and replayed, the post-commit path reads the same active/armed Goal ref, calls `goals.disarm(agent)`, and independently reads the same ref as disarmed. Pre-effect, still-armed, and post-effect-unknown failures have separate fail-closed/fail-loud outcomes. 9. `decideTurnBoundary` reads only structured Guard/Goal state. Assistant completion prose is diagnostic and never a stop or continuation control source; active/armed continuation belongs to the Goal Round Driver. 10. `renderRecoveryPacket` re-injects open requirements after compaction, resume, enable, rejection, or integrity loss. Injection is content-deduplicated: a re-armed packet with unchanged content is injected once, while resume, compaction, an enablement transition, relevant new evidence, or a new contract revision changes or forgets the digest. Unrelated historical evidence does not re-arm the same guidance. ## 0.4.2 capture and rebinding A legal plugin `user/message` notice marks the 0.4.2 capture boundary. Earlier events keep the 0.4.1 clause segmentation and visual classification; the runtime does not rewrite old source events, pending items, evidence IDs, or historical certificates. New compound requests separate explicitly named actions and retain GUI/visual acceptance as non-certifiable work. Updating a package and recording a change do not automatically mean installing or committing it. `context_guard_rebind` accepts `operation: propose|query|withdraw`, `item_id`, `proposal_id`, `clauses`, and optional `clarification_item_ids`. It has no model confirmation flag. A proposal's 1–8 clauses must concatenate to the complete original normalized text, and its serialized body must fit 8 KiB. Each clarification ID is either empty (keep the source clause) or names a later pending direct-root item that explicitly includes that clause and preserves captured identities and method constraints. The proposal records both root sources, item/revision identities, session, epoch, contract revision, candidate action/target/acceptance, and a digest. A GUI clause cannot be mapped to an installation-only result. The tool returns a candidate without mutating the projection. A matching durable tool result registers the proposal on replay. Replay validation is versioned: 0.5-era results match on structured semantic fields (status, reason code, proposal identity and digest) while display text may evolve; results carrying the frozen 0.4 response shapes validate byte-for-byte against the frozen 0.4 rules. Anything else is tampered or unknown and never replays. A proposal that would keep every clause at `generic_run` when the original is already `generic_run` is refused as `no_certification_gain`: it would spend a user confirmation without improving certification. Partition mismatches return the bounded exact source (text, length, digest) instead of a generic error. Since 0.5.0 a durable root message is one atomic confirmation transaction. The first non-empty top-level line may be the control line `确认重绑定 `; the confirmation validates against the state BEFORE that message, and the remaining lines keep their own semantics — an explanation request stays conversational, a new task is captured like any other instruction, and an explicit reversal ("先不要确认") makes the whole message ambiguous and unapplied. A control string buried in a sentence, inside quotes, or inside a code block is malformed and never confirms; a matching control line that is not in first position, or two control lines, is ambiguous with no partial effect. Confirmation rechecks the proposal against the current contract; changed, withdrawn, cross-session, or non-durable inputs keep the old item pending, and a matching confirmation observed in a not-yet-durable log is reported as `not_durable` — distinguishable from one that was never sent. Every child is constructed before the original is marked superseded. `supersededByItems` preserves the one-to-many relation, and `reboundFrom` preserves the proposal and confirmation source. A later root clarification keeps its own authority; partitioning alone cannot authorize a new mutation. Duplicate confirmation is idempotent. No digest-v3 wire format or upstream fixture changes are needed. A replacement changes the item/status set and contract revision, invalidating the earlier certificate; evidence is never copied into a passed state. A fresh checkpoint must revalidate the exact action, target, executor, host lock, and transition. Evidence predating the authoritative root clause referenced by a replacement is rejected while retaining its historical ID. ## 0.6.0 semantic layer The v5 protocol boundary (`Context Guard protocol boundary: v5.0.0`), written with the first real root-input step of a new session, activates the semantics below. A session that never wrote it keeps the whole-session contract, version-1 certificates, and the frozen digest domains; a session that wrote it keeps pre-boundary obligations under their birth rules while new work uses the v5 rules. `/context-guard migration` reports which of the two is in force. | Module | Owns | | --- | --- | | `domain/spans.ts` | UTF-8 byte offsets and half-open spans of the original root text | | `domain/host-selection.ts` | Trusted question round-trips; a selection is only ever a paired call + result | | `domain/work-unit.ts` | Derived units, delegation lineage, ancestor/descendant relations, explicit switch and delegation vocabularies | | `domain/closure.ts` | The single open-closure implementation: visible pending, certifiable open, unit closure with required descendants, and the ancestor constraints in force | | `domain/delivery.ts` | Trusted answer delivery: the four-part composition criterion over durable turn structure | | `domain/proof.ts` | v1 and v2 proof manifests, the v2 capability matrix, and the binding/refusal rules per kind | | `domain/release.ts` | Explicit release contracts, one-shot reservations, readback settlements, coverage surface, and the pre-effect gate | | `domain/reason-class.ts` | The frozen seven-class mapping for every reason code | | `domain/migration.ts` | Rule set in force, preserved identities, and the rollback precondition | | `domain/diagnostics.ts` | The single per-item judge, now carrying its reason class | ### Delivery A delivery fact exists only when the host's own log says so: an `assistant/message` for turn T at that turn's highest step, with no `interrupted` marker, followed by `turn/end { turn: T, reason.kind: "completed" }`. `assistant/attempt` records, aborted or errored turns, other turns' replies, and delegated sessions never bind a delivery. Delivery closes only information-slot obligations captured inside that turn (including those of a delegated sub-unit created in it), and it proves delivery only — never accuracy, sufficiency, or execution. ### Units and closure Units are derived from the durable message stream, never written. A delegation-marked root message opens a CHILD unit: the parent stays the current unit, so delegating a sub-task cannot drop the parent's own work, and the child's open obligations join the parent's closure as required descendants. An ordinary task switch opens a sibling, whose residual work deliberately does not block the newer task. A delegated tool round-trip is recorded per unit and its evidence is marked bounded, so a subagent's result is visible and auditable but can never close a parent obligation. Certifying a unit also refuses any binding that an ancestor unit's prohibition or unsatisfied condition still governs. ### Proof v2 The v1 manifest and `ccg.proofManifest.v1` are frozen and read by their own rules. The v2 manifest (`0.6.0`, `ccg.proofManifest.v2`) binds each obligation to a current subject, a declared source, and a real operation, and covers eight kinds. Tool success is only ever an `execution_fact`; a visual readback needs a fact that carries a visual-readback capability and actually read the subject; an input asset check must be a prior-state fact; an external fact needs a completed external-operation reference; and a cohort with no producer reports `proof_producer_capability_unavailable`. Strict policy uses this same matrix to demand the proof the user explicitly asked for. ### Explicit release Contracts, reservations and settlements are persisted through the plugin-notice channel the host already writes, and each is idempotent by its own identity (contract id, call id). Reservations are written before the effect and record the SRI the trusted producer read; a settlement records a trusted readback when one exists and otherwise stays `unconfirmed`, which preserves the in-flight protection and refuses a re-send. Adoption comes only from the root `command/run`, and `release status`/`release revoke` are read-only and authority-withdrawing respectively — neither is a state corruption. The contract freezes the candidate scope's `adoptedAtRevision`, and the closure certificate must be the one that certified exactly that revision: the release instruction is itself a new obligation, so requiring the CURRENT revision would make publishing depend on having already published, while freezing the revision still refuses a candidate whose content moved on. Candidate identity is read from trusted producers only: the action tool reads the exact tgz (byte SHA-256, npm SRI, embedded `gitHead`, package, version, repository) and the canonicalized registry, and the runtime resolves a declared ref with the audited git executable. Each named identity is compared with its own observation. `context_guard_release` is the recovery entry: `status` is a read-only report, and `reconcile` reads the external identity through the auditing registry adapter and settles a reservation only when the readback names the frozen bytes. It never re-sends a release, and it works after a restart and for a revoked-but-in-flight attempt. `RuntimeExecutorSeams` exists so an acceptance run can replace the mutation executor, the HTTP client and the pinned host cohort; the release gate, records, producers and replay stay production code. ## Bounded queries and recovery Checkpoint certification runs over the complete contract before display filtering. Default pages contain at most eight current items/constraints and ten evidence rows. Each list has its own cursor, bound to the session, epoch, contract revision, query, bindings, and evidence snapshot. `history` includes unsupported evidence with an unavailable disposition; default evidence and binding templates share action/target matching. Unknown cursor state is an explicit query error. The serializer reserves metadata space before selecting rows and returns at most 12 KiB of valid UTF-8 JSON. Oversized rows identify omitted detail; `detail_id` retrieves bounded JSON-text chunks. Later chunks require the initial `snapshot` as `detail_snapshot`. A long ID may use a SHA-256 lookup token in the summary; its original ID remains unchanged in the detail. Explicit `item_ids` can inspect passed and superseded provenance as well as current work. None of these views changes the certificate's scope. Recovery reserves its rules, query pointer and folding totals before filling item summaries. Separate category slots retain a key prohibition and the newest pending requirement before optional rejection details or evidence; many constraints cannot hide the current work. Long IDs are summarized independently of the reason and next step. Small packets distinguish capability restoration from requirement rebinding. Its default budget is 4,000 characters; values below 512 or non-integers are rejected. The 512-character form keeps a constraint, the current limitation, and a query pointer. Full constraint enforcement remains in the ledger and execution checks. The runtime re-derives binding refusals from persisted calls and deduplicates guidance against the current contract and relevant evidence. A real compact/resume boundary always resets that context-local deduplication.