@treeseed/dsh-chapters 0.1.24 → 0.2.0
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.md +2 -1
- package/cordis.patch.yml +49 -1
- package/docs/architecture.md +1 -496
- package/docs/backlog.md +234 -4
- package/docs/contract.md +127 -1
- package/docs/development.md +248 -12
- package/docs/host-compaction-seam.md +108 -0
- package/docs/knowledge-repo.md +532 -16
- package/docs/project-state.md +323 -0
- package/docs/verify.md +217 -0
- package/lib/archive.d.ts +49 -1
- package/lib/archive.js +56 -2
- package/lib/archive.js.map +1 -1
- package/lib/arrival.js +24 -10
- package/lib/arrival.js.map +1 -1
- package/lib/attach.d.ts +46 -0
- package/lib/attach.js +120 -0
- package/lib/attach.js.map +1 -0
- package/lib/auto-save.d.ts +74 -0
- package/lib/auto-save.js +162 -26
- package/lib/auto-save.js.map +1 -1
- package/lib/client.js +413 -11
- package/lib/client.js.map +1 -1
- package/lib/commands.d.ts +8 -1
- package/lib/commands.js +135 -7
- package/lib/commands.js.map +1 -1
- package/lib/compose.js +25 -6
- package/lib/compose.js.map +1 -1
- package/lib/continue-core.d.ts +24 -1
- package/lib/continue-core.js +41 -9
- package/lib/continue-core.js.map +1 -1
- package/lib/diag.d.ts +47 -0
- package/lib/diag.js +141 -0
- package/lib/diag.js.map +1 -0
- package/lib/engine-core.d.ts +51 -1
- package/lib/engine-core.js +63 -7
- package/lib/engine-core.js.map +1 -1
- package/lib/engine.d.ts +93 -0
- package/lib/engine.js +477 -36
- package/lib/engine.js.map +1 -1
- package/lib/enrich-queue.js +28 -2
- package/lib/enrich-queue.js.map +1 -1
- package/lib/enrich-wire.d.ts +92 -2
- package/lib/enrich-wire.js +135 -17
- package/lib/enrich-wire.js.map +1 -1
- package/lib/enrich.d.ts +27 -2
- package/lib/enrich.js +114 -18
- package/lib/enrich.js.map +1 -1
- package/lib/index.d.ts +51 -0
- package/lib/index.js +180 -2
- package/lib/index.js.map +1 -1
- package/lib/indexing.d.ts +7 -0
- package/lib/indexing.js.map +1 -1
- package/lib/injections.d.ts +1 -0
- package/lib/injections.js +38 -0
- package/lib/injections.js.map +1 -1
- package/lib/last-resort.d.ts +24 -0
- package/lib/last-resort.js +27 -0
- package/lib/last-resort.js.map +1 -0
- package/lib/prompt.d.ts +130 -0
- package/lib/prompt.js +255 -0
- package/lib/prompt.js.map +1 -0
- package/lib/registry.d.ts +39 -1
- package/lib/registry.js +76 -9
- package/lib/registry.js.map +1 -1
- package/lib/render.d.ts +31 -3
- package/lib/render.js +75 -3
- package/lib/render.js.map +1 -1
- package/lib/repair-run.d.ts +49 -0
- package/lib/repair-run.js +423 -0
- package/lib/repair-run.js.map +1 -0
- package/lib/repair.d.ts +200 -0
- package/lib/repair.js +354 -0
- package/lib/repair.js.map +1 -0
- package/lib/rules-commands.d.ts +22 -0
- package/lib/rules-commands.js +177 -20
- package/lib/rules-commands.js.map +1 -1
- package/lib/rules.d.ts +184 -7
- package/lib/rules.js +312 -27
- package/lib/rules.js.map +1 -1
- package/lib/save-core.d.ts +31 -1
- package/lib/save-core.js +153 -17
- package/lib/save-core.js.map +1 -1
- package/lib/session-rules.d.ts +222 -0
- package/lib/session-rules.js +454 -0
- package/lib/session-rules.js.map +1 -0
- package/lib/store.d.ts +22 -1
- package/lib/store.js +94 -17
- package/lib/store.js.map +1 -1
- package/lib/sync.js +13 -1
- package/lib/sync.js.map +1 -1
- package/lib/tools.d.ts +14 -0
- package/lib/tools.js +311 -32
- package/lib/tools.js.map +1 -1
- package/lib/types.d.ts +12 -1
- package/lib/types.js.map +1 -1
- package/lib/version.d.ts +9 -0
- package/lib/version.js +10 -0
- package/lib/version.js.map +1 -0
- package/lib/warnings.d.ts +10 -0
- package/lib/warnings.js +18 -0
- package/lib/warnings.js.map +1 -0
- package/package.json +9 -3
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
# Project state — the history of what was built and measured
|
|
2
|
+
|
|
3
|
+
**Moved out of `AGENTS.md` verbatim on 2026-10-22.** `AGENTS.md` is loaded into every agent session
|
|
4
|
+
against a workspace instruction budget, and it had grown to 70,401 bytes — over the limit, so the file was
|
|
5
|
+
being silently TRUNCATED when delivered: the rules near the top survived and the ones at the bottom did not,
|
|
6
|
+
which is worse than a smaller honest file. This is the project's record of what happened, what was measured,
|
|
7
|
+
and what remains unverified. It is reference material, not a rule surface: **read it when you need the
|
|
8
|
+
history of a decision, not before every task.** The rules that lived inside it are in
|
|
9
|
+
`AGENTS.md` § Rig and metering rules, and the load-bearing spec stays in `docs/`.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Start Here
|
|
14
|
+
## 2026-10-22/23 — the host 0.2.0-rc.2 acceptance sweep (#56)
|
|
15
|
+
|
|
16
|
+
**What actually happened, in order.** The pinned host moved to 0.2.x; the tape corpus stayed at
|
|
17
|
+
0.1.5-rc.1, so `CORPUS_HOST` closed the acceptance chain and no e2e claim was current (#56/#57). While
|
|
18
|
+
re-anchoring it, seven distinct failures turned out to be **one root cause plus six real defects**: the
|
|
19
|
+
preset pin had been written to a row id the host retired (`agent-presets` → `agent-preset-registry`), so
|
|
20
|
+
every browser boot since the move ran the STANDARD realm — no `compaction/*`, no arrival artifacting, no
|
|
21
|
+
`chapters_rules`. The legs were measuring an absence. Fixing the pin is what made the other six visible.
|
|
22
|
+
|
|
23
|
+
**Defects found by measurement, each with red-first rows, on `feature/56-reanchor-tape-corpus`:**
|
|
24
|
+
- **#77** — the continuation door called `sessions.open`, a verb that does not exist; the real seam is
|
|
25
|
+
`uiWorkspace.openSession` (`docs/contract.md` § Client Sessions Service). Three evenings of "the client
|
|
26
|
+
refuses to catalogue plugin-created children" were a door that never switched anything, while the tool
|
|
27
|
+
reported success. The door now verifies the switch from live state and degrades to
|
|
28
|
+
`switched:false, note:…` rather than claiming it.
|
|
29
|
+
- **The flattened 0.2.x `tool/result` shape** silently disabled arrival artifacting chain-wide (the
|
|
30
|
+
probe decoded `data.message` at the top level, where 0.2.x puts `data.message.message`). Arrival
|
|
31
|
+
artifacted nothing because the predicate was never true — `src/arrival.ts` now takes both dialects,
|
|
32
|
+
with a row that rebuilds the assertion against the host's real event shape.
|
|
33
|
+
- **#79** — the composer ghost ran `command/run` on **every render** (measured 4–26 per session, into
|
|
34
|
+
the transcript this plugin archives) because a `useEffect` listed `[props]` under a comment claiming
|
|
35
|
+
"once per mount"; and it kept pulling suggestions from a disabled feature.
|
|
36
|
+
- **#78** — relief that declines while the surface is over the trigger said nothing. It now prints
|
|
37
|
+
`surface=/trigger=/window=/ratio=` plus the offending node count; observed live:
|
|
38
|
+
`relief declined … (surface=33442 trigger=16000 window=32000 ratio=0.5)`. The engine deliberately does
|
|
39
|
+
**not** override the operator's arrival floor (AGENTS calls that switch a behavior switch, and a rig
|
|
40
|
+
that reinterprets config is the same dishonesty as a pin that is collected and never applied).
|
|
41
|
+
- **#81/#84** — `typeComposer` decided "did my send work?" from the editor's own emptiness, which the
|
|
42
|
+
host clears asynchronously: one session received the same instruction twice (seq 40 and 47). Then the
|
|
43
|
+
guard, matching an 80-character prefix, **swallowed** a genuinely different second instruction. Delivery
|
|
44
|
+
is now decided from the durable log, by the full instruction, after a bounded grace — and a drift row
|
|
45
|
+
fails if any composer call decides from the editor again.
|
|
46
|
+
- **#85** — replay booted a **different realm than record** because the rig's regime lived in three
|
|
47
|
+
places (distill tables, the chain script's inline env, boot defaults) and Playwright project `env`
|
|
48
|
+
reaches only workers, so `globalSetup` used defaults. One table now (`tests/e2e/rig-pins.ts`), read by
|
|
49
|
+
both the config and the boot.
|
|
50
|
+
- **#87** — the deepest one, and the last to become visible: **relief shadows the newest human
|
|
51
|
+
instruction.** Measured from one session's durable log: 13 compaction summaries, 108 distinct shadowed
|
|
52
|
+
seqs, `STILL-HERE` sent eleven times, **ten of them shadowed away**, final assistant text empty.
|
|
53
|
+
`retainRatio` reserves a token fraction as a verbatim tail, so an oversized node in the tail consumes
|
|
54
|
+
it and the question disappears with no error anywhere. The fix is inside our seam and honors
|
|
55
|
+
invariant 1: `carryOpenRequest` appends the newest human turn **verbatim** into the summary we author
|
|
56
|
+
(`openRequestCarryMaxChars`, default 4000; truncation is stated, never silent; injected scaffolding is
|
|
57
|
+
excluded by the one canonical predicate, `isInjectedProse`).
|
|
58
|
+
|
|
59
|
+
**The instruments earned their keep, and four bugs lived in the instruments.** `pending` never
|
|
60
|
+
matched a JSON-escaped newline; `ids.size > 0` disabled every #77 candidate; a report helper resolved
|
|
61
|
+
logs against the import-time home so its own gate could never pass; an unbounded
|
|
62
|
+
`scrollIntoViewIfNeeded` hung a leg for 17 minutes; my own MISS dump was swallowed by its own `catch`
|
|
63
|
+
for a whole run; and a second injected-prose list was caught by the #59 drift row within minutes.
|
|
64
|
+
`docs/verify.md` now carries a release gate shaped by these failures ("an unexercised assertion is a
|
|
65
|
+
rumor of an assertion"), and `npm run typecheck:e2e` exposes **77 errors in a browser plane that has
|
|
66
|
+
never been type-checked** (#83) — after I subtracted the ones my own new code caused.
|
|
67
|
+
|
|
68
|
+
**Still open as this lands:** #86 (a session with the arrival floor off can livelock on one oversized
|
|
69
|
+
result — 89 requests in 20 minutes, three outcomes for the same leg minutes apart), #80 (plugin command
|
|
70
|
+
results are durable but render nothing operator-visible), #82 (a distill and the deterministic suite share
|
|
71
|
+
the corpus directory), #81 criteria 3-4, and the #78 A/B product decision, which is the maintainer's.
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
**Phase 0 passed.** Across nine probe boots on an isolated instance, a plugin created a session seeded with
|
|
75
|
+
exactly one synthetic `user/message`, and that session was **listed** by `sessionQuery.listSessions`,
|
|
76
|
+
**resumable** via `agents.resume({ resumeSessionId })` after disposing the create handle, **durable across a
|
|
77
|
+
process kill**, and its TOC text round-tripped **byte-identical**. Structurally the head is bounded: ~102
|
|
78
|
+
tokens of notice versus ~2,476 for the equivalent transcript. Full measurements and the seed contract:
|
|
79
|
+
[spikes/probe/FINDINGS.md](spikes/probe/FINDINGS.md).
|
|
80
|
+
|
|
81
|
+
**Phase 0b passed (rounds 12-17): the compaction seam is real.** A plugin-subclassed `BasicCompactionEngine`
|
|
82
|
+
with a deterministic `summarize()` ran both the manual (`/compact`) and automatic (`agent/pre-step`)
|
|
83
|
+
paths to durable commit — surface 504→206 tokens, **zero summarization tokens**, log append-only
|
|
84
|
+
throughout. The live engine slot is the **agent-preset realm**, not the host plane; double-provisioning
|
|
85
|
+
one plane fails boot loudly; and the Phase 1 mount is a shipped preset naming `dsh-chapters` +
|
|
86
|
+
`setup`-mounted creation (all of it measured, cited, in
|
|
87
|
+
[docs/host-compaction-seam.md](docs/host-compaction-seam.md) and FINDINGS).
|
|
88
|
+
|
|
89
|
+
**Phase 1 stages 0-4 landed (rounds 18-22): the whole loop works with the real parts.** Registry/store/engine/
|
|
90
|
+
preset-install/tools pass — a realm row names our subpath export (`dsh-chapters/engine`), the cascade
|
|
91
|
+
finalizes post-commit, `presets/chapters/` installs itself into `.agent-presets` at boot (profile
|
|
92
|
+
`!!js` has no `require`, copying was the delivery mechanism) — and since 0.1.16 that copy validates the
|
|
93
|
+
WHOLE shipped file set and repairs missing files additively: the guard once tested only for `preset.yml`
|
|
94
|
+
while `agent.cordis.yml` is the file that mounts the engine, so a partial wipe was a permanent silent
|
|
95
|
+
failure (never clobber a user's edits; DO restore what is absent), and r22 closed the MVP loop: parent →
|
|
96
|
+
`chapters_segment` → `chapters_continue` (ranges only; plugin renders) → TOC-only child with the composed
|
|
97
|
+
preset (15K head) → the CHILD `read` its chapter and echoed its title back. Two real bugs caught and
|
|
98
|
+
fixed en route: finalization must run on the rejection path too (the retry loop can commit, then throw),
|
|
99
|
+
and `validateRanges` refuses before any file is written.
|
|
100
|
+
|
|
101
|
+
**Host 0.1.7-alpha.1 retired the preset mechanism that paragraph describes — do not carry it forward as
|
|
102
|
+
current.** Nothing reads `$DSH_HOME/.agent-presets` any more; a preset is an `@deepseek-ai/dsh-agent-preset`
|
|
103
|
+
*declaration row*, and the chapters preset IS one now: `preset-chapters`, inserted by our own bundle patch,
|
|
104
|
+
with the preset definition in its `config` — so it is a visible Loader entry you can inspect, override, or
|
|
105
|
+
`disabled: true` from a profile patch, exactly like the host's `preset-standard`. **Its row module is ours**
|
|
106
|
+
(`@treeseed/dsh-chapters/preset`, `src/preset.ts`), not the host package: it mirrors the host's plugin line
|
|
107
|
+
for line (`inject: ['agentPresets']`, `static [EntryGroup.key] = true` so child `!!js` expressions survive
|
|
108
|
+
until the child activates, `[Service.init]` yielding the registry disposer so **Cordis** owns the
|
|
109
|
+
registration's lifetime) and adds drop-what-will-not-load. Why not name the host package: one unresolvable
|
|
110
|
+
row aborts the plugin tree of a host predating declaration rows, and one unresolvable *child* makes the
|
|
111
|
+
whole preset `broken` — measured both ways; our module resolves wherever our package runs, and pruning a
|
|
112
|
+
missing child keeps the preset usable instead of declaring it unusable. **The lifecycle is the trap that
|
|
113
|
+
took longest**: registering in `apply()` and disposing through `ctx.effect` un-registered the preset the
|
|
114
|
+
moment activation finished (`register()` reported success while the roster came back WITHOUT `chapters`) —
|
|
115
|
+
same apply-fiber trap r35 caught for the storage domain; a row's own init is the only place with the right
|
|
116
|
+
lifetime. **Support floor is 0.2.0** (user decision 2026-10-16): the legacy `.agent-presets` copy, the
|
|
117
|
+
`presets/chapters/` tree and `preset-install.test.ts` are DELETED, and a pre-0.2.0 host logs
|
|
118
|
+
`this host cannot register preset rows` while its tools keep working. The tape acceptance chain needs
|
|
119
|
+
`dev/dsh-host-lock` moved to 0.2.x plus a re-distill. **#54 did the move: the lock now
|
|
120
|
+
resolves 0.2.0-rc.2 — chosen by WITNESS (the host this box runs the plugin on), not by npm's
|
|
121
|
+
latest — the refusal has lifted itself, and `hostSupportsPresetRows()` is what lifts it (a
|
|
122
|
+
version test in `tests/e2e/boot.ts`, because it runs before any host exists; silently skipping
|
|
123
|
+
the pins would make every segment pass a run that never exercised the preset).** Three 0.2.x
|
|
124
|
+
drifts were found by booting rather than guessing, all rig-side (and a fourth was NOT the host:
|
|
125
|
+
the acceptance chain's remaining failure is a **stale tape corpus**, now gated by
|
|
126
|
+
`tests/fixtures/model-tape/CORPUS_HOST` — `scripts/corpus-anchor.mjs` refuses replay by name and CI
|
|
127
|
+
skips the acceptance job with a tracked reason, issues #56/#57; a gate that blocks `record` too
|
|
128
|
+
would strand itself, so record stays allowed), all the kind that yields a
|
|
129
|
+
WRONG conclusion rather than an error: the durable log is `session.v4.jsonl.zstd` (five hardcoded
|
|
130
|
+
`v3` paths made every log assertion decode nothing and report "the archive never landed" —
|
|
131
|
+
`sessionLogFileFor()` resolves the highest now, pinned by a row); the host opens a **Preview
|
|
132
|
+
Notice**, a focus trap like the 2026-09-18 `Internal Testing Notice`, and dismissal must target
|
|
133
|
+
`[role=dialog]`-shaped elements (matching "any div mentioning a notice" matched the app wrapper
|
|
134
|
+
and pressed an unrelated 'Close'); and **provider config rides `cordis.patch.yml`** — a
|
|
135
|
+
profile-dir `settings.yaml` is read by nothing on 0.2.x, so without an `llm-pi-ai` block the
|
|
136
|
+
host has no `local` provider and the browser opens on "Add an API key to get started" with the
|
|
137
|
+
composer never reachable. Distillation no longer needs the GPU box: `E2E_UPSTREAM[_KEY|_MODEL]`
|
|
138
|
+
makes the tape proxy translate on the way out while the fixture keeps its pinned model id and a
|
|
139
|
+
key-free profile (the tape RECORDS the profile's name — a fixture of this rig, not of the
|
|
140
|
+
service that distilled it). Remaining: re-distill all eight legacy projects (the ninth, `prompt`, was distilled ON 0.2.0-rc.2 in #71 and needs no re-record), then a strict replay and a
|
|
141
|
+
fresh `coverage:e2e` ledger — until then no browser-plane claim is verified.** Health is still the host ROSTER's answer, asked AFTER activation, plus
|
|
142
|
+
`compositionInventory()` rows — **file presence is not preset existence, and "declared" is not "usable"**.
|
|
143
|
+
A session's preset id is durable state (`agent-preset/selected`), so an unregistered id makes every session
|
|
144
|
+
that chose it fail to *open* (`agent-preset/not-found`) while its log sits there intact — and meanwhile the
|
|
145
|
+
plugin boots, registers its tools, and Settings says "enabled and running". Facts with file:line:
|
|
146
|
+
`docs/contract.md` § Presets are declaration rows; the gate that turns the next host rename into a red row
|
|
147
|
+
instead of a broken user session is `tests/unit/preset-declaration.test.ts` (15 rows, each verified to fail
|
|
148
|
+
on the mutation it guards: ralph re-disabled, an unknown pruner key, `toolFilter` restored, `{{cwd}}`
|
|
149
|
+
restored, the preset id renamed).
|
|
150
|
+
Rounds 23-28 then: E3 measured (per-machine — see the Cache section, never cross the numbers),
|
|
151
|
+
`chapters_fork` (r24), the durable title (r25), the 20-check mechanical pass (r26/26b: 18/20 proven
|
|
152
|
+
on-scratch, the rest human-only), the target-hardware verdict (r28: zero-token compaction confirmed at
|
|
153
|
+
llama.cpp counters, ~2 s steady-state turns), and a full-install product bug found-and-fixed by the
|
|
154
|
+
same run — one domain, one opener, `acquireChapterStore` (e0e67d0).
|
|
155
|
+
**MVP state: engine + tools + preset verified ON THE REAL MODEL, and the message-level fork AND
|
|
156
|
+
continue BUTTONS are shipped and e2e-green** (both rows come from one table + one runner in
|
|
157
|
+
`src/client/doors.ts` — adding a third door must extend that table, never copy the body; a test refuses a
|
|
158
|
+
second runner) (`conversation.chat.assistant-actions` → `remote.commands` → watermark
|
|
159
|
+
fork → `sessions.open` after `refresh`; `tests/{unit,integration,e2e}`, Playwright with
|
|
160
|
+
workspace-cached chromium — client-plane facts are now measured, never guessed).
|
|
161
|
+
**Long-context mechanics (architecture amendments, 2026-09-19) are BUILT**: arrival-time
|
|
162
|
+
artifacting — tool results ≥ `toolResultArtifactTokens` never enter the surface (stub +
|
|
163
|
+
content-addressed artifact, queried via `chapters_artifact` toc/search/read; measured: 373KB
|
|
164
|
+
book, zero blob prefills, cacheRead monotone across the stub) — and plot carriage (persona
|
|
165
|
+
`PLOT:` discipline carried through in-place checkpoints; bounded elicited fallback behind
|
|
166
|
+
`elicitedPlot`). e2e is now TWELVE specs in NINE replay projects, each with its own boot (#71 adds the prompt-ghost journey, `prompt` @ 0.99 + NO_AUX_MODEL + E2E_PROMPT=1, scripted-distilled like plans — suite = knowledge-loop +
|
|
167
|
+
discovery @ 0.75 of the pinned 32K/15K stress regime; heavy @ 0.5 with the arrival floor OFF —
|
|
168
|
+
measured: a floor below the chunk size arrival-stubs the surface and silently prevents
|
|
169
|
+
compaction from ever being needed; arrival @ production 0.9 + floor 500; fanout @ 0.75 — the
|
|
170
|
+
chains its child sessions write are the deepest on this hardware and must be distilled in
|
|
171
|
+
isolation, and its child prompt caps readers at SIX 300-line reads (an uncapped qwen looped
|
|
172
|
+
past 30 exchanges; the cap still crosses the threshold inside every child); enrich @ 0.9 with
|
|
173
|
+
enrichmentEnabled; rules @ 0.9; fork @ suite pins — carved out of the shared suite boot 2026-09-25
|
|
174
|
+
when knowledge's fork child became the SPA's restore target for later specs (the fanout lesson
|
|
175
|
+
generalized: one journey, one boot); explore.spec is an on-demand selector-archaeology DEV
|
|
176
|
+
project, never in the CI chain) against port-keyed throwaway homes `var/e2e-home-<port>` — the dev
|
|
177
|
+
home is SHARED with live agents and must never be the test ledger (FINDINGS 2026-09-19: run 13
|
|
178
|
+
asserted against an agent transcript; draft identity, YAML colon-in-plain-scalar, worker-bound
|
|
179
|
+
setupFiles, and same-home sibling boots all bit; identity is now verified via localStorage + the
|
|
180
|
+
log's own header event). ACCEPTANCE RUNS OFF THE MODEL TAPE by default:
|
|
181
|
+
`npm run test:e2e:replay` serves every model turn from `tests/fixtures/model-tape/<project>` (proxy
|
|
182
|
+
`tests/e2e/model-proxy.ts`; explore 14.7 s replayed vs 1.7 min live; recorded usage keeps the
|
|
183
|
+
token meter — and thus threshold behavior — bit-identical); `npm run test:e2e:record` distills
|
|
184
|
+
tapes from the real model (once per scenario; re-record when PRODUCT prompts change —
|
|
185
|
+
persona, tool descriptions, thresholds — but NOT the notice: measured 2026-10-11 that no committed tape prefix contains a seeded notice, so notice prose is currently free; that is coverage luck (no journey makes a continuation child speak), not a guarantee. Workspace docs (AGENTS.md itself,
|
|
186
|
+
skill catalogs) are masked out of matching by the AGENTS-BLIND rule (user decision
|
|
187
|
+
2026-09-20: development artifacts, every real project ships its own — doc edits are
|
|
188
|
+
FREE, no re-record; reaffirmed 2026-10-01 with no exceptions: the tapes stand in for
|
|
189
|
+
ANOTHER project, not development of this one, so no doc edit — near-threshold
|
|
190
|
+
compaction summaries included — ever couples a tape to this repo's docs).
|
|
191
|
+
`rm -rf tests/fixtures/model-tape` for a clean corpus. The normalization
|
|
192
|
+
pipeline is CLOSED — escape/whitespace classes, volatile rules, tool-result content and
|
|
193
|
+
injected instruction blocks matched out; the AGENTS-blind addition was the last change
|
|
194
|
+
and required one final re-record; 2026-09-23 added two DERIVED-COUNT collapses to the
|
|
195
|
+
same family — `(N est tokens)` in chapter index lines and the char count inside
|
|
196
|
+
`⟦omitted:host-injected …⟧` markers (both move with doc/render drift, both identified
|
|
197
|
+
already by their line — pinning them made heavy/enrich tapes brittle across builds). Replay chain verified green — ALL SEVEN segments strict 2026-09-25 and — for the first time — GREEN ON HOSTED CI (GitHub Actions, no model, no GPU): the tape is machine-neutral by contract now — injections mask to fixed tokens everywhere (skill catalogs incl. the 'catalog changed' notice), marker RUNS collapse (a dev box injects more spans than a runner), digest message-tallies collapse to <MSGCOUNT> (they count injections), the enricher's input defuses markers before its cap (hosted divergence at char 758), WS-PATH/DSH-CHECKOUT blind the system prompt's identity lines, the sessions-dir slug is COMPUTED from ROOT (a hardcoded /home/adrian one made CI poll an invisible log for 7 minutes per spec), zstd is a runner dep, the host comes from dev/dsh-host-lock's hermetic lockfile (npm global mode rewrites the plugin tree; rc-ranges float), workspaces boot PRISTINE (sessionIds cleared — restore races ended), and an unborn-mirror push is an honest no-op, not local-only (pristine-store discovery: the old knowledge assertion passed only on accumulated dev state). All 464 deterministic + 85/85/85 gate green on the hosted runner too. The `rules` browser scenario was UN-PARKED — the r39 diagnosis: the spec read zstd-compressed session directories as flat files and hardcoded a rule id; the product was always right. The enrich e2e earned its keep: it exposed two real product bugs invisible to every deterministic layer (verify.md T9/T10 — the body-hash anchor convention mismatch that silently blocked enrichment since P2, and the missing host-plane route) (r38 lesson: the DRAFT screen turns composer text into a first MESSAGE, not a command — establish a session before exercising command surfaces; and RR2=137: a chatty live loop that records mid-test can be OOM-killed — keep e2e prompts tool-free in WORDS the model obeys; and 2026-09-23: a tape records the CONTEXT it was distilled in — re-record whole projects, never one spec of a shared boot, and never run two GPU suites at once). 608 unit/integration, zero skips when the TreeDX container is up (live-gate fails loudly when configured-but-unreachable; skips only with no configuration at all), under a GATED coverage ledger: `npm run coverage:ci` (c8 --check-coverage) enforces ≥85% statements/branches/functions/lines in CI — current 97.94 / 86.47 / 93.82; `npm run coverage:e2e` remains the browser-plane ledger (eight replays since the plans journey joined, boot-level V8 profiles remapped through REAL source maps; current 79.92/64.64/76.28 — reported, not tuned; first ledger 80/62 before the audit). The 2026-09-23 coverage audit closed its own gap list the same way: providerKindFor policy rows, credential migration, the git-command surface incl. every refusal text (T13), the TreeDX client's failure vocabulary (timeout/bad_json/envelope), git-plane auth (T11), the push retry/divergence paths, scheduler pull rows, listing pagination over a paginating stub (T12), overlay-delete, rule-number walk-past by prefix (id-uniqueness), the composer busy-retry budget, and the coverage instrumentation itself; then the deterministic plane was swept to the gate — engine-lifecycle (real cordis Context + real base engine: engine.ts 40→91 stmts), the tool wrappers' full success/refusal surface, the TreeDX error taxonomy over stub faults + parser-shape drift via injected fetchImpl, the local-path git transport, host-command error arms, and the pure planners (vocabulary/enrich/indexing/search/arrival/archive) — catching one REAL bug (canonicalizeRemote carried embedded credentials into the project key: same repo with/without creds forked the pool; fixed + pinned). CI/CD shipped 2026-09-24 and went fully green 2026-09-25: .github/workflows/ci.yml (deterministic gate + seven-segment tape-replay acceptance with NO LLM/GPU/key — tape misses fail loud, hosted runs ship diff-dumps sorted by shared prefix) and release.yml (tag = package version → suite-gated npm publish --provenance; NPM_TOKEN strategy chosen, wired for the treeseed-ai org move); the pristine-runner dress rehearsal caught the scaffold-clobbering patch guard, the credential-less-provider death, and the unregistered-workspace home — plus the profile is now built with plain npm (dsh plugin add was a pnpm forwarder — nothing in this repo touches pnpm, and it is not and never will be a monorepo; the host lock is a standalone manifest). docs/development.md § Hosted CI.
|
|
198
|
+
Git history was rewritten 2026-09-20 (test-results/.dsh purged, .git 7M→840K) and force-pushed —
|
|
199
|
+
clone fresh or `git reset --hard origin/main` after pulling.
|
|
200
|
+
**Knowledge layer P1 (record §13) is complete and live-proven**: signatures collect at real
|
|
201
|
+
`turn/end`s (r35j), the fork composes topics live (r36 — same-file turns merge, topic changes
|
|
202
|
+
split), the index + `chapters_search` + redaction + `/chapters-link`/`/chapters-status` ride a
|
|
203
|
+
sync loop that degrades to local-only honestly and rebuilds the mirror on divergence, with the
|
|
204
|
+
two-machine exit criterion automated over a real git smart-HTTP remote (integration + the
|
|
205
|
+
`knowledge.spec.ts` browser journey). **P2 is BUILT (2026-09-20)**: the idle/after-push
|
|
206
|
+
enrichment ladder with validation-before-commit, provenance chains (`generated:` newest-first),
|
|
207
|
+
the body-hash guard as the only sanctioned chapter-file mutation, `/chapters-enrich
|
|
208
|
+
run|model|report`; shadow-by-default emergent vocabulary writing git-visible `topic-alias`
|
|
209
|
+
curation + derived `topics/vocabulary.json`; and index-level fragment stitching (adjacent
|
|
210
|
+
legacy fragments cohere into ONE search entry citing all members — nothing merged on disk). Two-machine exit criterion proven — catching r37 on the way: stageAllAndCommit compared the workdir against itself, making modifications to tracked files permanently invisible to commits (additive P1 hid it; enrichment would never have traveled). **P3 is BUILT (2026-09-20, §15 amendment: approval = per-machine curation fact, never a file rewrite)**: write-once proposed rule files, /chapters-rule add|list|approve|revoke, chapters_rule_propose tool, verbatim core block in the continuation notice (byte-identical ABSENT, golden-tested), refuse-with-numbers overflow budget, category-aware search with a per-machine core bonus, and the rules lifecycle proven transport-grade over a real pool with three machines. **P3c — two-tier rules is BUILT, both capture speeds (#50; record §7.1 + §7.3–§7.9, amendments 2026-10-20 and 2026-10-21). Two things remain unverified, for different reasons: the **browser plane** (the host pin moved to 0.2.0-rc.2 in #54 and the boot's named refusal lifted, but the corpus still has to be re-distilled on the new host before ANY browser-plane claim is real — this feature added a tool AND changed the preset persona, both prompt bytes), and **mining quality beyond n=3** — `npm run mining:probe` did run against the real session model (`qwen3.8-flash`, regional token-plan door; the GPU box was unnecessary) on three of this repo's own chapters: 9 rules, 6 stated / 3 inferred, all inferences confined to the chapter with no human text. See `docs/verify.md § Two-Tier Rules` R5 and `docs/backlog.md` 1b/1c.** The hole was concrete, not vague: `planSummarize` composes `leadSections = [planSection, plotSection]`, so a plan survives an in-place compaction and a plot survives it, and a **rule does not**. Session rules (`scope: session`, local, never staged — one filter in `planStoreToRepo`) captured by a deterministic `ADD RULE:` parse at the existing `turn/end` listener (zero model tokens, invariant 1) plus enrichment capture (`rulesEnrichment`, ON) that MINES standing orders the human never said and lets them govern on arrival — `rulesAutoActivate: true` is the default, and the earlier design that required a verbatim human quote before a mined rule could apply was REJECTED by the maintainer and removed (record Amendment 2026-10-21): demanding proof that a human already said it makes the tier redundant with the deterministic marker, and the premise of the tier is that the human may not know. Visibility replaced permission: each mined rule renders with its warrant (`stated by you, mined from ch.N` / `inferred from ch.N — nobody said it`), the agent must NAME the session rules that shaped its reply, at most 3 per chapter plus per-tier display caps, and `reviewOnly` + `approve` is the opt-in review-before-trust posture; `rulesModel` gives mining a model of its own (one extra bounded call, and its answer REPLACES the annotation pass's rules). One auxiliary completion budget, `enrichmentMaxTokens` (default 4000), is the single source for that ceiling on both planes — the route measured here does not enforce `max_tokens` and counts reasoning tokens toward completion usage, so a small number is not safety, it is truncation on a stricter provider. What did not loosen: a session rule never overrides a project rule, and never becomes one without a human. Activation itself is unchanged: **DERIVED** from the ancestor path, never a `curation.jsonl` fact — the 2026-09-20 per-machine approval boundary is NOT relaxed, it still governs the entire shared tier. Rules are carried at the two moments a head is rebuilt — the continuation notice AND the in-place compaction checkpoint (`rulesCarry` is the third lead section in `planSummarize`, and `session-rules.renderRulesBlock` is the ONE builder both paths plus `chapters_rules` call: a source-shape row fails if either door starts picking rules itself) — **never injected per request**: invariant 3, and measured llama.cpp `cacheReuse 0` on a head-position replacement means a churning ruleset is a cache-wipe machine. `chapters_continue` carries both tiers, `chapters_fork` carries project only — the SAME `intent` axis plans already use (`docs/plans-carriage.md` §§ 9–13), not a new distinction. **#52 (same round, second half): rule ancestry ALSO crosses the delegation edge** —
|
|
211
|
+
`session-rules.ruleAncestorSet` walks registry `parentSession` ∪ the kernel header's
|
|
212
|
+
`parentSession`, transitively, READ-ONLY, upward only (a subagent inherits its delegator's orders; a
|
|
213
|
+
child's inferences never govern the parent), bounded at 64 expansions and LOUD when the cap bites;
|
|
214
|
+
every door, the checkpoint and `/chapters-rule active` call that ONE walk, and an anti-drift row
|
|
215
|
+
fails if a plane computes ancestry by hand again — hand-built ancestry is how delegated subagents
|
|
216
|
+
ended up with zero rules. And the `chapters` preset's **persona suffix** is the per-composition
|
|
217
|
+
channel that tells a session — fresh, continued, or delegated — to run `chapters_rules` before a
|
|
218
|
+
task and `chapters_search` for history: that is allowed because it is preset-scoped, not a
|
|
219
|
+
system-prompt contribution (invariant 3), and two couplings are pinned by behaviour: the suffix must
|
|
220
|
+
stay matched by the plot template-echo guard, and it may not introduce a `{{variable}}` the host has
|
|
221
|
+
not registered. Changing that suffix is a PRODUCT PROMPT change — the tape re-distill debt GROWS,
|
|
222
|
+
it does not shrink. Selection is pinned → topicality → index-only, via `rules.selectRules` — the head's selector, deliberately NOT `search.rankEntries`, which ranks the corpus (one implementation of each job, and bending a rebuilt head to read index shards was rejected); per-tier count caps config-driven, and the **withheld count is mandatory** — silent selection would rebuild "lost in the noise" inside the fix for it; the token ceiling still refuses with numbers, never clips. **No global tier was built, because none was needed**: `projectKey` is UUIDv5 over the canonicalized *remote* and `harnessId` defaults to hostname, so checkouts of one repository already share one rule namespace; what that round actually owes is loudness for the four ways the identity silently diverges (record §7.8) — a `path-derived` key, a fork listed first, TreeDX keying on the pool location, and an unlinked/un-synced clone, each of which must SAY it rendered zero rules: `/chapters-status` now prints an `Identity:` line and warns when the derived key differs from the bound one (the repo's own non-git test fixture trips it, so the warning is not theoretical).
|
|
223
|
+
One design lesson from that reversal, because it is the recurring mistake in this repo: a safety mechanism must block the harm, not the value. The quote gate blocked invented rules by also blocking every inferred one; the mechanisms that replaced it (a rendered warrant, the agent naming its rules, caps, one-command revoke) hold the same risk without amputating the feature. Suspicion of model authorship is a reason to make output visible and reversible, not a reason to demand human provenance.
|
|
224
|
+
Two local build facts, both learned the hard way: the deterministic suites need **Node ≥ 24** (`engines`), and on this box's Node 22 every test file fails in a way that looks like your diff; and until 2026-10-21 `npm run coverage:ci` intermittently reddened `git-auth-classification`/`sync-remote` with an EPIPE blamed on their `before` hook — **diagnosed as pre-existing, which was true and not the point**: the test git server wrote request bodies into a CGI child's `stdin` with no error listener, so a child that had already exited raised an async EPIPE nobody handled. Streams are guarded now (three consecutive combined runs: 800 pass / 0 fail). Recording the sequence because the mistake is the reusable part: proving a failure predates your diff is a step, not an answer.
|
|
225
|
+
**Knowledge transport is now a provider seam (2026-09-21, record §15: "transport is pluggable; git is one provider" + "host-injected context is not conversation")**: `src/provider.ts`'s six verbs carry every sync path; `kind:'git'` (default, every pre-existing record — zero behavior change) or `kind:'treedx'` (`/chapters-link treedx+<url>/<repo> <token>`, `src/treedx/`: workspace-create → overlay-write → commit transport, `.treedx-state.json` mirror memory). Search/rules/index/notice layers read the materialized mirror unchanged — §3.1 survives both backends; divergence routes through rebuild-from-the-store in both; TreeDX lease contention maps to the existing rejected→pull-retry, moved head to diverged. Contract + truth table in `docs/provider.md`; TreeDX API facts (doc-reads vs live-measured, with corrections) in `spikes/treedx/FINDINGS.md`; dev loop `scripts/treedx-local.sh` + `dev/treedx.compose.yaml` (the cloned repo's own compose is platform-bound and will not boot standalone — measured). Transport-grade proof: 24 tests over an in-process stub (`treedx-{provider,sync,link}.test.ts`); the real-container exit criterion (`treedx-live.test.ts`) ran and PASSED 2026-09-21 (5/5 against a booted dev-auth service; full suite 286/286 zero skips — the live run caught three parser shapes, the born-with-`.treedxkeep` phantom-deletion trap, the lease-survives-TTL close requirement, and a swallowed auth detail, all fixed and pinned). **Re-verification 2026-09-23 caught one more, in the wild:** `paths/list` PAGINATES (limit 100, base64 offset cursor in `page.nextCursor`) — reading one page silently truncated the corpus once the shared dev repo grew past 100 paths (machine B reported 'up to date' while missing real files). `corpusPaths` now follows the cursor to the end; the paginating stub + a 150-file row pin it (T12), and the live container proves it at the real service. **The same round landed the injection screen** (`src/injections.ts`): host-injected context (instruction files, runtime snapshots, skill catalogs, our own TOC notice — event-level and `<system-reminder>` spans inside human messages) is marked-and-counted, never archived; the conversation-bytes guarantee and TOC `(N msgs)` now say what is actually true, and the knowledge corpus carries no project's instruction bytes (H1 rows in `docs/verify.md`).
|
|
226
|
+
**Session-plan carriage is BUILT (2026-10-09, `docs/plans-carriage.md`)**: `src/plans.ts` pairs an
|
|
227
|
+
`exit_plan_mode` call with the `tool/result` that carries the human verdict (approved / rejected /
|
|
228
|
+
dismissed / pending / superseded / failed — the fifth class is the review never opening, which is not a
|
|
229
|
+
verdict); chapters carry a `## Plans in this chapter` block AFTER the verbatim transcript with the plan's
|
|
230
|
+
outline, bounded head and content-addressed full text; `plan/mode` folds last-value-wins into `planMode:`
|
|
231
|
+
instead of falling into `unrenderedSeqs`; the child's notice carries the operative plan (intent-aware
|
|
232
|
+
since the second round — see below; a pending one is marked unapproved); plans are forced chapter boundaries in
|
|
233
|
+
both segmenters and title the chapter they conclude. Two truths worth keeping: the token meter is NEVER
|
|
234
|
+
exempted (the block is counted by `preflight`, and a plan-less path renders zero bytes — which is why all
|
|
235
|
+
seven tapes still replay WITHOUT re-recording, verified 2026-10-09), and a plan whose review is still open
|
|
236
|
+
sits in an unfinished turn above the archive ceiling, so it is carried as live and un-stored. Fixed on the
|
|
237
|
+
way: chapter `artifacts/…` citations pointed at paths `chapters_artifact` could not resolve at all
|
|
238
|
+
(`writeArchive` writes inside the session tree; the resolver only looked at the store root) — true for
|
|
239
|
+
every deferred blob since arrival-stubbing shipped, now pinned (verify.md P10). The browser plane is IN the chain (project `plans`, verified 2026-10-10); its tape is distilled from
|
|
240
|
+
the scripted upstream `tests/e2e/stub-model.ts` because this box's model server drops the stream
|
|
241
|
+
mid-turn — read `tests/fixtures/model-tape/plans/PROVENANCE.md` before quoting what it proves (the
|
|
242
|
+
carriage yes, a model's choice to plan no). **Second round, same day, four decisions landed**
|
|
243
|
+
(`docs/plans-carriage.md` §§ 9–13, verify.md P12–P17):
|
|
244
|
+
- **Intent decides what a child is told.** A CONTINUATION (`chapters_continue`) extends the same stream
|
|
245
|
+
of work, so it gets `## Active plan` + `## Previous plans on this path` and, while the parent was still
|
|
246
|
+
planning, a seeded `plan/mode`. A BRANCH (`chapters_fork`, the fork button) splits a new direction, so it
|
|
247
|
+
gets **zero** plan bytes — its plans are history, in the cited chapters and as documents.
|
|
248
|
+
- **A plan is a document of a third kind**: `kind: plan`, written in `chapters/` as `plan-NNN-<slug>.md`
|
|
249
|
+
with its own registry counter (`nextPlanNumber`, allocated through the registry — never a directory
|
|
250
|
+
scan), so it never consumes a chapter number, is carried by the pool transport and the mirror rehydrate
|
|
251
|
+
for free, and is findable with `chapters_search` **as a plan**. Superseding writes a new document; the
|
|
252
|
+
old one keeps its bytes.
|
|
253
|
+
- **The enrichment ladder never touches a plan document.** Directives are not narrative; paraphrasing an
|
|
254
|
+
order is the drift this store exists to prevent. The guard reads the file's own frontmatter.
|
|
255
|
+
- **The operative plan is carried into every in-place checkpoint** (the plot mechanism, but chosen
|
|
256
|
+
session-wide and newest-first), bounded, with truncation announced.
|
|
257
|
+
- **Findability round (0.1.12, `docs/plans-carriage.md` § 14, knowledge-repo Amendment 2026-10-11): two
|
|
258
|
+
doors, ONE scorer (`search.rankEntries`) — the pool mirror when pulled, the workspace store otherwise
|
|
259
|
+
(and as a fallback when the pool has no match). A bare workspace used to answer `chapters_search` with
|
|
260
|
+
"link a remote" while its own chapters, `kind: plan` documents and rules sat two directories away; that
|
|
261
|
+
was the flaw the audit found. The answer must NAME ITS DOOR ("searched this machine's store, N docs …")
|
|
262
|
+
so "this machine only" is never read as "the whole project", and "nothing matched" ≠ "nothing archived
|
|
263
|
+
here" are reported apart (`documents`/`empty`). Artifacts are never indexed (they answer to
|
|
264
|
+
`chapters_artifact`; a second index is forbidden). The notice gains `## Finding things` naming
|
|
265
|
+
read/chapters_artifact/chapters_search (`retrievalHints`, default on) because an unmentioned tool does
|
|
266
|
+
not exist to a small model. Two pinned rows asserted the old contract and failed loudly — that is why
|
|
267
|
+
this is a fact rather than a guess; amended with the reason in the diff.
|
|
268
|
+
- **Kernel-drift gate (`tests/unit/host-seed-contract.test.ts`)**: the seeded `plan/mode` depends on the
|
|
269
|
+
host knowing the type and treating it as NOT surface-eligible. Read the kernel's OWN exported predicates
|
|
270
|
+
(`KNOWN_SESSION_EVENT_TYPES`, `isSurfaceEligibleType`, `isAppendSurfaceEvent`), never reimplement them —
|
|
271
|
+
a kernel change that silently ungates a child must show up as a red row, not as a passing suite.
|
|
272
|
+
- **Two deterministic doors, one builder (`/chapters-fork`, `/chapters-continue`)** — the load-bearing
|
|
273
|
+
lesson of attempt 5: **a tape cannot carry a model-authored range.** The recorded `chapters_continue`
|
|
274
|
+
said `endSeq: 30`, the runner's ceiling was 29, and `validateRanges` refused it — correctly. And a small
|
|
275
|
+
local model will not reliably chain `chapters_segment` → `chapters_continue` at all. So anything that must
|
|
276
|
+
be accepted repeatably goes through the plugin's OWN derivation (`deriveRanges`/`composeChapters`, zero
|
|
277
|
+
model involvement); the tool remains for the model's use but is never the only proof path. Both doors come
|
|
278
|
+
from `deterministicCommand(door)` and a row pins that they **archive identically** (same ranges, titles,
|
|
279
|
+
plan facts) — two implementations would drift. `intent` is the ONLY semantic difference: continue ⇒
|
|
280
|
+
`## Active plan` (+ seeded `plan/mode` while still planning); branch ⇒ zero directive bytes.
|
|
281
|
+
Also measured there: **the turn containing a plan submission does not end until the review is answered**,
|
|
282
|
+
so a session whose only turn is pending has no archive ceiling and the door refuses with its first guard.
|
|
283
|
+
- **The one exception to the injection screen**: the harness's `plan-mode` + `form:'notice'`
|
|
284
|
+
mode-flip sentence archives verbatim (named by producer AND form, so it cannot widen — v3 wrapper
|
|
285
|
+
`{kind:'plugin', plugin:'plan-mode'}` and v4 `{kind:'plan-mode'}` both match, issue #43).
|
|
286
|
+
`isHumanAuthored` stays strict on purpose — it drives topic signatures, and the r28 lesson was that
|
|
287
|
+
injected text moves chapter boundaries.
|
|
288
|
+
**Outstanding: the human verification rows (live `/compact` in a real browser, readability
|
|
289
|
+
judgment), the seeded-gate case OBSERVED in a browser (pinned deterministically; needs a second distilled session — `docs/plans-carriage.md` § 15), per-message anchoring (the button forks at the conversation's end today; `deriveRanges`
|
|
290
|
+
already supports any anchor), and the default-preset shipping policy (opt-in menu vs profile
|
|
291
|
+
default), which is the user's call, not ours.**
|
|
292
|
+
|
|
293
|
+
The fiber-eviction trap `dsh-session-fork/src/branch.ts:10-14` warns about did **not** bite — an unattached
|
|
294
|
+
session was the whole explanation for round 6's "not listed", fixed by
|
|
295
|
+
`workspaceRegistry.createCanonical(cwd)` → `workspace.attachSession(id)`.
|
|
296
|
+
|
|
297
|
+
**Cache behaviour measured (rounds 10-11, 13, 16, real provider).** A continuation builds its own cache —
|
|
298
|
+
0% hit cold, 74% warm — and the parent keeps hitting across the boundary, so **G3 and G2 are green**. G1's
|
|
299
|
+
head is now *measured* for the composed shape: r16's `setup`-mounted child's first request was **13,315
|
|
300
|
+
tokens (27-tool header, this roster)** — quote the older "≈ 8K" only as a projection. Steady state it
|
|
301
|
+
protects: 396,800 cached + 822 uncached (**99.79%**) live session. E3 measured — and it is
|
|
302
|
+
per-machine (r23/r28): on the cloud provider a replacement keeps ~7.4K of prefix cached (refill
|
|
303
|
+
halves, 13,658 → ~7,000); on llama.cpp (the target) the slot reuses NOTHING across a head-position
|
|
304
|
+
replacement (cacheReuse 0) — the local win is zero-token compaction (r28: 0 prompt tokens vs ~11 min
|
|
305
|
+
for an LLM summary on that box) plus ~2 s steady-state turns. **Never quote one machine's cache
|
|
306
|
+
number for the other.**
|
|
307
|
+
|
|
308
|
+
Metric formula, since getting it wrong produces 1721% hit rates: `totalPrompt = inputTokens +
|
|
309
|
+
cacheReadTokens`; `hit = cacheReadTokens / totalPrompt`. Judge "cache undisturbed" by
|
|
310
|
+
`cacheReadTokens` not collapsing, **not** by hit percentage, which falls innocently as the prompt grows
|
|
311
|
+
past a cache-block boundary.
|
|
312
|
+
|
|
313
|
+
Phase 1 follow-ups CLOSED: the title call is the *service* (`sessionController.rename`, live-handle
|
|
314
|
+
window — r25 durable), and the preset/composition note in invariant 2 ships in every child.
|
|
315
|
+
|
|
316
|
+
Coexistence with `dsh-session-fork` is currently **untestable, not disputed**: the shipped example fails
|
|
317
|
+
to boot the installed host ALONE (its rc.2-era code vs rc.1's `webServer` inject requirement — verified
|
|
318
|
+
by pair-boot then solo-boot, r26). Name-spaces are disjoint by inspection; re-run the pair-boot on a
|
|
319
|
+
version-matched host before claiming either way. The dev/test loop that avoids restarting the harness
|
|
320
|
+
being edited from is solved — see [docs/development.md](docs/development.md).
|
|
321
|
+
|
|
322
|
+
Then [docs/contract.md](docs/contract.md) for how to declare things, and
|
|
323
|
+
[docs/verify.md](docs/verify.md) before claiming any step passes.
|
package/docs/verify.md
CHANGED
|
@@ -165,6 +165,88 @@ renderer, the segmentation tool, and the child's notice.
|
|
|
165
165
|
| P15 | Plan mode survives a continuation only when it should | seeded `plan/mode` ONLY for `intent: 'continue'` while the fold says active (measured: kernel accepts the two-event seed, host projection folds it to `active:true`, one-event control does not); never for a branch, never after an approval — because approving a plan EXITS plan mode | unit `continue-plans.test.ts` (four rows: seed shape, approved-no-seed, branch-no-seed, `planSeedMode:false`) against the installed-kernel probe recorded in docs/plans-carriage.md § 10 |
|
|
166
166
|
| P16 | The notice reads the intent, not just the log | continuation → `## Active plan` + `## Previous plans on this path` (marked history); branch → zero bytes, plans live in the cited chapters and as documents; the mode statement is either "the directive above is live" or "this child starts OUTSIDE plan mode", never both | unit `plans.test.ts` (seeded/unseeded/branch rows) |
|
|
167
167
|
| P17 | The one injection-screen exception is named, not open-ended | `kind:'plugin' + plugin:'plan-mode' + form:'notice'` archives verbatim; any other plugin notice, snapshot or instruction shape still screens; `isHumanAuthored` (which drives topic signatures and `(N msgs)`) is unchanged | unit `injections.test.ts` (two rows, incl. wrong-form and wrong-plugin negatives) |
|
|
168
|
+
## Repair (#60) — recovering an archive from the durable log
|
|
169
|
+
|
|
170
|
+
| # | Claim | Procedure | Status 2026-10-22 |
|
|
171
|
+
|---|---|---|---|
|
|
172
|
+
| R1 | The auditor finds the measured damage | `node scripts/chapters-repair.mjs --home <lab> --project <cwd> --cwd-override <lab-tree>` on a COPY of SAIP's live registry + store + 26 session logs | **PASS** — 436 findings: hollow 5, empty-artifact 335, never-archived-span 19, unregistered-file 73, ghost manifest 2, stuck reservation 2. Matches the manual audit exactly. |
|
|
173
|
+
| R2 | A repair re-derives lost knowledge | apply, then grep for three phrases that previously matched 0 files | **PASS** — span 5265-5467 (the "foundational to the project" hosting brief) is now in `chapters/030-hosting-…-plan-1.md`; 719 moves across 3 passes |
|
|
174
|
+
| R3 | Repair converges (idempotent) | run apply again; the third pass must move 0 | **PASS** — pass 3 = 0 moves; audit falls 436 → 3 findings |
|
|
175
|
+
| R4 | What it will NOT do | 3 files remain: two `015-*.md` claiming one number. Re-registering needs a new number = renumbering a chapter → refused, with the reason printed | **PASS** — residue is a human decision, reported as such, not silently "fixed" |
|
|
176
|
+
| R5 | Nothing is deleted or rewritten | after a full apply, the husk files still exist (copied to `.repair-quarantine/`), the registry keeps every record (append-only), and the log's md5 is unchanged | **PASS** |
|
|
177
|
+
| R6 | The run is reversible | `restoreRepair` walks the journal backwards; new files return from quarantine | **PASS** — `tests/integration/repair-apply.test.ts` |
|
|
178
|
+
| R7 | Dry-run is genuinely read-only | registry bytes and directory listing identical before/after | **PASS** — pinned by a row |
|
|
179
|
+
| R8 | LIVE application | `~/.dsh` is read-only to the agent sandbox (EROFS), so SAIP and this repo have NOT been repaired for real | **BLOCKED on the user**: run `node scripts/chapters-repair.mjs --project /home/adrian/Projects/saip --apply all --yes` yourself, or approve the sandbox |
|
|
180
|
+
|
|
181
|
+
Known limitation, stated rather than hidden: an audit through `ctx.fs` cannot list a directory, so it
|
|
182
|
+
reports `blindSpots: ['orphan-file','test-litter']`; the offline script has `readdir` and sees them.
|
|
183
|
+
|
|
184
|
+
## Two-Tier Rules — SPEC (record §7.1, §7.3–§7.9; issue #50)
|
|
185
|
+
|
|
186
|
+
Rows **R1–R10 verified 2026-10-20** except **R-e2e**, which this host cannot run (see the last row
|
|
187
|
+
and §13 P3c). Each row below was written docs-first and then proven to fail on the mutation it
|
|
188
|
+
guards — the mutation runs are named per row so a future reader can repeat them. The P3 rows above
|
|
189
|
+
stay the shipped-behavior record; these extend it with the session tier.
|
|
190
|
+
|
|
191
|
+
| # | Claim | Status | How it is witnessed (and the mutation that proves it) |
|
|
192
|
+
|---|---|---|---|
|
|
193
|
+
| R0 | The empty tier still renders **zero bytes**, and the pre-#50 option shape renders **byte-identically** to the previous builder | ✅ verified | The golden literal was captured by running **HEAD's** `buildRulesSection` over the same fixture and diffing it against the new one (text-equal, kind-equal, only the non-rendered `withheld` field added). On the checkpoint path an empty set asserts neither header nor announcement appears |
|
|
194
|
+
| R1 | A rule survives an in-place compaction | ✅ verified | `rules-checkpoint.test.ts` drives the **REAL realm engine** through `summarize()` and reads the text that would be prefilled next: project rule + session rule verbatim, project before session (positional precedence); a parent's rule rides the child's checkpoint (R1b — the tier binds the TREE); over budget the omission is announced. Mutation: dropping `rulesSection` from `leadSections` breaks 3 of 4 rows |
|
|
195
|
+
| R2 | `chapters_continue` carries both tiers; `chapters_fork` carries project and **zero session bytes** | ✅ verified | `rules-doors.test.ts` asserts the asymmetry through the shared renderer, and a **source-shape row** fails if `tools.ts` or `engine.ts` calls `buildRulesSection` directly — two rule-pickers would drift apart invisibly (the same argument that pins `rankEntries` and `doors.ts`) |
|
|
196
|
+
| R3 | `ADD RULE:` is deterministic and free | ✅ verified | `session-rules.test.ts` (8 rows): line-start-only, minimum length, dedupe; the two injection screens (`isHumanAuthored`, `stripReminderSpans`) each with a positive and a negative; assistant-restatement never captures (the amplification path); idempotent across repeated turn/ends; numbers honor registry **and** disk; §9 credential screen; listener containment (disabled / missing cwd / throwing store). The module holds no `ctx.llm` reference at all |
|
|
197
|
+
| R4 | Session rules never reach the shared pool, on **either** provider | ✅ verified | `rules-transport.test.ts`: a real git pool (A stages project only, B pulls and never sees the session file, A still renders it locally — exclusion ≠ invisibility) and the TreeDX stub's commit tree. Mutation: deleting the single `isSessionRuleFile` skip reddens both |
|
|
198
|
+
| R5 | Mined rules are governed by **policy**, not by proof that a human said them, and each one carries its **warrant** (§7.5, Amendment 2026-10-21) | ✅ verified | `rules-mining.test.ts` (20 rows): `rules` is OPTIONAL so the old three-field answer still validates; bounds (≤3 **written** per chapter, sub-minimum dropped, a garbage `rules` value cannot cost the annotation); the quote check is a LABEL — `stated` when the chapter's human bytes contain it, `inferred` when they don't, and BOTH govern under the default `rulesAutoActivate: true`; the row named "THE REVERSAL, PINNED" asserts exactly that, and the rendered head shows `inferred from ch.N — nobody said it` beside the text with the agent required to name the rules it applied; `autoActivate: false` writes `reviewOnly: true` and renders NOTHING until `approve` (a curation fact — the write-once file is byte-compared unchanged); assistant prose is inadmissible even as a label; ladder rows prove human-bytes-only evidence, mining before the hash-guarded write, `rulesEnrichment: false` logging the drop, an unwired plane WARNING, a throwing sink not losing the annotation, and `rulesModel` making a second call on its own route whose answer replaces the annotation pass's; `/chapters-rule active` prints the posture and the inferred count. Mutations: predicate ignoring `reviewOnly` → 2 rows; `autoActivate` ignored → 2; `rulesModel` ignored → 2; basis classification collapsed → 1 |
|
|
199
|
+
| R6 | Selection is deterministic and announced | ✅ verified | Shuffled input order yields byte-identical selection; two identical runs render identical text; the withheld line appears exactly when `shown < active`, and its ABSENCE is asserted when no cap is set. `active`/`chapters_rules` return the counts even when nothing was withheld, so "you are seeing all of it" is a fact |
|
|
200
|
+
| R7 | Nothing was silently clipped anywhere the budget bites | ✅ verified | Token ceiling still returns `refusal` with per-rule numbers and now counts **both** tiers; the checkpoint announces the omission instead of squeezing; the door path still refuses the continuation with numbers (§1.2) |
|
|
201
|
+
| R8 | Ungoverned never looks like governed | ✅ verified | `/chapters-status` prints an `Identity:` line with the divergence warning — and this repo's own non-git test workspace hit that branch during verification, which is the honest evidence the warning is not theoretical; `chapters_rules` answers "no linked project" separately from "nothing approved on this machine" separately from "nothing captured this session"; the head states the zero-project tier only when session rules are actually governing it |
|
|
202
|
+
| R10 | Every chapters session — fresh, continued, or DELEGATED — is told to use `chapters_rules` and `chapters_search` | persona row parsed from the shipped patch: the suffix names both tools, names delegation, keeps the `PLOT:` discipline, adds no unregistered prompt variable, and remains matched by the plot template-echo guard. Verified by mutation — dropping `chapters_rules` from the suffix reddens the row |
|
|
203
|
+
| R11 | Rule ancestry crosses the delegation edge, and every door uses the ONE walk | `tests/unit/rules-lineage.test.ts` (8 rows: header-only parent, transitive mixed edges, one-way direction, cycle termination, the 64-expansion cap with its loud flag, unknown session, throwing reader, anti-drift). Verified by mutation: disabling the header edge reddens 4 rows |
|
|
204
|
+
| R9 | A conflict is reported, never resolved | ✅ verified | `conflicts` wrote no file (directory listing asserted unchanged), the prompt explicitly forbids rewriting, and with no conversation route the verb refuses rather than half-answering |
|
|
205
|
+
| R10 | Losing a checkout is re-derivation, not corruption | 🟡 partial | `parseRuleMarkers` is deterministic over message text and capture dedupes against disk, so a replay fills gaps without duplicating — but the **end-to-end wipe-and-replay row is not written**. The mining half is now re-derivable by re-running the ladder, and capture dedupes against disk, so a replay fills gaps rather than duplicating. |
|
|
206
|
+
| R-e2e | The browser plane: a live model, the `rules` journey, the new tool in the header | ❌ cannot run on this host | `npm run test:e2e:replay` **refuses by name** (`tests/e2e/boot.ts:130`: the pinned host predates preset declaration rows). The re-record this feature owes is queued with the `dev/dsh-host-lock` move in `docs/backlog.md` — stated, not skipped silently |
|
|
207
|
+
|
|
208
|
+
**First live witness (2026-10-21, `npm run mining:probe`).** Three real archived chapters of this repo,
|
|
209
|
+
mined by `qwen3.8-flash` — the model that powers live sessions here — through the shipped code paths
|
|
210
|
+
(`buildMineInput` → `parseMinedRules` → `quoteIsHuman` → `captureMinedRules`), local :8080 down so the
|
|
211
|
+
token-plan hosted route was used. Nine rules captured (the 3-per-chapter cap bound every chapter),
|
|
212
|
+
**6 stated / 3 inferred**, and all three inferences came from the one chapter with ZERO human bytes —
|
|
213
|
+
a subagent chapter — which is exactly what the classification should do. The model's own `basis` claim
|
|
214
|
+
was the weaker signal: chapters 2 and 3 each declared only 1 stated rule, while the plugin's check
|
|
215
|
+
found 3/3 quotes genuinely present in the human text. So the label is earned from evidence, not from
|
|
216
|
+
the model's say-so, and the head rendered all nine in 479 tokens with nothing withheld at a cap of 12.
|
|
217
|
+
On quality: none of the nine was wrong or unsafe; the three inferences were arguably the most
|
|
218
|
+
transferable ("verify decisive architectural claims in shipped code before treating them as settled").
|
|
219
|
+
n = 3 chapters on one repo's own history is not proof, but it is no longer nothing.
|
|
220
|
+
|
|
221
|
+
**A cost finding, stated twice because the first version was wrong.** The probe reported completion
|
|
222
|
+
usage of 969–3237 tokens against a 700-token request. The inference I drew — that the annotation
|
|
223
|
+
ceiling was truncating mining — was **wrong**: a direct test of the same route with a 64-token cap
|
|
224
|
+
still produced 113 tokens with `finish_reason: stop`, and usage itemizes
|
|
225
|
+
`completion_tokens_details.reasoning_tokens`. `max_tokens` is advisory on this provider, not
|
|
226
|
+
enforced, and reasoning counts toward completion usage. So nothing was truncating; what was true is
|
|
227
|
+
that the ceiling had no business being a literal at three call sites (900/900/600) sized for
|
|
228
|
+
pre-mining answers. It is now `enrichmentMaxTokens`, default **4000** — above the observed 3.2K —
|
|
229
|
+
because a *strict* provider would enforce it, and one that itemizes reasoning deserves a budget that
|
|
230
|
+
covers the thinking. The lesson stays worth recording: a usage number is not evidence about a
|
|
231
|
+
mechanism until the mechanism is tested directly.
|
|
232
|
+
|
|
233
|
+
**Inference QUALITY had no witness when the posture was set; the first data is below and it is thin.** The mechanisms are proven against fixtures; nothing here can say how often a small model infers a *correct* standing order, now that inferences govern by default. That is the open risk of the 2026-10-21 decision, and the measured first step is cheap: point the enrichment route at a reachable model, run `/chapters-enrich run` over real archives, then read the `stated` vs `inferred` ratio in `/chapters-rule list --scope session`. If inferences are wrong more often than a human catches them in replies, the answer is `rulesAutoActivate: false` — not a new evidence gate (the amendment records why the gate was the wrong instrument). The box's GPU driver was unreachable when this landed (`nvidia-smi` cannot talk to it), so no vLLM boot and no real mining pass ran.
|
|
234
|
+
|
|
235
|
+
**Two environmental facts, learned the hard way on 2026-10-20 and worth repeating.** (1) The
|
|
236
|
+
deterministic suites need **Node ≥ 24** (`package.json` engines); on Node 22 every test file fails
|
|
237
|
+
with `ERR_NO_TYPESCRIPT`/unknown-extension, which looks exactly like a broken change and is not one.
|
|
238
|
+
(2) Under `npm run coverage:ci`'s parallel load the real-HTTP-git files
|
|
239
|
+
(`git-auth-classification`, `sync-remote`) intermittently failed with an EPIPE blamed on their
|
|
240
|
+
`before` hook. It was **recorded as a box-level flake and left alone — which was the wrong call**: the
|
|
241
|
+
"prove it predates my diff" step was right, and stopping there was not. The cause was real and is now
|
|
242
|
+
fixed in the rig: the test git server wrote each request body into a CGI child's `stdin` with **no
|
|
243
|
+
error listener**, so a child that had already exited raised an async EPIPE with no handler, which the
|
|
244
|
+
runner reported as uncaughtException after the test ended. Guards now sit on the child's three
|
|
245
|
+
streams and on both request/response objects ([tests/integration/http-git-server.ts](http-git-server.ts));
|
|
246
|
+
three consecutive combined runs since then are 800 pass / 0 fail. Lesson for the next agent, in the
|
|
247
|
+
repo's own style: a pre-existing failure is still a failure.
|
|
248
|
+
Ledger on this branch: **93.16% statements / 85.53% branches / 90.01% functions** — the gate holds.
|
|
249
|
+
|
|
168
250
|
## The Cache Metric, Before You Trust a Number
|
|
169
251
|
|
|
170
252
|
`usage.inputTokens` is the **uncached delta** and `usage.cacheReadTokens` is the cached prefix, so:
|
|
@@ -204,6 +286,27 @@ that still need a running chapter engine:
|
|
|
204
286
|
| E7 | Arrival-time artifacting (architecture amendment) | **green 2026-09-19 (9.3m)**: 373KB book / one read → exactly 1 arrival prune at the tail; model answers buried token + chapter count via 5 `chapters_artifact` calls; ZERO compaction events at production 0.9; blob fingerprint in no `request/*` event; cache table in FINDINGS shows prefix intact (cacheRead monotonically 15.7K→24.5K) |
|
|
205
287
|
| E8 | Plot carriage across in-place checkpoints | forward path live (persona discipline — suiteB oversized run carries `PLOT:` blocks through 7 compactions); render→extract round trip pinned in unit (`carried plot survives the checkpoint round-trip`); elicited fallback unit-pinned (capped input/output, degrades to null) — fires only when the agent writes no note | compaction lands at `agent/pre-step`, bracketed `compaction/start.turn` = open turn number (r13/r17 `turn: 2`); never mid-request, never mid-step |
|
|
206
288
|
|
|
289
|
+
## Door 5 — Subagent Settlement (#47; read contract corrected by #58)
|
|
290
|
+
|
|
291
|
+
The settlement door is the only archive that runs with nobody watching, so its checks are about **payload**,
|
|
292
|
+
not about plumbing. Every row here was verified to fail on the mutation it guards.
|
|
293
|
+
|
|
294
|
+
| # | Check | How to verify |
|
|
295
|
+
|---|---|---|
|
|
296
|
+
| S1 | The durable read is payload-bearing | `tests/unit/host-durable-read.test.ts` reads the **host's own published types** and asserts `SessionEventRecord` has no `data` key while `readSession`/`snapshotSessionEvent` yields one. This is a kernel-drift gate: a host rename must turn it red, not re-create husks. Cross-check by hand: `zstd -dc ~/.dsh/sessions/<slug>/<id>/session.v4.jsonl.zstd \| head -20` for a settled child — the real log has `data.name`, `data.arguments`, `data.message.content`; a hollow chapter has none of them. |
|
|
297
|
+
| S2 | Both arms, both modes | A **one-shot** child settles with a live handle (`snapshotEvents()`); a **continuable** child is disposed *before* `subagent/end` (`dsh-subagent/lib/index.js:1240-1250`) so only the durable arm runs. Integration rows cover each arm × each mode, and one covers **depth 2** (a continuation child that delegates — `d57b4710`'s parent is `ch-dc3be3ab` in the SAIP field data). |
|
|
298
|
+
| S3 | No archive that rendered nothing | Feed the real `data`-less record shape to `saveSession` and assert: skipped `payloadless`, **no file written**, **no chapter number reserved**, `nextChapterNumber` unchanged. Then the converse pair: the same span read through `readSession` archives with `stats.messages > 0`. |
|
|
299
|
+
| S4 | The watermark is never poisoned | After a S3 refusal, a later settle of the same child still archives (i.e. `max(endSeq)` did not advance). This is the property that makes the defect recoverable instead of permanent. |
|
|
300
|
+
| S5 | Warnings survive the door | `writeArchive` emits `N event(s) rendered no output` for a low-content chapter; assert door 5 logs it (via the injected `warn`/sink), not returns-and-drops. |
|
|
301
|
+
| S6 | Lineage recorded, tree NOT re-homed | After a settle the child's own row/frontmatter carry `subagent` + `parentSession` from the durable header, and the child keeps its OWN tree: `rootSession` is unchanged and its chapter paths still resolve. **AMENDED 2026-10-22 (#58):** this row originally demanded `linkChild` onto the parent's path. Measured against the code, that buys no reach (`buildIndex` walks ancestors only) and re-homes files already written under the child's root, so the door does not link; the row now pins the absence, and `subagent-auto-save.test.ts` ("lineage by design") holds both halves. |
|
|
302
|
+
| S7 | Fanout containment | A 10-child fanout schedules sync 10× and runs enrichment **0** times (`runNow` never per settlement) and no child blocks another (per-child promise chains). |
|
|
303
|
+
| S8 | Field census (human, one-time) | `SELECT` the store: no chapter may have `messages === 0` with `events > 0`. The recovery half of this defect is #60; measure **before/after counts** there, and quote them as a repair result, never as a passing test. |
|
|
304
|
+
|
|
305
|
+
**Anti-pattern that created this section:** a stub that returns *your* type instead of the host's.
|
|
306
|
+
`tests/integration/subagent-auto-save.test.ts` once faked `listEvents: async () => durable` with full
|
|
307
|
+
events and passed against a host verb that returns metadata. If a test proves a door works while the
|
|
308
|
+
production arm returns a different shape, the test is the bug.
|
|
309
|
+
|
|
207
310
|
## Bug Reports
|
|
208
311
|
|
|
209
312
|
Cache and context behaviour are the plugin's core promise, so a report must include:
|
|
@@ -234,3 +337,117 @@ transport registry). The first push against a real remote is therefore a
|
|
|
234
337
|
If isomorphic-git ever chokes on a specific forge's smart-HTTP, the documented
|
|
235
338
|
fallback is a CLI driver (the `GitDriver` seam in `src/gitops.ts` exists for
|
|
236
339
|
exactly this swap) — the loop's logic is driver-agnostic by design.
|
|
340
|
+
|
|
341
|
+
## Prompt recommendations (#71) — SPEC + verification rows
|
|
342
|
+
|
|
343
|
+
Interface: site §28 (`concepts/prompt-recommendations`); host facts in `docs/contract.md`
|
|
344
|
+
§ Composer Ghost Layer. Off by default — **the effective model string non-empty IS the
|
|
345
|
+
enable**; there is deliberately no second boolean.
|
|
346
|
+
|
|
347
|
+
Deterministic rows (each must be proven to fail on the mutation it guards):
|
|
348
|
+
|
|
349
|
+
| Row | Pins |
|
|
350
|
+
|---|---|
|
|
351
|
+
| enable derivation | `effectiveModel = setting('prompt.model') ?? config.promptModel`; empty or unset ⇒ OFF, and a stored empty override beats a non-empty config (`off` verb) |
|
|
352
|
+
| input assembly | chapter summaries first, then the live tail; total ≤ `promptContextChars`; host-injected spans defused BEFORE the cap; deterministic order (same input ⇒ same bytes) |
|
|
353
|
+
| answer validation | trim → reject empty → reject > `promptMaxChars` → ONE retry with the cap restated → refuse with the measured length. Never truncate |
|
|
354
|
+
| cache & watermark | one entry per session keyed by the last completed `turn/end` seq; fresh hit ⇒ zero llm calls; new turn ⇒ recompute on next ask; in-flight dedup shares one promise |
|
|
355
|
+
| command arms | `model` show/set/clear texts, `off`, `suggest` refusals (off / no route / not computable / model failure) each name their remedy |
|
|
356
|
+
| status line | `/chapters-status` prints ON with the model + source, or OFF — both states, linked store or not |
|
|
357
|
+
| client pure logic | `shouldShowGhost` matrix: enabled ∧ !running ∧ draft==='' ∧ hasText ∧ !suppressed/boundary; accept ⇒ setDraft; typing ⇒ hidden; Esc ⇒ suppressed until the boundary moves |
|
|
358
|
+
| client anti-dup | `src/client/index.ts` contains no `commands.execute` for the ghost either — the wire body lives in `src/client/prompt-ghost.ts` ONE time, mirroring the door-runner rule (tests/unit/client-doors.test.ts pattern) |
|
|
359
|
+
| honesty | a suggestion never reaches a chapter file, the notice, the pool, or any model request: an archived session that used the feature contains zero recommendation bytes |
|
|
360
|
+
|
|
361
|
+
Manual browser procedure (browser-plane claim stays **gated by #56** — the corpus must be
|
|
362
|
+
re-anchored before replay can vouch for it):
|
|
363
|
+
|
|
364
|
+
1. `/chapters-prompt model local/qwen3.8-flash` (or a cloud route) ⇒ expect "…enables the feature".
|
|
365
|
+
2. Finish a turn, idle with an empty draft ⇒ the ghost line appears dimmed inside the
|
|
366
|
+
composer card, labeled with the model; the host placeholder is HIDDEN beneath it (draft
|
|
367
|
+
empty ⇒ host placeholder visible — our layer renders over it; if both are readable at
|
|
368
|
+
once, that is a styling bug).
|
|
369
|
+
3. Type one character ⇒ ghost gone instantly; delete it ⇒ ghost back (while not accepted).
|
|
370
|
+
4. Click into the editor, press Right-arrow ⇒ suggestion lands as real draft text, cursor
|
|
371
|
+
at its end; nothing sends. Enter sends as a normal human message — then verify in the
|
|
372
|
+
log that the message is plain human-authored.
|
|
373
|
+
5. Esc on a fresh ghost ⇒ hidden for this turn; send any turn ⇒ boundary moves, a new offer
|
|
374
|
+
may come.
|
|
375
|
+
6. `/chapters-prompt off` ⇒ ghost never reappears in this or later sessions, `suggest`
|
|
376
|
+
refuses; `/chapters-status` says OFF.
|
|
377
|
+
7. Kill the model route (wrong provider) ⇒ ghost hidden, refusal only in command output, no banner.
|
|
378
|
+
|
|
379
|
+
**Distilled, observed, AND replay-verified 2026-10-22** (`prompt-suggestion.spec.ts`, project `prompt`,
|
|
380
|
+
port 41739): the scripted-upstream record run PASSED in a real browser on host 0.2.0-rc.2 — chip visible
|
|
381
|
+
with the suggestion, hidden by one typed character, re-shown on an emptied draft, Escape dismissed
|
|
382
|
+
boundary 1, the next boundary re-armed, ArrowRight accepted into the draft via the host's own
|
|
383
|
+
`InputActions.setDraft`, Enter sent it, and the log held the human message with zero persona bytes.
|
|
384
|
+
A true `E2E_MODEL=replay` run then passed with ALL SIX requests served from tape and zero misses
|
|
385
|
+
(corpus gate lifted for that check ALONE and restored byte-identical — the committed gate still
|
|
386
|
+
refuses the chain until #56 re-anchors the other eight; `tests/fixtures/model-tape/prompt/PROVENANCE.md`). The distill ALSO caught a rig bug worth remembering: `localModelUp` answered "no upstream" while a
|
|
387
|
+
healthy stub ran on the `E2E_UPSTREAM` port — the guard probed :8080 for any LOCAL base, the same
|
|
388
|
+
silent-skip family #54 fixed for remotes. A ROW had pinned that behavior ("loopback is answered on
|
|
389
|
+
:8080, not by E2E_UPSTREAM") — and the row was measured WRONG in use, twice, by this very journey
|
|
390
|
+
refusing to run against a live stub. Per the house rule (change obsolete behavior with its tests):
|
|
391
|
+
the row now pins probe-the-actual-base for local upstreams, plus a dead-local-port NO arm, with the
|
|
392
|
+
reason in its comment. The guard lying by REFUSING is the same disease as lying by nodding.
|
|
393
|
+
|
|
394
|
+
## Release gate — field checks, because green tests shipped a broken product twice
|
|
395
|
+
|
|
396
|
+
Every item below is the shape of a defect that reached `main` or was found in the field (SAIP,
|
|
397
|
+
issues #58–#61, #77–#82) **while the suite was green**. They are not more unit tests: several of
|
|
398
|
+
this week's bugs had rows passing correctly around them. The gate is that a human or an agent
|
|
399
|
+
observes the affordance working on a real project, and that every new instrument is shown to be
|
|
400
|
+
capable of failing.
|
|
401
|
+
|
|
402
|
+
Run on a scratch clone of a real project (not this repo), at the pinned host and pinned window.
|
|
403
|
+
|
|
404
|
+
1. **A continuation opens, not just a continuation created.** Fork (button or `chapters_continue`),
|
|
405
|
+
then require: the app's current session IS the child (read `dsh.sessions.current`, or the door's
|
|
406
|
+
own note — it now refuses to claim a switch it cannot verify), the child is in the sidebar, and
|
|
407
|
+
the state survives a reload. *Why: #77 — three evenings of "the client refuses to catalogue
|
|
408
|
+
plugin-created children" were a door calling `sessions.open`, a verb that does not exist
|
|
409
|
+
(`docs/contract.md` § Client Sessions Service). Green throughout, because no test read the id.*
|
|
410
|
+
2. **The realm is mounted, not declared.** One session header must read `agentPreset: "chapters"`,
|
|
411
|
+
and the sink must show at least one `compactIfNeeded entered` line. *Why: the preset pin sat on
|
|
412
|
+
a retired row id (`agent-presets` → `agent-preset-registry`) and every acceptance boot ran the
|
|
413
|
+
standard realm for two days — zero `compaction/*` events in 15 logs, while leg verdicts looked
|
|
414
|
+
like product failures (#56, #78). "Declared" was asserted; "invoked" never was.*
|
|
415
|
+
3. **Context relief actually happens, with numbers.** Force one oversized read at the pinned
|
|
416
|
+
window; require `compaction/*` events (or an arrival stub pair) in the durable log, then that the
|
|
417
|
+
NEXT turn answers a plain question. *Why: #78's decline is now reported with
|
|
418
|
+
`surface=/trigger=/window=/ratio=`, and a session that survives but answers a stale transcript
|
|
419
|
+
token is exactly what `oversized-turn.spec` caught — after #81, the wrong answer had been the rig
|
|
420
|
+
sending the instruction twice.*
|
|
421
|
+
4. **A command reaches the human.** Run `/chapters-status` and check BOTH the durable
|
|
422
|
+
`command/done` AND the screen; today the screen half fails and that is #80, not a flake. A
|
|
423
|
+
delivered-but-invisible result is a feature the operator cannot use (#61a, #67).
|
|
424
|
+
5. **A disabled feature is quiet in the durable log.** On a boot without `promptModel`,
|
|
425
|
+
`countGhostCommandRuns(log)` must be ≤ 1 (one capability probe per mount). *Why: #79 measured 4–26
|
|
426
|
+
`chapters-prompt` command pairs per session — a "once per mount" effect depending on `[props]`,
|
|
427
|
+
writing into the transcript this plugin archives, with the OFF test passing because it only
|
|
428
|
+
looked at the chip.*
|
|
429
|
+
6. **The rig's pins reach the realm.** The boot's own `cordis.patch.yml` must carry this project's
|
|
430
|
+
`thresholdRatio` and arrival floor on the `chapters-engine` row. *Why: `Pins` were collected and
|
|
431
|
+
applied to nothing for a whole release cycle (leg headers claimed 0.5 while realm defaults
|
|
432
|
+
ran).*
|
|
433
|
+
7. **The corpus is one behavior.** `CORPUS_HOST`'s `host` equals the pin, and every project's
|
|
434
|
+
`recordedAgainst` names the same code generation. If any leg was recorded before a rig-behavior
|
|
435
|
+
change (send timing, window enforcement), re-record it: a mixed corpus passes for the wrong
|
|
436
|
+
reason (#56, #81, #82).
|
|
437
|
+
8. **A boot may read only tracked state.** List every file the e2e home is seeded from and prove each is
|
|
438
|
+
in `git ls-files` — a gitignored path in that chain makes the corpus a recording of one laptop.
|
|
439
|
+
*Why: #89, three layers deep. First the matcher folded no workspace root (CI's `/home/runner/…` vs
|
|
440
|
+
the recorded `/home/adrian/…`); then `spawn('dsh')` took PATH and got a **linked dev build** whose
|
|
441
|
+
version string matched the pin; and even with the pinned tarball booted by absolute path, CI sent a
|
|
442
|
+
2-message first request against a 6-message tape — because `boot.ts` copied
|
|
443
|
+
`.dshdev-local/settings.yaml` (gitignored) with the provider route and `contextWindow: 32000`. Each
|
|
444
|
+
layer looked like the end of the story; the tape diff (`E2E_TAPE_DIFF` / `E2E_MISS_DUMP`) is what
|
|
445
|
+
said otherwise every time. Reproduce across machines before believing a green local chain.*
|
|
446
|
+
|
|
447
|
+
9. **Instrumentation must be shown to be capable of failing.** For every gate this week added, name
|
|
448
|
+
the mutation that reddens it — the hygiene guard, the roster predicate, the report helper, the
|
|
449
|
+
window rows and the bounded-scroll row all have one. *Why: three bugs this week lived in code
|
|
450
|
+
written to observe the fourth — `\bpending\b` missing an escaped newline, `ids.size > 0`
|
|
451
|
+
disabling every #77 candidate, a helper resolving logs against an import-time home so a gate
|
|
452
|
+
could never pass, and an unbounded `scrollIntoViewIfNeeded` that hung a leg for 17 minutes. Each
|
|
453
|
+
looked like a product failure. **An unexercised assertion is a rumor of an assertion.**
|