simplify(seams): prune dead methods from the persistence and bash seams

Two capability seams carried abstract methods no production consumer calls.
A method no consumer programs against is not a seam — it is speculative
surface every implementation must still provide and test.

- SessionPersistence: remove has() and delete(), the coordinator's
  has/delete/deleteCore, and the PersistenceBackend.deleteStored hook (with its
  jsonl + sqlite + in-spec memory-stub impls). Surviving service surface:
  create/append/load/list. Production uses only load() (resume) and list()
  (ACP session/list).
- BashExecutor: remove get(id) and list(), the abstract decls and the
  LocalBashExecutor impls. The internal tasks map survives (it backs
  ownerOf/readOutput/kill); get/list were pure public accessors over it with no
  shipping caller and no bash_list tool.
- Migrate tests that reached through ctx.bash.get(id) to the public completion
  seam: a doneFor(id) helper over onTaskDone awaits a task by id, and the
  HMR-reload ownership test now proves task survival through A's own bash_output
  ([status: running]) plus ownerOf + B-rejection — a stronger through-the-tool
  assertion than the removed lookup peek.
- Update seam READMEs (six -> four service methods, drop the deleteStored hook
  and the get/list row) and the two implemented persistence RFCs in place.

Implements docs/rfc/implemented/simplification/2026-06-20-prune-dead-seam-methods.md
This commit is contained in:
Tianyi Cui
2026-06-21 02:17:27 +08:00
parent 584349f881
commit 7792347c4f
25 changed files with 92 additions and 224 deletions

View File

@@ -214,17 +214,15 @@ describe('SessionPersistenceSqlite: durability and crash semantics', () => {
{ type: 'turn/start', seq: 0, time: 1, data: { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } } },
{ type: 'user/message', seq: 1, time: 2, data: { content: [{ type: 'text', text: 'hi' }], source: { kind: 'user' } } },
])
expect(await b1.ctx.sessionPersistence.has(m.id)).toBe(true) // materialized
await b1.dispose()
// A fresh backend loads it: the interrupted (only) turn's real events are
// preserved and closed with a synthetic turn/end {interrupted} — NOT
// truncated. The session was materialized, so has()/list() report it present.
// truncated. The session was materialized, so list() reports it present.
const b2 = await backend(path)
const loaded = await b2.ctx.sessionPersistence.load(m.id)
expect(loaded.events.map(e => e.type)).toEqual(['turn/start', 'user/message', 'turn/end'])
expect(loaded.events.at(-1)!.type === 'turn/end' && loaded.events.at(-1)!.data).toMatchObject({ reason: { kind: 'interrupted' } })
expect(await b2.ctx.sessionPersistence.has(m.id)).toBe(true)
expect((await b2.ctx.sessionPersistence.list()).map(x => x.id)).toContain(m.id)
await b2.dispose()
})