Files
vscode-cowriting-plugin-con…/specs/coauthoring-cross-rung-format.md
T

492 lines
28 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
status: graduated
---
# Solution Design: Cross-Rung Git-Native Persistence Format + Gitea Round-Trip (F5)
| | |
| --- | --- |
| **Author(s)** | Ben Stull (with Claude) |
| **Reviewers / approvers** | Ben Stull |
| **Status** | `draft` |
| **Version** | v0.1.0 |
| **Source artifacts** | Epic `benstull/vscode-cowriting-plugin#1` · Feature `#14` (F5) · Parent specs: `coauthoring-inner-loop.md` (§9 OPEN→F5: "the inner-loop shape, not yet the cross-rung contract"), `coauthoring-attribution.md` (§9 OPEN→F5), `coauthoring-propose-accept.md` (§9 OPEN→F5) · Ladder lineage: `ben.stull/rfc-app#48` (the three rungs) and `ben.stull/rfc-app#46` (the Gitea-substrate rung: "continuity of substrate — no migration cliff") · F4 shipped: `#12` (PR #13, session 0012) |
**Change log**
| Date | Version | Change | By |
| --- | --- | --- | --- |
| 2026-06-10 | v0.1.0 | Initial draft — brainstorming session 0014 | Ben Stull + Claude |
---
## 1. Business Context
### 1.1 Executive Summary
F5 graduates the `.threads/` sidecar from "the editor's private shape" into the
**cross-rung contract**: a published, versioned, machine-validatable format any
rung of the ladder (editor → Gitea substrate → rfc-app) can read and write
without re-homing. It adds the three things a second writer needs — a
**contract** (prose + JSON Schema, with compatibility rules including
unknown-field preservation), a **cross-rung identity** extension on
`Provenance` (git's own email model, plus `onBehalfOf` for agents), and a
**deterministic merge** (`mergeArtifacts`) — and **proves the round-trip**: a
record written in the editor is modified out-of-editor by a Gitea-rung stand-in
and renders correctly on pull. Everything is additive; `schemaVersion` stays 1.
### 1.2 Background
Epic #1's outcome statement promises the record is "portable up to the Gitea
rung later **without re-homing**", and rfc-app#46 names the strategic prize:
**continuity of substrate — no migration cliff** ("git is truth; the surfaces
are clients"). Every prior design deferred the contract to exactly this
session: F2 called its format "the inner-loop shape, not yet the cross-rung
contract"; F3 and F4 each routed "may re-home `attributions[]` /
`proposals[]`" here. Meanwhile the shipped format was *built* for this moment
— plain pretty JSON, stable key order, trailing newline, state-not-history
sections, shared `anchors` — all chosen in F2 for mergeability. Issue #14
carried six design forks to this session; all six are resolved here (§6.7,
§9). One shipped fact forces real work: `serializeArtifact` rebuilds a
canonical object from **known fields only**, so today's writer would silently
destroy another rung's data on rewrite — the forward-compatibility gap F5
closes (INV-15).
### 1.3 Business Actors / Roles
- **Coauthor (human / machine)** — as in F2F4; unchanged in-editor.
- **The second writer** — new: a teammate's editor on another clone, or a
Gitea-rung surface appending to the same record per the contract.
- **The downstream rungs** — the Gitea substrate (rfc-app#46) and rfc-app:
consumers of the contract this Feature publishes.
### 1.4 Problem Statement
Portability has never been exercised. There is no published contract, no
compatibility/versioning rules for readers that aren't this codebase, no
forge-resolvable identity (`Provenance.id` is a local `$USER`), no defined
behavior when a second writer touches the record, and a writer that drops
fields it doesn't recognize. Until F5, "git is truth; the surfaces are
clients" is an assertion, not a property.
### 1.5 Pain Points
- Clone the repo on a second machine and the record *probably* works — nothing
guarantees it, and any other tool that rewrites the sidecar loses data.
- No rung can validate a sidecar without reading this extension's TypeScript.
- `kind:"human", id:"benstull"` means nothing to a forge; agents don't record
who they acted for.
- Two writers = undefined behavior; git will textually conflict pretty JSON
and nothing documents the semantic resolution.
### 1.6 Targeted Business Outcomes
A contract any rung can implement against; identity a forge can resolve; a
merge story a human (or tool) can execute deterministically; and a
demonstrated out-of-editor round-trip — converting the epic's portability
promise into a tested property and unblocking the rfc-app#46 substrate
brainstorming with a concrete input.
### 1.7 Scope (business)
**In scope:** the cross-rung contract (prose, normative) + JSON Schema +
rung-neutral validator; unknown-field preservation in the writer; the
`Provenance` identity extension (`email`, `agent.onBehalfOf`); newer-major
fail-safe (read-only) behavior; `mergeArtifacts` (deterministic union-by-id,
documented + unit-tested); the Gitea-rung stand-in writer script + round-trip
host E2E; byte-fidelity tests.
**Out of scope (deferred, not forgotten):** the Gitea-side substrate
primitives themselves (rfc-app#46's build — discussions UI, LLM participants,
native proposal layers); any rfc-app work; a sync daemon/server (git push/pull
IS the transport); a git merge *driver* installation (the pure function is the
semantics; wiring it into `.gitattributes` is a later convenience); file
rename/move tracking; `ratifiedBy` / multi-party provenance (team-rung
concern); rejected-proposal history trails.
### 1.8 Assumptions · Constraints · Dependencies
- **Parent:** Epic #1 (F5 is its final decomposition item; the three
acceptance pillars shipped in F2 #4, F3 #6, F4 #12). **Blocked by:** #12
(shipped). **Feeds:** the rfc-app#46 brainstorming and the rfc-app rung.
- **Established format facts** (the contract ratifies what shipped):
per-document `.threads/<path>.json`; shared `anchors` + `Fingerprint`
(text/before/after/lineHint, INV-3); `Provenance` (`human|agent`);
`threads[]` / `attributions[]` (state, not history) / pending-only
`proposals[]` (INV-13); `SCHEMA_VERSION = 1`; stable key order + trailing
newline (INV-2). The extension stops being the assumed sole writer.
- All changes are **additive** — existing sidecars remain valid v1 documents
unmodified; the serializer's optional-field-omission pattern (F4 precedent)
carries the new fields.
- No new auth/credential surface; no server.
### 1.9 Business Use Cases
- **BUC-1** A coauthor clones the repo on another machine (or a teammate
does) and the full record — threads, attribution, pending proposals —
arrives intact and keeps working.
- **BUC-2** A Gitea-rung surface (today: the stand-in; tomorrow: rfc-app#46's
primitives) appends a thread reply per the contract; on pull, the reply
appears in the editor's comment thread.
- **BUC-3** Two writers diverge; the documented merge yields one record with
no silent data loss, and anything genuinely conflicting is surfaced.
---
## 2. Solution Proposal
Publish the **contract** in two halves: a normative prose document
(`coauthoring-sidecar-contract.md`, a standing spec in the content repo) and a
**JSON Schema** (`schemas/coauthoring-sidecar.schema.json`, code-resident in
the plugin repo next to the reference implementation, wired into the unit
suite and runnable by any rung via a small validator script). Extend
`Provenance` additively with git's own identity model — `email` on humans,
`agent.onBehalfOf {id, email?}` on agents — and make the writer
**round-trip-safe**: unknown fields are preserved on rewrite (serialized after
known keys, sorted), and a sidecar with a newer major `schemaVersion` is
read-only (fail-safe). Ship the merge semantics as a pure, vscode-free
`mergeArtifacts(ours, theirs)`: sections union by `id`, thread messages union
by `id` ordered by `createdAt` (the forge-appends-a-reply case merges cleanly
by construction), same-id divergence resolved by `updatedAt` where present
else a deterministic tie-break — and every such resolution **surfaced** in the
result, never silent. Prove the loop with a Gitea-rung **stand-in writer**
(`scripts/crossrung-reply.mjs`: validate → append a reply → stable-serialize)
driven by host E2E through the existing external-change machinery.
---
## 3. Product Personas
- **PP-1 Inner-loop coauthor** — unchanged in-editor experience; gains
second-machine/teammate safety.
- **PP-3 Rung implementer** — new: the developer building the Gitea-substrate
or rfc-app surface against the contract (today impersonated by the stand-in
script and the validator).
## 4. Product Use Cases
- **PUC-1 (fidelity)** A sidecar written by the editor survives
serialize → git → reload byte-stable, and survives a *conforming foreign
writer's* rewrite with all fields intact (including ones that writer doesn't
know).
- **PUC-2 (out-of-editor reply)** The stand-in appends a thread reply per the
contract → the editor (on external change / pull) renders the new message in
the right thread at the right anchor.
- **PUC-3 (merge)** Two divergent copies merge via `mergeArtifacts`: disjoint
changes union; the reply-append case is clean; same-id divergence resolves
deterministically and is reported.
- **PUC-4 (fail-safe)** The editor encounters a sidecar with a newer major
`schemaVersion`: it surfaces a warning and does not write — never destroys
what it can't fully read.
- **PUC-5 (validation)** Any rung validates any sidecar against the schema
(`scripts/validate-sidecar.mjs <file>` or any standard JSON Schema
validator); the plugin's unit suite validates its own outputs.
## 5. UX Layout
No new UI surface (Claude Code, not Claude Design — same §5 call as F3/F4).
Out-of-editor replies render through the **existing** F2 thread machinery
(external-change → re-render). The only new user-visible element is a warning
notification + read-only behavior on a newer-major sidecar (PUC-4), reusing
the established notification patterns. The contract, schema, validator, and
merge function are developer-facing artifacts.
---
## 6. Technical Design
### 6.1 Invariants
Parent invariants INV-1..INV-13 carry over unchanged. F5 adds:
- **INV-14 (contract-canonical):** the contract document is the format's
source of truth. Format changes land in the contract first, then in code —
documentation-leads-automation (handbook §4.1) applied to the format. The
JSON Schema and the TypeScript model conform to it; on disagreement the
contract wins.
- **INV-15 (round-trip preservation):** a conforming writer **preserves
fields it does not recognize** when rewriting a sidecar. No rung's writer
may destroy another rung's data. (Unknown keys serialize after known keys,
sorted lexicographically, so output stays deterministic — INV-2.)
- **INV-16 (versioning):** within a major `schemaVersion`, changes are
**additive-only** (new optional fields); readers ignore unknown optionals.
A reader encountering a **newer major** version goes **fail-safe
read-only**: render what it understands, write nothing. Breaking changes
bump the major and are recorded in the contract's changelog.
- **INV-17 (merge honesty):** merging is deterministic union-by-id per the
contract's documented rules; any same-id divergence is resolved by those
rules **and surfaced to the caller** — never silently guessed (INV-1's
spirit at the artifact level).
### 6.2 High-level architecture
Four small units — two artifacts, two vscode-free modules — plus a serializer
change and one fail-safe check; controllers and the editor UX are otherwise
untouched:
- **The contract** (`vscode-cowriting-plugin-content`:
`specs/coauthoring-sidecar-contract.md`) — normative prose: every section
and field, its semantics and invariants, the identity model, the
compatibility rules (INV-15/16), and the merge rules (INV-17), with a
changelog. Status-stamped and graduated like a spec; versioned with the
schema.
- **The schema + validator** (plugin repo:
`schemas/coauthoring-sidecar.schema.json` + `scripts/validate-sidecar.mjs`,
`ajv` as a devDependency) — machine-checkable conformance; wired into the
vitest suite (every serialized fixture validates) and runnable standalone
by any rung.
- **`model.ts` extensions** — `Provenance` gains optional `email` (human and
agent alike); the agent payload gains optional
`onBehalfOf: { id: string; email?: string }` (the operator the machine
acted for — rfc-app#46 §6.5's `On-behalf-of:` made data). The serializer
gains **unknown-field preservation** (INV-15) for the artifact root and
every record type. `currentAuthor()` starts populating `email` from the
workspace git config when available.
- **`mergeArtifacts`** (new vscode-free `src/mergeArtifacts.ts`) —
`mergeArtifacts(ours, theirs): { merged, conflicts }`: anchors map union;
threads/attributions/proposals union by `id`; thread `messages` union by
`id` ordered by (`createdAt`, `id`); same-id record divergence → newer
`updatedAt` wins where the type has one, else the lexicographically larger
canonical serialization (deterministic), and the id is reported in
`conflicts`. Refuses (throws) on differing majors (INV-16).
- **Fail-safe check** — `CoauthorStore.load` surfaces the artifact's
`schemaVersion`; controllers (via one shared helper) treat a newer major as
read-only: render best-effort, skip every `store.update` path, warn once
(PUC-4).
- **Stand-in writer** (`scripts/crossrung-reply.mjs`) — the Gitea-rung
impersonator: read sidecar → validate → append a `Message` to a named
thread (new `id`, `createdAt`, author with `email`) → stable-serialize →
validate again. Doubles as executable documentation of "how a foreign
writer behaves."
```mermaid
flowchart LR
subgraph editor rung
ext["extension writers\n(Thread/Attribution/Proposal controllers)"] --> ser["serializeArtifact\n+ unknown-field preservation (INV-15)"]
end
ser --> sc[".threads/&lt;doc&gt;.json\n(canonical carrier)"]
sc <--> git["git push / pull\n(the transport)"]
git <--> forge["Gitea rung\n(today: crossrung-reply.mjs stand-in)"]
contract["contract doc (content repo)\n+ JSON Schema (plugin repo)"] -. governs (INV-14) .-> ser
contract -. governs .-> forge
sc --> merge["mergeArtifacts\n(union-by-id, surfaced conflicts, INV-17)"]
sc --> guard{"schemaVersion major\n&gt; known?"}
guard -- yes --> ro["read-only + warn (INV-16)"]
guard -- no --> render["existing render/re-anchor paths\n(F2/F3/F4, unchanged)"]
```
### 6.3 Data model & ownership
Additive only — `schemaVersion` stays **1**; every existing sidecar is already
a valid v1 document:
```jsonc
// Provenance — human (new optional identity)
{ "kind": "human", "id": "benstull", "email": "ben@wiggleverse.org" }
// Provenance — agent (new optional on-behalf-of)
{ "kind": "agent", "id": "claude",
"agent": { "sdk": "@cline/sdk", "model": "sonnet", "sessionId": "run_…",
"onBehalfOf": { "id": "benstull", "email": "ben@wiggleverse.org" } } }
```
- **Identity is git's model:** `email` is the cross-rung join key (forges
already map email → account); `id` remains the display/short name. No
signatures in v1 (provenance, not authentication — the git history is the
integrity layer).
- **Ownership changes:** the extension is no longer the assumed sole writer.
Any conforming writer may append/modify per the contract; the
self-write-suppression machinery is untouched (foreign writes *should*
arrive as external changes and re-render — that is the F2 path).
- **Unknown fields:** preserved on rewrite at every level (artifact root,
anchor, thread, message, attribution, proposal), serialized after known
keys, sorted (INV-15, INV-2).
### 6.4 Interfaces & contracts
- **Contract doc** — normative; field tables per section + invariants +
compatibility (INV-15/16) + merge rules (INV-17) + changelog. The F2/F3/F4
spec §6.3s are its inputs; it supersedes them as the format reference
(INV-14).
- **Schema/validator** — `validate-sidecar.mjs <file>` exits 0/1 with
per-error paths; vitest helper `expectValidSidecar(artifact)` used by the
model/store/merge suites.
- **`mergeArtifacts(ours: Artifact, theirs: Artifact): { merged: Artifact;
conflicts: string[] }`** — pure, vscode-free, documented as *the* reference
implementation of the contract's merge rules. Not yet wired into any UI;
rungs and humans invoke it (a `.gitattributes` merge driver is a later
convenience, §1.7).
- **`CoauthorStore.load`** — unchanged signature; a shared
`isNewerMajor(artifact)` helper gates every write path (controllers skip
`update`, warn once per doc).
- **`scripts/crossrung-reply.mjs <sidecar> <threadId> <body> [--author-id
--author-email]`** — the stand-in; validates before and after its write.
### 6.5 PerProduct-Use-Case design
- **PUC-1 (fidelity):** unit tests pin byte-stable serialize→parse→serialize;
a "foreign writer" test parses a fixture containing unknown fields (root +
per-record), rewrites via `serializeArtifact`, and asserts the unknowns
survive in sorted-after-known position (INV-15).
- **PUC-2 (out-of-editor reply):** host E2E — create a thread in the editor
(F2 path) → save → run the stand-in against the sidecar (append reply) →
trigger the external-change path (the F2/F3/F4
`externalWriteAndReload`/watcher pattern) → assert the thread renders the
new message at the re-resolved anchor. No LLM; no real network — the
stand-in is a local process, exactly what a forge's write looks like to git.
- **PUC-3 (merge):** unit tests of the documented cases: reply-append vs.
disjoint editor edit (clean union, no conflicts); both sides append to the
same thread (messages interleave by `createdAt`, no conflicts); same
attribution id with divergent extents (newer `updatedAt` wins, id
reported); divergent same-id proposals (deterministic tie-break, id
reported); differing majors → throws.
- **PUC-4 (fail-safe):** unit: a v2 fixture loads, `isNewerMajor` true; host
E2E: open a doc whose sidecar says `schemaVersion: 2` → warning surfaced,
edits/saves do NOT rewrite the sidecar (file bytes unchanged).
- **PUC-5 (validation):** the schema validates all shipped fixtures + every
serialized artifact in the unit suite; the validator script exits non-zero
on a deliberately broken fixture.
### 6.6 Non-functional requirements & cross-cutting concerns
All additions are O(artifact) pure functions; no runtime dependencies added to
the extension bundle (`ajv` is dev/scripts-only). Unknown-field preservation
slightly grows the serializer but keeps determinism (INV-2). Identity adds
PII-class data (emails) to a repo-committed file — acceptable: it is exactly
what git commits already record; the contract notes it so adopters know.
Fail-safe read-only can mean reduced function on version skew — by design
(INV-16); the warning names the version. No secrets, no server, no new auth.
### 6.7 Key decisions & alternatives considered
| Decision | Chosen | Alternatives rejected |
| --- | --- | --- |
| Substrate carrier (fork a) | `.threads/` sidecar stays the canonical cross-rung carrier | git notes (no default push/fetch — breaks "travels with ordinary git"; forges don't render notes; bind to commits while the record is document-scoped); commit trailers (only fit commit-shaped data); a forge reads/renders/diffs JSON files natively — the F2 decision gets *stronger* cross-rung |
| Contract form + home (fork b) | Prose contract = standing spec in the **content repo**; JSON Schema + validator = **plugin repo** (code-resident by the reference implementation, like the engineering repo's central-schema discipline applied at app scope) | schema in the content repo (test suite would read cross-repo; drifts from the model it validates); engineering `schemas/` now (org-central before a second rung exists — graduate it there when the Gitea rung lands, noted §9); prose-only (not machine-checkable); schema-only (semantics/invariants don't fit JSON Schema) |
| Cross-rung identity (fork c) | Optional `email` on `Provenance` + optional `agent.onBehalfOf {id, email?}` — git's own identity model; forges map email→account | forge usernames (couples the record to one forge — the ladder spans surfaces); signed identities (authentication, not provenance; git history is the integrity layer; YAGNI v1); a global actor registry (a server by the back door) |
| Merge semantics (fork d) | Pure 2-way `mergeArtifacts`: union-by-id; messages ordered by createdAt; updatedAt-else-deterministic tie-break; conflicts surfaced (INV-17) | 3-way with base (git gives no cheap base for sidecars in conflict resolution; union-by-id with fresh uuids makes 2-way sufficient at this scale); whole-file last-writer (silently loses the other side); mandated git merge driver (installation burden; the pure function IS the semantics — driver is later sugar) |
| Round-trip proof shape (fork e) | Local stand-in writer script (validate → append reply → stable-serialize) + the existing external-change E2E path; byte-stability covers transport | real Gitea API round-trip in CI (infra for nothing — git moves bytes verbatim; the forge's *write* behavior is exactly the stand-in); building any Gitea-side primitive now (rfc-app#46's tracker, not this Feature) |
| Versioning (fork f) | Everything additive → `schemaVersion` stays 1; INV-16 rules (ignore unknown optionals; preserve unknown fields; newer-major → read-only) | bump to 2 (nothing breaks — a bump would force every reader to handle two majors for no gain); no formal rules (the status quo — exactly the gap F5 closes) |
| Writer forward-compat | **Preserve unknown fields** on rewrite (INV-15), sorted after known keys | drop unknowns (today's behavior — a conforming v1.1 writer would destroy a v1.2 rung's data); refuse to write on unknowns (turns every additive change into a flag day) |
### 6.8 Testing strategy
- **Unit (vitest, vscode-free):** schema validation of every serialized
artifact (PUC-5); byte-stability + unknown-field preservation round-trips
(PUC-1); identity serialization (email/onBehalfOf present/absent — F4's
optional-omission pattern); `mergeArtifacts` documented cases incl. the
conflict reports and the differing-major throw (PUC-3); `isNewerMajor` +
v2-fixture load (PUC-4).
- **Host E2E (`@vscode/test-electron`, F2F4 pattern, no LLM):** the
round-trip — editor-created thread → save → stand-in appends a reply →
external-change → new message renders in the thread at the re-resolved
anchor (PUC-2); newer-major sidecar → warning + no rewrite on save (PUC-4).
- **Scripted checks:** `validate-sidecar.mjs` against good + broken fixtures;
the stand-in's own before/after validation. No manual smoke needed — F5 has
no LLM leg.
### 6.9 Failure modes, rollback & flags
A non-conforming foreign write → schema validation pinpoints it; the editor's
existing resolve-or-orphan ladder already handles semantic damage (anchors
that no longer resolve orphan, never guess). Version skew → INV-16 read-only,
recoverable by upgrading the older rung. Merge conflicts → surfaced ids +
documented rules; worst case the file is plain JSON a human resolves (the F2
stance, now with rules). Email absence (no git config) → fields omitted,
everything still valid (additive). No feature flag: additive schema + a
contract doc; old extensions keep working against new sidecars (they drop
unknown fields — which is precisely why INV-15 ships *now*, before any second
writer exists).
---
## 7. Delivery Plan
### 7.1 Approach / strategy
One planning-and-executing session (F5 = #14), plan written just-in-time from
this spec, per the F2F4 precedent.
### 7.2 Slicing plan
- **SLICE-1** The contract: `coauthoring-sidecar-contract.md` (content repo,
normative, with changelog) + `schemas/coauthoring-sidecar.schema.json` +
`scripts/validate-sidecar.mjs` (+ ajv devDependency), schema wired into the
unit suite (every serialized fixture validates).
- **SLICE-2** Writer conformance: unknown-field preservation in
`serializeArtifact` (INV-15) + `Provenance` identity extension
(`email`, `agent.onBehalfOf`) + `currentAuthor()` email population +
newer-major fail-safe (`isNewerMajor` gating all `store.update` paths,
PUC-4 warning) — with compat tests (existing sidecars load/serialize
unchanged).
- **SLICE-3** `mergeArtifacts` + unit tests of every documented merge case
(INV-17).
- **SLICE-4** The stand-in (`scripts/crossrung-reply.mjs`) + host E2E
round-trip (PUC-2) + newer-major E2E (PUC-4).
- **SLICE-5** Docs: README cross-rung section; plugin-repo pointers to the
contract; full gate.
E2E are first-class plan tasks (handbook §9 / §4; this app's required tier is
host E2E — a VS Code extension, no browser surface, no deploy stage — the
F2F4 precedent).
### 7.3 Rollout / launch plan
Still non-shippable (no marketplace publish). "Done" = issue #14 acceptance
met: contract + schema published, unit + host E2E green (round-trip, merge,
fail-safe, preservation all exercised). No manual smoke — no LLM leg.
### 7.4 Risks & mitigations
| Risk | Mitigation |
| --- | --- |
| Contract drifts from code | INV-14 (contract first) + the schema runs in the unit suite — drift fails CI |
| Unknown-field preservation breaks byte-stability | deterministic placement rule (after known keys, sorted) + round-trip tests |
| Merge rules too naive for real concurrency | scale honesty: union-by-id + fresh uuids makes overlap rare at inner-loop scale; conflicts are surfaced, file stays human-resolvable; revisit with the team rung |
| Email-as-identity is wrong for some forge | it is git's own join key and the lowest common denominator; forge-specific ids can be added additively later (INV-16) |
| Premature contract (Gitea rung may want changes) | everything additive + a changelog + the rfc-app#46 brainstorming consumes this as INPUT — cheaper to amend a contract than to reverse-engineer an implementation |
| Read-only fail-safe surprises a user on version skew | warning names the version and the action; recoverable by upgrading; tested in E2E |
---
## 8. Traceability matrix
| Requirement (issue #14 acceptance) | Use case | Design | Slice |
| --- | --- | --- | --- |
| Contract document with compatibility rules | PUC-5 | §6.2, INV-14/15/16 | SLICE-1 |
| Identity crosses rungs (Provenance) | PUC-2 | §6.3, fork (c) | SLICE-2 |
| Round-trip proven, no re-homing | PUC-1/2 | stand-in + INV-15, §6.5 | SLICE-2/4 |
| Concurrent-writer story defined and exercised | PUC-3 | `mergeArtifacts`, INV-17 | SLICE-3 |
| Validation runnable outside the extension | PUC-5 | schema + validator | SLICE-1 |
| Unit + host E2E coverage | — | §6.8 | SLICE-14 |
## 9. Open Questions & Decisions log
- **RESOLVED (this session — the six forks routed by #14):** (a) carrier =
the sidecar, ratified cross-rung; (b) contract = prose (content repo
standing spec) + JSON Schema (plugin repo, code-resident); (c) identity =
`email` + `agent.onBehalfOf`, git's model; (d) merge = pure 2-way
union-by-id with surfaced conflicts (INV-17); (e) proof = local stand-in
writer + external-change E2E + byte-stability; (f) versioning = additive,
`schemaVersion` stays 1, INV-16 rules (incl. writer unknown-field
preservation, INV-15).
- **OPEN → Gitea rung (rfc-app#46):** graduate the schema to the engineering
central `schemas/` when a second rung actually implements the contract;
whether the substrate wants a `.gitattributes` merge driver shipped;
forge-specific identity fields beyond email.
- **OPEN → later:** `ratifiedBy` / multi-party provenance (team rung); file
rename/move tracking (path is still the key); rejected-proposal history
trails; signed provenance if integrity-beyond-git is ever required.
## 10. Glossary & References
- **Cross-rung contract** — the published format definition all ladder
surfaces implement against. **Foreign/conforming writer** — any non-editor
process writing a sidecar per the contract. **Stand-in** — the local script
impersonating the Gitea rung's write behavior. **Fail-safe read-only** —
INV-16's posture on newer-major sidecars: render, never write.
- Epic #1 · Feature #14 (F5) · F2 #4 · F3 #6 · F4 #12 (PR #13) · parent specs
`coauthoring-inner-loop.md` / `coauthoring-attribution.md` /
`coauthoring-propose-accept.md` · rfc-app#48 (ladder) · rfc-app#46
(substrate rung) · `wiggleverse/engineering#19` (adjacent metadata
discussion).