edit()/overlay()/apply_overlay() on InformationSpace. edit() unifies the write path through one principled route — draft overlay then apply: write-through-capable target fast-forwards (APPLIED), read-only target keeps the draft as local truth (KEPT_DRAFT), external drift refuses (no clobber). Integration tests cover all four. 64 tests green, pyflakes clean. Flips WP-0008 done. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
5.3 KiB
id, type, title, domain, repo, status, owner, topic_slug, created, updated, depends_on, state_hub_workstream_id
| id | type | title | domain | repo | status | owner | topic_slug | created | updated | depends_on | state_hub_workstream_id | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| SHARD-WP-0008 | workplan | write path — overlay engine, writable adapter, apply-under-drift | whynot | shard-wiki | done | tegwick | whynot | 2026-06-15 | 2026-06-15 |
|
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
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()
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
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
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
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
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
pytestgreen, 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_stateis 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.