--- id: SHARD-WP-0008 type: workplan title: "write path — overlay engine, writable adapter, apply-under-drift" domain: whynot repo: shard-wiki status: done owner: tegwick topic_slug: whynot created: "2026-06-15" updated: "2026-06-15" depends_on: - SHARD-WP-0007 state_hub_workstream_id: "12bed418-39d6-47fa-a359-ff04bae6ec99" --- # SHARD-WP-0008 — Write path ## Goal Implement the **write path** on top of the foundation slice (SHARD-WP-0007): the **overlay-before-mutation** lifecycle (draft → patch → apply-under-drift) plus write-through for capable shards, faithful to `FederationRequirements.md` ADR-05, `CoreArchitectureBlueprint` §8.2 (overlay engine) / §8.6 (apply-under-drift) / §8.1 (overlays are coordination-canonical events). Target capability: **edit a page → draft overlay recorded in the decision log → apply (fast-forward) to a writable shard, or refuse on drift, or keep as local truth on a read-only shard**, with `overlay_state` surfaced in provenance (union without erasure). **Non-goal (this slice):** three-way/auto merge (refuse-on-conflict is enough now), federation propagation, network API, lossy native-syntax overlays. Those are later. ## Guiding rules - Overlay-before-mutation (I-5); no silent remote mutation. Detection is core, resolution is policy (I-7). Overlays/decisions are coordination-canonical (the decision log, §8.1). - Honour the §11 dependency rule and capability-as-data: write only where the verified profile supports `WRITE`; everything below write-through degrades to overlay (I-8). --- ## Writable file-store adapter + positive write conformance ```task id: SHARD-WP-0008-T1 status: done priority: high state_hub_task_id: "80492f8e-125c-4015-b3c0-821fbec038e0" ``` Make `FolderAdapter` optionally **writable** (`writable=True`): declare `WRITE` + `write_granularity=PER_PAGE` in the profile, implement `write(key, body)` (write-through to disk, return the updated `Page` with a new rev), and `current_rev(key)` for drift detection. Extend the conformance suite with a **positive, non-destructive write probe** for adapters that claim `WRITE` (write a probe key, read back, clean up). Tests: writable round-trip; read-only folder still rejects write; conformance passes for both. ## Overlay model + OverlayEngine.draft() ```task id: SHARD-WP-0008-T2 status: done priority: high state_hub_task_id: "cc6bf9a3-667d-468d-972d-dae51931a657" ``` `coordination/overlay.py`: an `Overlay` value type (id, target identity, base_rev, body, state) and `OverlayEngine.draft(identity, body, base_rev)` that records an `OVERLAY_CREATED` event in the decision log (coordination-canonical) and returns the draft. The log fold exposes open overlays. Tests: draft recorded + retrievable via fold; overlay id stable. ## Patch rendering ```task id: SHARD-WP-0008-T3 status: done priority: medium state_hub_task_id: "90d98c16-ed3b-414f-802c-b0400eca6ede" ``` Render an overlay as a reviewable **patch** (a `Patch` with a unified diff of base→overlay body, Markdown/native). Pure function over (base body, overlay body). Tests: patch shows the change; empty patch when unchanged. ## apply-under-drift ```task id: SHARD-WP-0008-T4 status: done priority: high state_hub_task_id: "2a0179b1-802e-44e6-883d-9f1babefee80" ``` `OverlayEngine.apply(overlay_id)` with §8.6 semantics: compare overlay `base_rev` to the shard's `current_rev`; **unchanged → fast-forward** (write-through via the adapter, record an applied/`MERGE_DECIDED` event, overlay state → APPLIED); **changed → refuse + re-present** as a conflict (no silent clobber); **read-only target → stays DRAFT** (local truth, fully attributed). Tests: ff apply mutates the shard; drift refuses; read-only keeps draft. ## Overlay-aware union read ```task id: SHARD-WP-0008-T5 status: done priority: medium state_hub_task_id: "4536d74f-3860-4b4c-82d2-e8d20e6e2125" ``` When resolving a page that has an **open overlay**, surface it: the read reflects `overlay_state=DRAFT` in the provenance envelope and (where policy shows drafts) the overlaid body as a local projection over the canonical page — never hiding that it is an unapplied overlay. Tests: page with a draft reads with overlay_state DRAFT; applied/none reads clean. ## Wiring into InformationSpace + integration ```task id: SHARD-WP-0008-T6 status: done priority: medium state_hub_task_id: "ab01fffb-61ad-416c-9f13-fdfbfd503153" ``` Add `InformationSpace.edit(name, body)` (write-through if the resolved shard supports WRITE, else create an overlay), `overlay(name, body)`, and `apply_overlay(id)`. Integration test for the full path (write-through on a writable shard; overlay→apply fast-forward; drift refusal; read-only shard keeps a draft). Update SCOPE; `pytest` + pyflakes green. --- ## Acceptance criteria - `pytest` green, pyflakes clean, no new runtime dependencies. - Overlay lifecycle works: draft (logged) → patch → apply (fast-forward) / refuse-on-drift / stay-draft-on-read-only; write-through works for capable shards. - No silent remote mutation: applying against a drifted/read-only target never clobbers. - Overlays are coordination-canonical (in the decision log); `overlay_state` is surfaced in provenance (union without erasure). - Each task committed; state-hub synced. ## Suggested task order T1 writable adapter → T2 overlay/draft → T3 patch → T4 apply-under-drift → T5 overlay-aware read → T6 wiring + integration.