manage_adr could only replace: mode='update' overwrites the stored
document in full, so adding one entry costs a re-send of the whole ADR.
That is a data-integrity problem before it is a cost one — the caller has
to reproduce every byte it did not intend to change, so the unchanged
prefix survives only as well as the round-trip that carried it.
mode='set_sections' rewrites only the sections named in `section_updates`
and leaves the rest of the stored document untouched, so the stored copy
stays the authority for everything the caller did not name. Unlike a
whole-document append it is idempotent: a client that loses a response
and retries re-sets the same section to the same body and the document is
byte-identical, where an append would silently duplicate the chunk.
Only the six canonical section names are writable. That is a correctness
constraint rather than a style rule: adr_try_section_header() parses ONLY
canonical headers, so a non-canonical '## FOO' written here would be read
back as body text of the section above it, and a second identical write
would append a duplicate — destroying the idempotence the mode exists for.
Three things the new mode needed that were not there:
- cbm_store_adr_update_sections() now wraps its read-modify-write in
BEGIN IMMEDIATE. Three writers replace this row wholesale — the indexing
pipeline, the UI POST /api/adr handler, and mode='update' — so the
unguarded get/merge/store lost whichever of them committed between the
read and the UPSERT. mode='update' is a single atomic UPSERT and never
had that window; a section merge introduces it.
- set_sections joins the write_request classification. A mode missing from
it takes no per-project mutation lease, resolves the store query-only,
and never reaches open_adr_store_for_write — its write would be
attempted through a read-only handle while an index runs.
- The write path reads the legacy <root>/.codebase-memory/adr.md itself.
The existing migration runs on the read path only because it must not
block on the lease; without this a section write would merge onto an
empty document and discard an ADR still present on disk.
CBM_ADR_MAX_LENGTH now applies to an MCP write path: it is enforced inside
cbm_store_adr_update_sections, which mode='update' does not go through. An
empty section body, an unknown section name and a missing section_updates
are all rejected before any store is opened, so a caller that meant to
write never receives a success-shaped read.
Section-level update was chosen over the whole-document append proposed in
PR #1243; the analysis that established the problem is from that PR.
Co-authored-by: andis777 <24672074+andis777@users.noreply.github.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>