ADR-007: The memory-mode gate follows what an operation reads or writes, not its method name¶
Status¶
Accepted, extended by ADR-018 (wing, room and drawer management follows the same gate)
Context¶
ADR-002 decided that memory mode is per-session and enforced in one place,
AppServices::require_read/require_write.
It left open which operations count as memory operations.
The first answer was by name: search, recall, wake_up, diary_read and the checkpoint and diary writes were gated,
and everything else (job listing, job control, mine, repair, audit) was called "administrative".
That classification leaked in both directions:
- A job record carries its whole input.
A checkpoint job's payload is the memory being written, so a
Disabledsession, which must behave as if MemCastle did not exist, could read palace content throughjobs listandjobs show, and through the MCP job tool. - A
ReadOnlysession could mutate the palace through the back door.minefiles drawers and an appliedrepairdeletes them, yet neither was gated, so a session forbidden to write could submit a job that did.
Decision¶
An operation is gated by what it does to palace content, whatever it is called or which interface exposes it.
- Reads (rejected only in
Disabled):search,recall,wake_up,diary_read, and joblistandshow, because a job record exposes its input. - Writes (rejected in
ReadOnlyandDisabled):checkpoint,emergency_checkpoint,diary_write,mine, and an appliedrepair(dry_run = false). - Administrative, never gated:
status(counts and a version, no content), job control (pause,resume,cancel,retry, which need a job id that a session unable to list jobs never learns),demo(touches no palace content),auditand a dry-runrepair(they only report). - A rejection names the operation the caller actually made (
emergency_checkpoint, notcheckpoint). - Read-only operations must not write:
a
ReadOnlyread of a wing that does not exist returns an empty result and creates nothing.
A new operation is classified explicitly, in app::AppServices and in domain::memory_mode's module documentation,
by asking two questions: does it return palace content, and can it change it?
Alternatives rejected¶
- Gate by method name or verb (
list_*andget_*are reads,submit_*is administrative). This is how the leak arose:submit_minesounds like queueing, andget_jobsounds like metadata. - Gate nothing that is "administrative" and document it.
It is true for
statusand job control, but not for anything whose result contains palace content or whose effect changes it. - Redact job records instead of gating them. A job's input is arbitrary and nested; a redaction that misses a field is a leak, and one that hides too much makes the record useless to the session that submitted it.
- Per-capability flags. Already rejected in ADR-002: three modes cover every case seen, and a flag matrix would have to be kept in step with this list.
Consequences¶
ReadOnlykeeps job reads (it can already read the same content throughsearch), and losesmineand appliedrepair.Disabledcannot list, show or otherwise read jobs, which also stops it discovering job ids, but can still control a job it already holds an id for.- Every interface inherits the classification, because it lives in
app; MCP, REST and the CLI cannot disagree. - Every new operation has to be classified, which is a review burden by design.