`dsh` shipped two config trees that were 43 rows the same: apps/cli/cordis.yml
composed web as 74 flat rows, while the TUI booted examples/tui-agent/cordis.yml
whose single `@deepseek-ai/dsh-tui-demo` row mounted twelve plugins behind a
twenty-key pass-through Config. Neither file was what its location claimed —
apps/cli hardcoded the "example" as the product default and the "demo" bundle
was the application — and every capability change had to be made twice.
- apps/cli/base.cordis.yml holds the 43 shared rows; tui.cordis.yml and
web.cordis.yml are patch lists stating only what differs per surface
- overlays apply as SIBLING patch lists at one include level, because include
patches never cross an include boundary. Precedence: base < surface <
(--config | personal ~/.dsh/config.yaml) < launcher flag/profile patches
- `--config` now applies an overlay INSTEAD OF the personal one, so a demo or
test tree never inherits the user's route; new `--config-replace` boots a file
as the entire tree (the old `--config` behaviour). Both survive /resume
- vendor/include: index each `insert`ed row as it is added so a later patch can
configure or disable it. Upstream built the id index once before the patch
loop, leaving every surface-only row — the whole TUI front door — silently
unpatchable from user config. Logged as local modification 8
- session identity moves to dsh-agent-loop's CONFIGURED_AGENT_IDENTITIES_KEY;
dsh-tui's MAIN_SESSION_ID_KEY is deleted (only the bundle read it)
- delete examples/tui-agent, examples/cordis-agent, packages/examples/tui-demo;
TUI tests → apps/cli/tests, cordis e2e → packages/cordis/tool-cordis/tests,
examples/code-mode survives as an overlay leaf
- `dsh web` gains --config, threaded into AppCLIEntry as an extra overlay
Three latent defects surfaced and are fixed here: the TUI captured the optional
sessionQuery service once at construction and could permanently disable /resume
when it won the mount race; the session-store root silently reverted to a
project-local ./.sessions; --config-replace was dropped by the resume handoff.
Verified by booting each tree through the real Loader (TUI 55 entries, web 75,
zero unsettled) rather than reading YAML. All eight terminal snapshots replay
byte-identically; 14/14 PTY smoke, 112/112 snapshots, 25/25 doc-sync, hygiene
and lint clean.
Three ui-conversation spec conflicts resolve to master's SlotTestRuntime
rewrites. Adaptation to the new outward session face: ISession gains the
command verb (the composer chip and the /permission picker submit through
it), FixtureSession grows the matching fail-loud stub plus the
waitingApproval summary default, and the picker reads the projection
through projections.faceOf (the ProjectionsFace shape) instead of the
retired store getter.
Two real branches gain assertions (effectivePermissionPreset's backward
scan over non-preset events; the popup options() throw when the projection
vanished between availability and open — a sync throw, not a rejection).
The empty ui-permission node half joins the ui-model entry on the
client-lane coverage debt list (same pure-UI plugin shape, same TODO).
gen-cordis-catalog/api, config/persistence catalogs, module graph, and doc
graphs pick up the ui-permission package, the permissions projection key,
and the /permission command; KnobState and PermissionSelect join the
type-link exemptions (owned by the permission package's own docs), and
ui-permission joins the sentence Model Experience allowlist (indirect via
the host command). The ui-conversation and dsh-permission READMEs gain
their zh halves for the approval/chip and projection/command paragraphs;
all three touched pairs re-record.
The read side becomes the 'permissions' session projection: src/types.ts is
the key declaration's one home (PermissionSelect = whole select: table
options in declaration order plus a current-only 'custom'), served through
./types and the ./client re-export. The unit folds the three whole-value
knob events (permission/preset, sandbox/mode, approval/policy) into a plain
KnobState and views the select over the composition defaults the service
already owns; current() shares the same derive step, so the fold exists
once. The write side becomes the /permission command (the /plan
registration shape): bare invocation reports the current preset and the
table, a preset argument switches through set() immediately — no turn
anchoring (knob events need no enclosure), no dedicated RPC. Both children
activate only when their registry is composed.
The session.permissions/setPermission unary pair, the PermissionOption wire
DTO, the client Session wrappers, and the fixture/fake mirrors all leave the
wire: the read side moves to the 'permissions' session projection and the
write side moves to the /permission command in follow-up commits, so the
web protocol gains no permission methods at all.
The pendingSwitches + prompt-submit flush + hasOpenTurn move also goes.
Knob events no longer need turn enclosure: the persistence scanner keeps
standalone events after the last turn/end as part of the preserved prefix
(remove-synthetic-log-only-turns), none of the three knob invariants demand
an open turn, and the setters append bare events. An idle switch commits
immediately; hasOpenTurn stays a user-approval private fold (its audit pair
is the one contract that still requires enclosure).
The old PermissionSelect chip and its mount-time fetch die with the RPCs
(the resident composer broke the mount-once assumption); the projection-fed
replacement lands with the Access seat swap.
The parent implementation introduced sandboxMode and approvalPolicy as generic SessionHeader fields, then propagated those fields through both persistence backends, session-query indexes, collision checks, policy-specific seed-boundary folds, catalogs, and a broad test matrix. That storage plane is unnecessary: Session already accepts a validated constructor seed, and persistence captures that seed when the session is announced before committing its first batch.
Capture each parent override synchronously at delegation, append source-tagged sandbox/mode and approval/policy records after the optional fork prefix, and create the child with that combined seed. Keeping header.seedLength at the original fork-prefix length preserves lineage while ordinary last-event-wins folds make the inherited records outrank stale parent history and remain subordinate to later child switches. Unswitched parents still stamp nothing, so children continue to follow deployment defaults.
Remove the generic header fields and every persistence/query/schema branch built around them. Collapse the inheritance suite from ten leaking scenarios to four owned-context cases covering real filesystem confinement, stale fork precedence, delegation-time capture, and the no-override path. The assembled headless snapshot now asserts the persisted inheritance event directly.
This keeps the security behavior while restoring policy ownership to the existing event log and deleting the speculative durability machinery that the original tests did not exercise.
39 conflicts resolved per the reattachment plan (missions worktree-projbiz
0728-1859): baseline wins for deleted packages (host/runtime, old ui/acp,
ui-sidebar Rows/tree) and retired specs; unions for wire-layer exports and
client summary fields; the approval takeover, waitingApprovals tracking, and
PendingApproval domain face carry over onto the master structure. The two
new host specs follow the runtime->apiproxy rename. Dead PR-side wiring
(ConversationInjected permissions/setPermission spread, InputBar controls
prop, boot.ts sandbox composition) resolves to master and its replacement
lands in follow-up commits.
Retarget the feature branch to the current master tip without rewriting its existing review history. Keeping this as a dedicated merge checkpoint makes the later simplification diff attributable to the stacked child rather than mixing base movement with design changes.
Resolve the identified-message API drift in the feature tests by constructing complete user messages, reading the nested tool-result message shape, and adapting the prompt-submit listener signature. Preserve both sides of the user-approval conflict: master’s createUserMessage wrapper and the feature’s inherited-policy attribution.
Regenerate the Cordis and persistence catalogs, re-record the session README pair, and refresh the affected ACP/headless fixtures so derived artifacts describe the merged source rather than either parent in isolation.
Validated with the focused policy/session/persistence/query suites (430 tests), focused ACP/headless snapshots (3 tests), build, doc-sync (25 gates), lint, hygiene, and git diff checks.
Lint: eslint --fix over the merge-crossed projection/command files (arrow
parens, trailing commas, unnecessary assertions), Extract<> replaces the
keyof-map & string intersections no-redundant-type-constituents rejects,
the fold-adapter's merge loop drops its non-null assertions for a
bounds-carrying cursor, one JSDoc line wrapped under max-len (api-catalog
regenerated). Snapshots: the four TUI goldens re-recorded for the merged
event-count shift (the durable command lifecycle adds one event to the
seeded diagnostics log). The headless advanced-toolchain snapshot passes
on CI and fails locally in this sandbox both with and without these
changes (30s child timeout — environment-bound, tracked in the ledger).