# Known gaps What follows is the honest boundary of this release. Every line is either absent, partially implemented, or verified only to a stated depth. If something is not listed here and not demonstrated elsewhere in the documentation, treat it as unverified. **How to read this**: wherever the repository describes a mechanism in the future or ideal tense, this file and the [mechanism truth table](optimization/truth-table.md) take precedence. The truth table labels every entry `implemented / partial / design only` and records each place where "the docs say A, the code does B". Mixing `current` with `design goal` in the same tense was this documentation set's worst habit. ## Not implemented - **The eight-state verification machine.** The ontology in [Verification ontology](verification-loop.md) describes a full state machine for verification objects. What ships is the subset the kernel actually enforces: hypotheses, observations, evaluations, evidence, and facts, with levels L0–L4. The richer lifecycle remains a design target. - **A universal L4 human-release gate.** A per-step/per-branch human release for L4 deliveries is implemented: the kernel refuses delivery without that record. A blanket gate over every evaluation is not implemented. - **Plan authorization is attribution, not a gate (a settled design decision, not an open one).** The review card **always** asks a person to approve (`CreatePlan` has no auto-confirm branch, and the third `confirmed_by` source `'autonomy'` is deleted) — the card itself is the gate, and only a human approval writes the `by='user'` stamp. But **being unauthorized does not block explicit delivery**: it only makes auto continuation `hold`, while `AdvancePlan` proceeds and back-fills `confirmed_by='progress'` in the same mutation (behaviour is authorization). In one sentence: an unapproved plan does not auto-continue; when you advance it explicitly, the first delivery records attribution as it happened. The kernel messages, the runtime card and the prompts say this one same sentence. - **The four external origins: human upload / file drop / callback / pull.** The level table (L4) lists them, but **the observation object has only `self` and `scout` today** — the type declares only the values that really have producers (a value without one is a lie in the type). When those four entries actually land, come back and add the values to the declaration. - **~~The ontology tab and graph editing (stages D–E)~~ (shipped in 0.2.0; the bullet is kept as a record of the stale claim).** The graph band, ontology/entity views, concept filtering, in-place assertion chips, the conflict row, the vocabulary maintenance zone, and graph editing through the human-gate route (same criteria, same ledger as the model's verbs) all have UI now. See stages D–E of the [development plan](optimization/domain-ontology-plan.md). - **No cross-project vocabulary reuse.** Entries are **project-scoped** (they travel with the workspace ledger), so a new project starts its vocabulary over; a user-level "my vocabulary" library is a separate thing this version does not do. - **Workspace read faces have an owner — fixed for spawned children, open for fresh primary sessions.** `domain.md` and `facts/INDEX.md` are workspace-level read faces, and spawned children (evaluator / scout / executor) structurally cannot write them any more: the ownership check lives inside the write functions (`isSpawnedChild`, from the host session header). Before the fix, a child's own pre-step re-laid the shelf from its **empty** projection — which is exactly why one long run had the evaluator twice reading a 7-line placeholder while the primary line read 96 lines / 21 terms three times in a row, both sides stable: the file oscillated with whoever laid it last, and nobody was lying. What remains open: a **fresh primary session** in a workspace that already has a shelf still re-lays it from its own (empty) projection — the deeper cause is that the fold is session-scoped while the shelf is workspace-scoped; making vocabulary workspace-scoped is an architectural change recorded here, not done. - **The graph is rendered by React Flow (@xyflow/react 12), but only one real-browser walkthrough exists.** The dependency is not resolved from npm — the client half is loaded by the host module system, whose `require` knows only platform seed words and registered rows — so `tools/build-vendor.mjs` bundles it into **a module row of this plugin** (with its required `style.css`), and the build concatenates it with the main file into a single `lib/client.js`. Upgrading @xyflow/react therefore goes through that same path (`npm run vendor`), not just package.json. The vendor artifact is generated, not committed, and both dev-time suites **skip honestly** rather than fail when it is absent. **A real-browser check now exists** (`npm run check:browser`): real Chrome + real React + the real vendored React Flow, mounting `GraphBand` through the host's loading contract and asserting the canvas, nodes, edges, Controls, MiniMap and injected styles are all present. It guards the **loading and component-shape** classes of failure (both real-machine failures were of those two kinds), not the visual feel. **Not yet checked**: link labels and the MiniMap in a real browser at high node counts, touch gestures, and how the open-workspace `fitView` actually feels (it waits 50ms for the library to mount before fitting). - **Knowledge preflight has real-run evidence, but only from one session.** The preflight line ("relevant known, directly referenceable") appeared 29 times in that session's log; the task explicitly instructed "do not run any query tool first", and the model still registered vocabulary and promoted a complete fact autonomously. But this is **one sample**: whether the preflight is still read at turn 40 of a long run, and whether the hit rate stays useful as the vocabulary grows, are unanswered. - **The knowledge gate (`claims_untyped`) has never fired in a real run.** Triage and gap visibility have real-run evidence: in one of three sessions the model, after seeing the gaps on its card, **proactively** registered a concept and a predicate, revised the goal via `SetGoal` to attach the assertion, and promoted a complete fact carrying `hypothesis` / `level` / `evidence` / `scope` / `assertions`. But the gate **never blocked anything** in that session — the other two had nothing to block (no proposition reached `promote_at_level`). Both branches of the gate (block / pass) are **covered only by unit tests**, with no real-run sample; the risk that "the model might coin a word just to pass the gate" is also unobserved. - **The behaviour change came from "gaps visible + preflight proactive", not from "gate blocking" — all three stay.** In the measured runs, what actually drove the model to register vocabulary and attach assertions was the **actionable** gap readings and the relevant-known list on the runtime card; the gate is the backstop against bypassing, not the driver. This ordering is the reverse of the design assumption, and is recorded here as such. - **Machine evaluator self-running.** An independent evaluator is dispatched by the kernel when admission sets `needs_audit`. That evaluator runs as a real sub-session; there is no separate background re-evaluation loop. - **One real-browser walkthrough is now done (depth stated precisely).** Clean install + real Chrome + deepseek-flash, one full session: the preset switches, the native review card appears, a human Approve lands the stamp as `by='user'`, the main chain runs to completion (including the independent evaluator sub-session and two fact promotions), `/goal` renders from the native menu, and the Deliverables/Facts/Worldlines/Skills·Memory panels draw (screenshots in `docs/shots/browser-e2e-*.png`). **Not yet checked**: panel interaction details over long sessions (provenance jumps, proposition expansion), browser rendering of `/evidence` `/worldline` `/plan-review`, and the English UI. Also, **the README panel screenshots need retaking**: the ontology view (graph band / shelf / chips / conflicts) is a new surface absent from all four existing shots; the old "facts" copy has also changed. - **A sub-run's conclusion reaches the ledger and the model through the kernel itself, never through the runtime's best-effort delivery (settled design, not open).** All four sub-run kinds (scout, worldline executor, evaluator, arbitration reviewer) go through one-shot `subagents.start()` handles: the ledger only trusts the `run.result` this process holds, and the conclusion text reaches the model in the tool return of the collecting call. The native settlement notice is a bonus at most — it is best-effort, and in a live process the kernel could not reliably see it through either the projection or its own session log, so it is **not load-bearing**. - **Recovering a sub-run's conclusion after a process restart is backed only by unit tests.** The child-log recovery path (`recoverFromChildSession`) exists as a slow path, but in the deployed form it never produced a conclusion (the observed executor settle came from the live handle). **There is no end-to-end evidence for restart recovery.** - **The one-shot form has no next turn.** If a sub-run settles after the parent's last collection point, the ledger cannot record it; the long-run invariant reports it honestly as dangling rather than pretending it was collected. The resident form has a next turn and is not affected. **Turn-end records the workspace, not "who is still in flight"**: on the host's `agent/turn-stopping` (dispatched serially just before the turn stops) the kernel commits this turn's writes as a workspace ledger snapshot (`git/snapshot`) — it **judges nothing in flight, writes no verdict and wakes nobody**. A turn that ends **by error** (provider connection failures, retries exhausted — the most common way a one-shot run dies) takes a different path: the host does *not* dispatch `agent/turn-stopping` there, so the kernel watches `agent/error` too and the snapshot still lands. "Who is still in flight" is asked of the host by the **next turn's run-state card** (`subagents.listChildren`), never inferred from this closing observation — that would be an inference, not a fact. What remains uncovered is a hard kill (SIGKILL / timeout): that beat never runs and only the dispatch facts survive. - **A goal-axis audit can still be left dangling: when the process is hard-killed, an `audit/dispatched` has neither an `audit/settled` nor any other closing record.** The confirmed half is fixed: `runEvaluator`'s three `unknown` exits (evaluator failed / did not finish normally / **failed to write the audit card**) no longer leave only the dispatch fact — all three push `audit/settled{verdict:'unknown'}` (a bad ending is still an ending), and `sweepEndedAudits` first **recovers the verdict from the child's session log** (the same path scouts use, via `recoverVerdictFromChildSession`), recording unknown only when recovery fails; `ended_uncollected` and `lost` are written as two different things, and the settlement text no longer gives advice ("re-delivering dispatches a fresh evaluator" is gone — whether to retry is a plan-level decision, not the ledger's to make). What remains is a **hard kill / a vanished process**: nothing executes that beat, and only the dispatch facts survive. - **Settlement now has a host-event channel (`subagent/end`), and it is still only in-process.** The event is emitted by the service that owns `run.result`, on the same promise settlement (success and failure alike), so it arrives even when our handle is gone; once the process is gone there is nothing, and child-session log recovery is the remaining path. - **The resident (web) form has not been re-verified since this refactor.** The real-browser run predates the unified lifecycle; this round changed settlement and delivery, so the browser-side continuation window and panels need a fresh acceptance run. - **Heavy long-run evidence stays local.** Workspaces and raw session logs live in `~/.dsh/e2e-archive/` (not committed, to keep the repository small); what is committed is the decoded trajectory, the structured result and the provenance. A fresh clone cannot obtain the raw session logs without re-running. ## Verified only to a stated depth - **Domain-vocabulary validation stops at *shape*.** Value form, subject domain, range, `is_a` cycles and intra-fact self-conflict are all checked before anything lands; dimensional conversion and numeric tolerance are **not**, formula semantics are **not** parsed, and whether the file behind a `code` form exists is **not** checked (that is the filesystem's side, and doing it would make the fold touch I/O). - **Conflicts are surfaced, never adjudicated.** When two un-retracted confirmed facts land on the same single-valued predicate, the same subject and different objects, the projection reports one conflict pair: it **retracts neither side**, judges neither true, and enters no gate. Handling one goes through the existing human gate (`fact/reviewed`: retract or keep). - **Instances in the entity graph are neither registered nor de-duplicated.** Instance nodes are projected out of assertions, and the same label is the same node; entity resolution (two names for one thing) is not done. - **Old facts cannot gain assertions.** An assertion lands only at promotion — facts promoted before 0.2.0 (and any fact written without assertions) show as "unstructured" forever and are never rewritten retroactively. - **The ledger's coverage is per turn, not per write.** A snapshot is taken at each turn boundary in which this session wrote something, so exploration output is inspectable and restorable. What it does **not** provide is attribution: the kernel cannot see what `bash` produced, so the in-workspace git history never says which write belonged to which tool call. The designed per-write attribution ("every write enters the ledger, with turn/tool attribution") is deliberately not claimed. - **Fact-row navigation.** Clicking a confirmed fact to jump to the step that produced it is verified at the node-render level, not yet in a live browser on a session that actually promoted a fact. - **Plan review wording.** The human-gate wording was corrected in code and covered by tests, but the corrected copy has not been re-read in a live browser. - **The packaged client bundle in a browser.** Running the plugin from `dsh plugin add` in a browser was, until this release's verification pass, untested. That pass found a real defect: the client bundle registered itself under the old package id, so the browser refused to register the plugin at all and every panel silently disappeared. It is fixed, and the check is now part of release verification. ## Structural limitations - **The soul map is maintained by hand.** [Soul map](soul-map.md) maps each principle to the mechanism and test that carry it. There is no mechanical equivalence check between the written constitution and the implementation; the map can drift, and only review keeps it honest. - **Some principles remain preferences.** Where a constraint lives only in prompt text and not in a mechanism, the map says so rather than implying it is enforced. - **The case documents are illustrations, not evidence.** [`docs/cases/`](cases/) describes how the loop behaves on three kinds of question. They are not a benchmark suite, and no run records are shipped with this library. ## Open decisions - **English UI and English prompts (partially corrected, aligned 2026-09).** The install-side CLI (`doctor`, `install`, `seed`) still prints Chinese, and the browser panels still use Chinese as their source text (English tables exist but have not been proofread screen by screen in a real browser) — so a non-Chinese user may still get a Chinese interface. **The half that is already fixed**: the preset prompts **no longer require Chinese throughout**. They now say "follow the user's current language, one language per turn" (the language subsection in `preset/plugins/prompts.js`), so "the prompts lock everyone into Chinese" is no longer true. One design decision remains open: which language the CLI prints in. (The panels can reuse the native DSH locale service; that mechanism is already in place.) ## Before you can run it - **pnpm is a prerequisite — and it is DSH's, not ours.** `dsh plugin …` forwards to pnpm, so an executable `pnpm` has to be on `PATH`; without it the CLI stops with `pnpm not found on PATH` and no profile can be managed. Install it directly (`npm install -g pnpm`, or your system package manager). `corepack enable` is the tempting shortcut, and it is not an install: it drops a version **router** on `PATH` that fetches a pnpm the first time it is invoked. Corepack 0.34 — the one Node 24 ships — launches pnpm by looking for `bin/pnpm.cjs`, which pnpm 11 onwards no longer ships (`bin/pnpm.mjs`, then a native binary at the package root), so it can fetch a version it is unable to run; and its shims can sit earlier on `PATH` than a pnpm that already worked, shadowing it. - **The DSH CLI is what adds the plugin, and it ships in the npm package `@deepseek-ai/dsh`.** Starting the harness with `npx` does **not** put `dsh` on your `PATH` — that copy lives in the npx cache and exists only for that one process. So either borrow it for the install (`npx @deepseek-ai/dsh plugin --profile web add clearai-dsh`), or install the CLI once with `npm install -g @deepseek-ai/dsh`. - **Restart `dsh web` after installing.** The host half and the client half are both cached inside the running process; refreshing the browser is not enough, and a process that keeps running while its package is replaced will serve a broken client bundle. ## Fixed after the first release - **A gate could be impossible to open.** The plan review is a real gate: only a person's approval writes the authorisation mark, and the kernel refuses to start work without it. But the review card was raised **only** when a plan was created — so after a person chose "revise first, then resubmit", the model revised the plan and there was **no entry point left** to present it again. The plan stayed unauthorised forever while the kernel correctly refused to work. A gate that cannot be opened is worse than no gate: it turns a mechanism into a dead end. Since 0.1.2 `AmendPlan` and `RefinePlan` present an unauthorised plan again automatically, and `RequestPlanReview` is an explicit entry point for either the model or a person to re-present it. Only approval writes the mark; every other outcome still writes nothing. ## Operational caveats - **Worldline branches live in the ledger, not in your folder.** When the workspace is not a git repository, the kernel keeps a bypass ledger repo under `$DSH_HOME/storages/clearai/ledger/` and treats the workspace as its working tree. A fork's branch and worktree live **there**. Deleting or garbage-collecting that directory orphans live forks: the plan still shows an open fork, but its branch and working copy are gone. This used to block the whole plan: the fork had been recorded as a git fork, so convergence kept trying to merge a branch that no longer existed. Since 2026-09-12 the kernel checks whether the branch ref still resolves and the working copy still exists **at convergence time**; if not, it records the adoption, records that no merge happened, and leaves placing the winner's artifacts to a normal delivery. The plan no longer stalls on an object that can never converge. - **Merging is how adoption lands the winner's files.** A worldline is an independent working copy on its own branch; adopting it means bringing it back into the workspace. DSH has no workspace-branching primitive of its own, so this layer is ClearAI's, layered on the harness's native subagents and session log. When there is no usable git context at all, the kernel does not merge: it records the adoption and leaves placing the artifacts to a normal delivery.