Add RFCs for the remaining quality-proposal ideas
Eight proposals grouped by category, each with problem statement, concrete plan, and risks: property-based testing over the protocol-shaped core (chunk streams, event logs, schema DSL); mutation testing as the counterweight to the 100%-coverage gate; deterministic tests + a universal replay-invariant fixture + nightly race stress; architectural conformance (dependency-cruiser rules and the LlmAdapter conformance kit); runtime arg validation at the model boundary with a structured error taxonomy and dev-mode invariants; doc-sync enforcement (typechecked doc snippets, API reports); supply-chain checks and nightly vendor-drift verification against the manifest; and deep-readonly public surfaces (logged-vs-in-flight mutability boundary). AGENTS.md points at docs/adr and docs/rfc.
This commit is contained in:
44
docs/rfc/008-immutable-public-surfaces.md
Normal file
44
docs/rfc/008-immutable-public-surfaces.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# RFC 008: Deep-readonly public surfaces
|
||||
|
||||
Status: proposed
|
||||
|
||||
## Problem
|
||||
|
||||
The session log is append-only by contract, but `session.events` returns
|
||||
`readonly SessionEvent[]` whose *elements* are mutable: a plugin can reach in
|
||||
and rewrite history (`events[0].data.content.push(...)`), silently breaking
|
||||
replay equivalence and the derived-history guarantee. The same applies to
|
||||
derived messages and prompt assemblies passed through waterfalls — mutation
|
||||
is sometimes the intended idiom (waterfall middleware mutates the request)
|
||||
and sometimes corruption (mutating a *logged* event), and the types don't
|
||||
distinguish.
|
||||
|
||||
## Proposal
|
||||
|
||||
Make immutability part of the type where mutation is corruption:
|
||||
|
||||
- `SessionEvent` data becomes `DeepReadonly` on the way OUT of a session
|
||||
(`events`, `session/event` listeners); `append()` keeps taking plain
|
||||
mutable input. A `DeepReadonly<T>` utility type lands in dsh-llm next to
|
||||
the brand/never helpers.
|
||||
- `deriveMessages()` returns deep-readonly messages; the loop clones before
|
||||
handing a mutable request to the `agent/request` waterfall (mutation there
|
||||
is sanctioned — the clone makes the boundary explicit and cheap, once per
|
||||
step).
|
||||
- `PromptAssembly` stays mutable through its waterfall (sanctioned) but the
|
||||
registry's internal section list is cloned per assembly (already true).
|
||||
- Optionally, dev-mode `Object.freeze` of event data behind the RFC 005
|
||||
invariants flag, so sanctioned-mutation violations throw in tests rather
|
||||
than corrupting silently.
|
||||
|
||||
## Plan
|
||||
|
||||
Introduce `DeepReadonly`, flip the session read paths, fix resulting
|
||||
compile errors in consumers (expected: a handful in tests), add the
|
||||
freeze-in-dev option alongside RFC 005's invariants plugin.
|
||||
|
||||
## Risks
|
||||
|
||||
`DeepReadonly` types can produce noisy errors at waterfall boundaries where
|
||||
mutation IS the API — keep the mutable/readonly boundary exactly at "logged
|
||||
vs in-flight" and document it in the session README.
|
||||
Reference in New Issue
Block a user