docs: align O2 chapter conversion and Case 2 access with owner rulings

This commit is contained in:
jason.woltje committed 2026-10-22 20:37:56 -05:00
1 parent 1ac76a4177
commit 8d209016b6
4 files changed
+757 -800

No files matched your search

+6
View File
@@ -5358,3 +5358,9 @@ Gander: after Duckworth-0100 design approval with C1-C5, prepared the two host-s
2026-10-22T23:13:27Z | Gyro Q195 O2 fenton-0007 F7/F8 | Named human-only editor.saveGroupPauseMs=2000 and editor.saveGroupMaxMs=15000 with integer/range/cross-field validation and W3a UI/CLI refusal tests. W3b owns production adoption refusal until W4b; isolated fixture conversion remains testable without request-flag bypass. W4b owns adoption module/API tests and reviewed lifting only with committed-reader/citation readiness and, if Q36 approves, the separate notice row PASS. Q36 unresolved stays closed. Static ten-brief, setting-key/bounds, refusal/lift ownership and two-question checks pass. Docs only; no build, browser/worker run, product/queue/live change. Fenton checks F7/F8 on next push.
2026-10-22T23:13:48Z | Gyro mos-1359 / r3 push guard | D13 records save-group timing as Mos's accepted reading of Q226 F41, not Jason's ruling; both defaults remain human-only settings. Push guard identity gyro/purpose push: exit 0 documented degraded-pass, CANNOT_ASSERT credential-unresolvable; audit gyro-q195-ci-queue-audit.jsonl, no credential fallback or CI-green claim.
2026-10-23T00:52:58Z | Gyro Q235-Q237 policy rewrite started, Vikunja 571, mos-1366/1367 | Before: approved O2 e36bce352 keeps Markdown originals at their paths and Q36 notices open; landed W4 2af460ab7 has the same boundary. Imported that byte-identical W4 baseline as 1ac76a417, from refactor 062646487, to make the policy-only revision diff explicit. Prepared P1-P6/B1-B4: human one-way per-story import, human-set source-repo archive, DOCX current reader, scoped format map/archived non-canon reads, W7 conversion publication after W4b/approved PRD/10-27 and Jason first-real-book go. Q234 exact CSP, mos-1363 and S0 M1-M14 retained. W4 locator/extractor, shares and restore-fence blocks compare byte-identical to the approved baseline. Scope/source review ongoing; Mos impact map must be folded before Fenton handoff. No build/browser/worker run, code, queue, private-book or live action.
2026-10-23T01:37:07Z | Gyro Vikunja571 final impact-map / mos-1372/1375 fold | Prepared parent/W4 rewrite against brain-redux d183246 impact map at stack 062646487. P1-P6/B1-B4 and engineering Q1-Q16 are mapped to eleven briefs. D9 replaces the earlier private O2-only context/per-document/share model: normal Case 2 access in any authorized thread/room, supporting writes intact, changed policy awaits PRD 1.17 approval. New DOCX story creation has the same opening gate as human per-story import. Host-only push to human-set off-host remote replaces the no-remote ban; remoteURL defaults unset and same-host bare is disallowed. W7 gates on Hermes #423 step5, W4b, PRD approval, post-10-27 and first-real-book go; owns conversion events COUNTED/--events/pre-conversion restore reconciliation, manifest order/next_chapter, creative role cite wording, archive/old-path edits and format/link/citation routing. Store+off-host acknowledgment precedes archive commit/push, then catalogue publication; post-push failures remain resumable pending with pinned source. Current checks pass: complete brief fields, policy/map coverage, exact Q234 CSP, locator/projection mechanics byte-equivalent except explicit authority wording, A01-A35 IDs, immutable map pin, four-doc scope and append-only logs. W4 authority/remote/recovery sections reopened for Fenton; older APPROVE not reused. No product/build/browser/worker/private-book/queue/live action. Earlier entry describing preserved shares/fence applies only to the pre-D9 intermediate draft; D9 now withdraws those unimplemented structures.
2026-10-23T01:37:55Z | Gyro 571 push guard | identity gyro/purpose push: exit 0 documented degraded-pass CANNOT_ASSERT credential-unresolvable, scratch audit gyro-q195-ci-queue-audit.jsonl; no credential fallback or CI-green claim.
+2
View File
@@ -1552,3 +1552,5 @@ are never rewritten or removed; corrections are new entries.
2026-10-22T23:03:39Z | Gyro (Codex gpt-6.1-sol, mos-1356) | Q195/Q231/Q233 O2 design rewrite, gyro/editor-o2 from c4f77373f | Canonical queue next gyro: nothing. Ten docs-only briefs; full-feature WordCraft, independent 15-crate/199-package security review, editor-only CSP candidate, instant git DOCX saves and retained Markdown/wiki sources, committed-only DOCX citation gate, no agent story prose. Static structure/source-anchor/scope checks only; no code, build/run, queue or live change. Fenton review requested after push.
2026-10-22T23:09:38Z | Gyro (Codex gpt-6.1-sol, fenton-0006) | Q195 O2 design corrections on gyro/editor-o2 | F1 requirement boundary sent to Mos, adoption gated; F2-F6 source-checked docs fixes prepared. Ten briefs and two open owner items checked statically. No implementation, build/browser/worker run, queue or live action; Fenton re-check after push.
2026-10-22T23:13:27Z | Gyro (Codex gpt-6.1-sol, fenton-0007) | Q195 O2 r3 F7/F8 | Save-group keys/defaults/validation and W3b refusal/W4b lift ownership explicit. Static docs checks only. Q36 remains open; no implementation or gate lift. Direct Fenton/Mos handoff after push.
2026-10-23T00:52:58Z | Gyro (Codex gpt-6.1-sol, mos-1366/1367) | Vikunja571 Q235-Q237 O2 parent/W4 format-policy rewrite on gyro/editor-o2 | Canonical queue next gyro: nothing. Prepared P1-P6 and B1-B4 docs, baseline W4 import 1ac76a417; no product/build/browser/worker/private-file/queue/live action. Mos impact map fold precedes review/push handoff.
2026-10-23T01:37:07Z | Gyro (Codex gpt-6.1-sol, mos-1368/1372/1375) | Vikunja571 O2 parent/W4 revision complete for review | P1-P6/B1-B4 and A17-A41/Q1-Q16 folded; same-thread Case 2 lookups/support writes, human-set host-pushed off-host remote, Hermes/restore/counting/order/role gates. Static docs checks pass; old locator/projection mechanics retained, authority/access review reopened. No code, build/browser/worker run, private conversion, queue or live action. Direct Fenton/Mos/Sage handoff after push.
File diff suppressed because it is too large. Load diff
+229 -569
View File
@@ -1,78 +1,91 @@
# Q195 O2 W4: committed DOCX citations and access
# Q195 O2 W4: committed DOCX citations and Case 2 access
Design for Gitea #1799, assignment sage-3060/mos-1360. Owner Rocko;
independent reviewer Megavolt. Normative parent: `e36bce352`,
`docs/plans/2026-10-21_q195-writing-editor.md`, blob
`322ec50f2669cffc0f8256d6beffdaccc4d8dba8`, D12, D24-D26, W4 and W4b.
This is a proposed implementation contract, not access enablement or a parser
security PASS. Reviewer owns the separate verdict file; the author does not
write an approval there.
Policy rewrite for Vikunja 571, mos-1366/1367/1368/1372/1375, Q234–Q237.
Normative parent: `docs/plans/2026-10-21_q195-writing-editor.md` in the same revision.
Original W4: Rocko/Megavolt, #1799, refactor 062646487, blob
`2af460ab772f8d204598dc8cb79b3c161f311623`. Gyro writes this assigned revision; Fenton
independently reviews it. The reviewer-owned verdict file is untouched.
Impact map: brain-redux d183246, `projects/mosaic-mvp/case2-docx-impact-map.md`, stack pin
062646487. Sections 1/2/6/7 and A19/A20/A28 are reopened, plus part/view authorization
wording. Exact locator, package-validation and projection mechanics in sections 3–5 remain
from the reviewed baseline except their replaced per-document-scope wording.
## 1. Gates and compatibility
## 1. Confirmed format policy and approval gates
W4b implements this contract only after independent W4 APPROVE and Sage's
accepted implementation brief, S0 and its other parent gates. A release must
ship extractor, resolver, authorization and sharing together; no partial
agent read rollout. Missing server readiness refuses every O2 read, resolve,
share and flag. A human setting, pin or request field cannot assert readiness.
W5 owns flags, not chapter writes. No agent execute, edit, replace, save,
tracked-change or comment-writing operation is introduced.
Q235–Q237 P1–P6 and mos-1372 D1/D2 settle the design basis: canon and playground story
files become DOCX, retaining universe/book and source scope. Type-folder notes, agent files,
instructions, PRDs, character sheets, summaries and supporting documentation stay Markdown.
An unmarked-folder story is eligible; the human selects one story file at a time. New
stories start directly as DOCX in the human-selected universe/book or playground, without a
Markdown precursor, under the same production-opening gate as import.
Q36 / Vikunja 562 is OPEN. Production Markdown adoption remains closed under
mos-1358, independently of whether this design is approved. If Jason approves
superseded notices, a separate C2 reader/reply row must implement and pass
them before adoption; otherwise Mos records Jason's disposition before any
adoption brief proceeds. This document neither implements nor decides notices.
The October 27 release stays Markdown. Production import/new-story opening waits for W4b,
W7, the post-10-27 release, the approved Case 2 PRD/brief amendment, Hermes retirement
(#423 step 5) and configured off-host durability. The first real use on Melanie's books
also waits for Jason's scoped go. All starts are human-only; `library.convert` has no role
grant or agent request path and stays a hard wait unless an approved PRD changes it.
Nothing in this document authorizes conversion, new private story creation or deployment.
Q234's exact frame CSP, mos-1363 and S0 M1–M14 remain unchanged implementation gates.
Existing C2 Markdown, its line-cite schema, library grants and supporting-doc
writer remain unchanged. An authorized old Markdown original is historical
source, never the current DOCX or a permission to its later edits. No DOCX
paragraph is called a line. Following an adopted source from a C2 read,
indexing it as Markdown, changing C2 replies or a synchronized Markdown
projection requires a separately approved Case 2 amendment through Mos/Jason.
W4b must test old C2 fixtures unchanged alongside the new discriminated DOCX
contract. It may migrate only new O2 records, with a named schema edge Sage
allocates; it must not reinterpret existing line citations in place.
mos-1372 D9 replaces the prior W4 O2-only-conversation, per-document-grant and per-send-share
requirements for Case 2 lookups. Agents read converted stories through the normal Case 2
lookups in any thread or room under the same live project/universe/source-scope checks as
Markdown canon/playground. They can then use existing supporting-Markdown write tools under
Q223. The agent never writes a story/DOCX/archive file. D9 is marked as a choice for Jason
in PRD 1.17 approval; it is design text, not an already approved role/policy expansion.
No code may rely on that changed requirement before the approved PRD and independent review.
## 2. Trust and document identity
Human import commits the verified DOCX to a private staging ref first and obtains its
off-host push acknowledgment. It then creates/pushes the source archive move+mark through
new human-only bus plumbing on the C2-02/C2-03b writer, not two single-path saves. Finally
one host publication protocol advances writing-published and the catalogue under the
publication/read lock. No staged ID is reader-visible before catalogue CAS. Before that
switch, agents read the source at its pinned commit; sync cannot advance it early. After
it, only the current DOCX is a chapter. Pre-push failure leaves source Markdown unmoved;
after archive push, failure is a visible resumable pending operation, never an assertion
that nothing moved, a rollback push or Markdown revival. Old bytes/history remain.
The host reader alone opens `<dataRoot>/writing/<business>/<project>/`.
Business, project and document are opaque IDs resolved through the host's
owned manifest, never request paths. Each document has a stable ID and one
canonical DOCX path; a rename does not change its identity. A copy/new import
gets a new document ID and no inherited grant, share, pin or cache authority.
Origin metadata is evidence only. Copying a source does not launder access.
The archive is in the human-set source-repository folder, default archive/converted, with
converted date, source commit/blob/path and replacement ID in the operation record. It is
read-only in the Console. Explicit `archived` scope returns only requested authorized
records, always marked and non-canon; default/current queries never opt into it. An old
source lookup returns a typed conversion record and routes an authorized DOCX read, never
stale Markdown or fake DOCX lines. Historical line cites still verify at commit/blob and
show converted/historical state; archive access alone is not current story authorization.
A read snapshots one reachable committed manifest and its DOCX blob from the
same commit. It never combines HEAD's manifest with another commit's DOCX.
The reader accepts only server-resolved commits reachable from that document's
published history; arbitrary git objects and another document's same blob
are not selectable. Historical access also requires the document's current
live authorization. Removed history, missing blobs or missing extractor
revisions refuse; there is no worktree, export or engine-memory fallback.
The commit/blob field is a locator, not an authority token.
Every conversion runs the pinned extractor before archiving and refuses unsupported output.
An independently reviewed importer preserves every wiki token and front-matter key; metadata
moves to the canonical manifest (type/name/appears_in/relationships feed the index). Links
resolve across Markdown/DOCX through owned aliases, not host paths. A lost link fails the
fidelity report. Plain links/front-matter values resolve or raise R4; unresolved chapter
embeds must not take the old silent attachment-drop path. Images/.canvas/.base remain attachments.
The project's sole publication ref is `refs/heads/writing-published`; only
the human writer advances it, atomically and without force updates. Each
commit contains the book manifest. For a document, its publication commits
are the first-parent commits on that ref that add or change its DOCX blob or
its canonical manifest entry (including deletion/reinstatement). The first
commit is a publication; a sibling-only save is not. `currentCommit` is the
latest such commit for this document at the captured ref tip. Version latest
selects that commit; a locator commit must be one of those publication commits,
not an arbitrary intervening project commit. Deleted document entries refuse
current reads, including history, until explicitly reinstated and granted.
Published history is exactly that document's reachable publication sequence.
A sibling save does not change its currentCommit, stale flag or cursor.
Staleness is still exact commit inequality within that document, even if a
human deliberately saves an earlier byte-identical blob as a new publication.
## 2. Document identity, order and freshness
The writer's successful commit publishes atomically before Saved. A latest
lookup begun after that receipt resolves that published commit, not an old
cache entry. A lookup already in progress remains internally on its captured
commit. Return the captured version explicitly; never label it latest after
observing a newer publish. Recheck live grants immediately before returning
content, not just before asynchronous extraction.
Host code alone opens `<dataRoot>/writing/<business>/<project>/`. Opaque document IDs resolve
through the canonical manifest, never caller paths. Conversion preserves book/chapter identity
and scope, binding the original path/name as an alias to the DOCX ID. New stories get a fresh
ID and the human-selected universe/book or playground. No format-based canon promotion.
The map tells authorized agents format, source scope, owned location and current version;
archived entries are available only in explicit archived requests. No host paths/credentials.
A lookup snapshots the reachable committed manifest and DOCX blob from one publication.
No worktree, draft, archive or engine-memory fallback. `refs/heads/writing-published` is the
canonical served ref. A document's publications are the first-parent commits adding or
changing its blob/manifest entry; sibling-only saves do not change its currentCommit.
History locators name only such reachable publications, with current project/universe checks.
Deleted entries refuse until explicitly reinstated by an authorized human. New local saves
publish before acknowledgment. A lookup starting after an acknowledged save returns that
save or a later one and names it. A lookup already in flight stays on its captured version;
never label an observed older snapshot latest. Recheck live access before emitting text.
D10's ordered chapter-ID manifest drives manuscript paging and next_chapter after conversion,
replacing row216's initial path-order rule (RULINGS.md:32–34). Before the first conversion,
seed the order from that existing rule; replace each source entry with its DOCX identity in
place. A human reorder is a manifest commit. next_chapter never names an archive. Pending
unconverted files retain their legacy identity/scope until their individual publication
switch; no file has two working formats and new stories start as DOCX. Book-null notes
stay out of manuscript; hybrid exclusions and source-universe boundaries still apply.
## 3. Exact wire schema, version 1
@@ -81,7 +94,7 @@ commits/blobs are lowercase 40-hex Git object IDs in the repository's current
SHA-1 object format. A future object format requires a new schema revision.
Digest values are lowercase 64-hex SHA-256. Integers are safe nonnegative
JSON integers. No floats, coercion, duplicate JSON keys or implicit defaults
except the explicitly listed request defaults below. These are proposed O2-only records.
except the explicitly listed request defaults below. These are proposed DOCX records for the Case 2 lookup/citation union.
A locator is exactly:
@@ -214,10 +227,10 @@ extractor supports them; never guess equivalent namespaces.
| Class | Exact part names | Story selector | Agent exposure |
| --- | --- | --- | --- |
| body | word/document.xml | main | final by default, explicit grant |
| footnote | word/footnotes.xml | w:footnote/@w:id | own story plus owning document grant |
| endnote | word/endnotes.xml | w:endnote/@w:id | own story plus owning document grant |
| comment | word/comments.xml | w:comment/@w:id | only explicit comment scope |
| body | word/document.xml | main | final by default; existing project/universe access |
| footnote | word/footnotes.xml | w:footnote/@w:id | own story; same project/universe access as its document |
| endnote | word/endnotes.xml | w:endnote/@w:id | own story; same project/universe access as its document |
| comment | word/comments.xml | w:comment/@w:id | explicit part request; same project/universe access |
| layout | word/headerN.xml, word/footerN.xml (N positive decimal) | none | denied in v1 |
| supporting | separately declared non-story document | separate identity | C2 tools/scopes only; no DOCX scope inheritance |
| opaque | media, styles, numbering, theme, fonts, custom XML, embedded objects | none | no raw bytes through O2 reader |
@@ -288,8 +301,9 @@ kind paragraph-mark-insert or paragraph-mark-delete with its revision ID;
it never becomes a whole-paragraph deletion span. A mark-only insertion's
text stays in original; a mark-only deletion's text stays in final. Inline
run revisions remain independent. No view edits or accepts/rejects changes.
Original/markup and comments require separate scopes, so final body access
never reveals hidden deletion/comment text. Revision author/date metadata
Original/markup and comments are explicit view/part requests under the same project/universe
authorization; no per-document grant is added. A default final-body read never silently
includes deleted revisions or comment stories. Revision author/date metadata
is omitted from agent responses in v1.
Closed story/run leaf table (all names in w namespace unless stated):
@@ -372,7 +386,8 @@ plain text, and tokens cannot cross paragraph/view boundaries.
## 6. Reading APIs, verification, paging and staleness
These are proposed typed tool schemas over the existing runner socket, not
native WordCraft MCP. Caller/session/business come from the bound broker.
native WordCraft MCP. The same reader backs Case 2 lookups in any authorized thread or
room under mos-1372 D9. Caller/session/business come from the bound broker.
Agents cannot override them using object fields.
- `editor_read_committed({project,document,version,textView,part,cursor,limit})`:
@@ -412,14 +427,14 @@ without progress.
Cursor is a host-authenticated opaque token binding principal, session,
project/document, commit/blob, extractor, view, part, next ordinal, the complete
authority revision vector defined in section 7 and expiration (5 minutes). It carries no plaintext content.
project/universe authorization and format-publication revision vector defined in section 7 and expiration (5 minutes). It carries no plaintext content.
Tampering, reuse by another session, wrong request parameters or a revoked
scope refuses. A latest cursor continues its captured snapshot with stale
metadata if a save intervenes; starting again with null cursor returns the
new publication. Cursor is not a capability and grants are rechecked every
page. Repeated ordinal ranges are stable within one committed projection.
Resolver steps: authorize the current principal tuple, live scopes and exact/historical share (there is no separate undefined historical-view scope); load
Resolver steps: authorize the current principal, live project/universe/source scope and publication membership; load
that commit's manifest/blob; validate package; run the exact pinned extractor;
select exact part/story/ordinal and mandatory XML-ID value; select code-point range;
require literal quote equality and SHA-256(UTF-8 quote) equality. Require both
@@ -431,14 +446,13 @@ an explicit old-version label. If current grant no longer allows it, even
old quote resolution refuses and the UI hides cached content.
Errors: `editor-context-unavailable` for missing readiness; `editor-access-refused`
for absent ownership/grant/share (same response for nonexistent or foreign IDs);
for absent live project/universe authorization (same response for nonexistent or foreign IDs);
`editor-document-unsupported` for parser limits or unsupported features after
authorization; `editor-version-unavailable` for authorized missing history or
extractor; `editor-citation-invalid` for locator/quote mismatch;
`editor-cursor-invalid` for invalid paging; `editor-range-too-large` for size.
The exact refusal envelope is `{ok:false,error:{code,message}}`; code is one
of those enum strings or editor-policy-conflict/editor-sharing-unsupported
(both 409, defined in section 7), and message is fixed server text for that code (no user
of those enum strings or editor-policy-conflict (409, stale catalogue/settings revision), and message is fixed server text for that code (no user
input). Both missing/foreign and ungranted IDs use the same code/message/status
403. Syntax errors before identity lookup use editor-citation-invalid, 400;
context unavailable is 503, cursor-invalid 400, version unavailable 409,
@@ -446,417 +460,131 @@ unsupported/range-too-large 422. Successful tools return `{ok:true,result}`
where result is the defined response; errors never include result/partial
rows. Return no source text, XML, path or untrusted parser error in refusals.
## 7. Authority data, dedicated sessions and sharing
### 7.1 Scope and principal records
### 6.1 Case 2 query and citation adapters
O2 context is permitted only in newly created **O2-dedicated conversations
and execution sessions**. Existing C2 threads/sessions cannot be converted,
attached to O2 context or resumed/forked into O2 mode; new IDs and a fresh
empty context are mandatory. There is no mixed-thread turn. The UI offers
"Open dedicated writing conversation", never silently changes an existing
chat's permissions. Existing C2-02 supporting-doc writes and C2-06 replies
retain their semantics. The narrowing below belongs only to this explicitly
opted-in O2 mode, closing review M1 without a Case 2 amendment. It changes
neither the Case 2 schema nor the meaning of its replies/line citations.
Keep `library_find`, `library_get`, `library_search`, `library_manuscript` and the universe
lookup. Host adapters join Markdown metadata/read results and the committed DOCX extractor
into one typed, scope-filtered index; only current identities enter normal lookups. A request
on a converted alias returns its conversion record and the authorized DOCX result. Every
answer names format, store/owned ID and captured commit. No loss of chapter search/manuscript
or supporting-doc workflow is accepted. Same project/universe/source-scope rules apply in
all threads/rooms; archive/draft scope never becomes canon because its format is DOCX.
Authorization principal is a tuple, not a seat's display name:
`{business,human,agentInstance,conversation,executionSession}`. human is the
stable authenticated identity that owns the O2 conversation; agentInstance
and executionSession come from its bound broker run. The request cannot
supply/replace them. All human readers and all agent lookups require live
R5 project/universe access plus the O2 grant below; role files grant the
operation only. The business owner is not an implicit reader of someone
else's private conversation. A viewer can read only their authorized human
view, never write a share/settings change or invoke an agent by sharing.
An owner/collaborator with current project write access may share within
their own readable O2 scope. Agent scope and sharing human scope intersect;
a human cannot share a part or view they lack.
DOCX pages use paragraph locators and opaque cursors, not invented line equivalents.
Retain section 6's 64-KiB/1..100 paragraph bounds (default20); human reply limits still apply.
A manuscript cursor binds ordered-manifest commit, the selected document publication vector,
principal/request/scope revisions and expiry. Resume only that snapshot with current access;
changed/foreign cursor refuses. next_chapter uses the next visible current manifest ID,
never an archive or a guessed sort of DOCX names. Index/cache keys include local publication
and extractor revisions; no minute-old Markdown refresh window is copied to DOCX.
O2 scopes live in the new append-only bus table writing_access_events, not
access.json or role-file wildcard changes. Proposed human-only verb
`editor.access.set({project,document,subject,scope,expectedRevision})` takes
subject exactly `{kind:"human"|"agent",id}`; agent id is the business's
bus agent **instance ID**, equal to principal.agentInstance, never a role
definition ID or a seat display name. scope is exactly
`{enabled,parts,views,history,mayShare}`. parts is a sorted unique nonempty
subset of body/footnote/endnote/comment (all stories within the selected
class); views is a sorted unique nonempty subset final/original/markup;
history and mayShare are booleans. mayShare must be false for agent subjects.
enabled=false revokes all access; even its otherwise populated scope cannot
authorize. No wildcards or implicit note/comment inheritance. A comment
requires parts including comment and the requested view. history=true permits
older publications subject to a matching share; history=false permits only
the current publication. The document is always explicit.
C2-06's checked cite union preserves legacy `{path,commit,lines,quote,as?}` for Markdown and
adds `{kind:"docx",locator,as?}` where locator is section3's exact range/selection. Never infer
a cite kind from a .docx suffix or parse DOCX as lines. The host checks project/universe,
source canon/playground/archive status, fetched provenance, quote and commit before message
storage. requireCitation and as:canon apply to both; historical converted/archived sources
cannot assert current canon. The host reads Mosaic-Agent trailers from the correct source
repository or writing-store publication, never trusts an agent author field. Console and
the shared reply renderer show format, locator/lines, version, staleness and converted state.
C2-18 Discord remains a separate ruled delivery task, not enabled by a shared renderer.
Only the existing business-owner human proof may write these policy grants,
through the existing trusted human CLI or authenticated write-enabled Console
transport, with the existing origin/session/proof checks. No new proof file,
caller or exemption. Not tester, agent, viewer or collaborator policy writes.
Sharing and scope are different: an authorized collaborator may send context
but cannot change grants. expectedRevision is the latest sequence for that
business/project/document/subject, zero when none. Compare, append and audit
are atomic; conflict returns editor-policy-conflict (409, fixed message).
Receipt is `{ok:true,result:{id,revision,previousRevision,scopeDigest}}`, where
revision is the appended seq, id is host-generated, and scopeDigest is SHA-256
of canonical scope JSON using section 3 serialization. Missing grant denies.
## 7. Existing Case 2 authority, remote durability and restore
Each access decision records an authority vector exactly
`{restoreEpoch,humanAccess,agentAccess,projectAccess,threadAccess,sharingPolicy,share}`.
humanAccess and agentAccess are the respective latest writing_access_events
seq values; projectAccess is the existing R5 access revision; threadAccess
is the latest O2 conversation membership-event seq; sharingPolicy is the
latest writing_policy_events seq; share is the exact writing_shares seq.
restoreEpoch is the independent restore-fence epoch in section 7.5.
Missing inputs deny rather than zero-default except initial policy explicitly
materialized by the human writer as the default. A cursor binds every value;
any change invalidates it. Each new lookup reevaluates rights, including a
revocation between asynchronous extraction and response emission.
### 7.1 Access and supporting writes (reopened M1)
### 7.2 One human per context; settings and chip requests
Reuse existing bound business/project/universe access, live holder/role and source scope.
No private O2 conversation ID, per-document access table or send/share token gates a Case 2
lookup. Any existing authorized thread/room may use the checked reader and normal library
tools. No arbitrary native WordCraft MCP, execute, open/save or raw filesystem/git tool is
added. The host store is never mounted in a worker. Rendered/extracted content travels over
the ordinary authorized Case 2 tool/message path and existing transcript/run-record policy;
the withdrawn private-only transcript/output rule cannot forbid that documented workflow.
Grant loss blocks later lookups through existing checks; do not promise erasure of earlier
delivered text or rewrite immutable run evidence. This is the D9 choice for PRD approval.
An O2 conversation has exactly one owner human and one agent instance. No
second human joins it in v1: a thread-share attempt refuses
editor-sharing-unsupported (409), rather than borrowing the sender's grants
for someone else. A second human starts a separate O2 conversation, supplies
their own grant/share and gets no previous content. This is a new O2 mode,
not a restriction on existing C2 sharing. Human and agent must still have
matching business/project/universe rights. Read-only previews cannot send.
The existing `library_doc_write`/`library_doc_delete` still require library.write plus the
live supporting-repo Gitea grant and supportingPath before git. They operate only on human-
marked supporting Markdown, with the existing size/base-CAS/seat-author/push/notice/revert
rules. An agent can read a DOCX story and update its character sheet in the same authorized
thread. There is no agent write to DOCX, original story, archive or conversion manifest.
The archive prefix wins over supporting globs; conflicts refuse mapping. Format/status/type
notes in the six creative contracts replace stale 'every item cites lines' wording with
free chat plus optional checked line/DOCX citations under requireCitation. No per-document
export grant or O2-only session is a hidden prerequisite.
Human-only `editor.sharing.set({conversation,mode,expectedRevision})` is
available only to that authenticated owner with current write access over
the same trusted write transports. mode is off/visible-chip-per-send/
conversation-current. It atomically appends writing_policy_events and returns
`{ok:true,result:{id,revision,previousRevision,mode}}`. Default is the explicit
visible-chip-per-send policy on new conversation creation. Selecting off
immediately invalidates **all** prior shares, reads, resolves and cursors for
that conversation, not only new sends. Selecting another mode later does
not revive shares from the earlier policy revision. No pin changes a mode.
The cursor authority revision is the server's current project grant, universe/mapping/scope
revision and format-publication generation for the bound caller/session. It contains no
sharing human or writing_access_events/writing_shares sequence. Recheck before every page,
resolution, index hit and message insertion. Scope revocation and format changes invalidate
cached authorization; metadata for missing/foreign IDs uses the same refusal as before.
In visible-chip-per-send the human sends exactly
`{conversation,message,expectedPolicyRevision,chip}` via the human send path.
message is human-authored free-chat text; W4b's bounded human API handles it,
never an agent-supplied impersonation. chip is null or one of:
### 7.2 Selection convenience and ordinary messages
- `{kind:"range",document,baseCommit,selection}` where selection is one
docx-range/docx-selection locator, with its business/project/document/
commit equal to the server-owned conversation/document and baseCommit.
- `{kind:"document",document,baseCommit,parts,views,history}` selecting a
whole-document **exact publication**, with explicit part/view lists and
history boolean constrained by both grants.
Keep Q233's human-only setting, default a visible removable chip on each send. A chip may
attach only a verified committed locator/version, never unsaved selection text. It helps the
writer point at a passage; it neither grants nor limits the agent's ordinary project/universe
lookup rights and does not freeze a later latest lookup to that older commit. The sent cite
remains immutable historical evidence. Multi-human/thread/room behavior follows existing Case 2
permission checks, not a new one-human conversation restriction. Saved-context/pin records
remain on the bus through their reviewed schema, with no share-token authority layer.
Both chips require a server-issued human editor selection token as an
additional required `selectionToken` key. It binds authenticated login,
conversation, document, baseCommit, saved editor model revision, and either
exact locator or whole-document scope digest, expires in 2 minutes and is
single-use. The human editor adapter creates it only after exact mapping to
the committed projection. The request supplies no draft text or engine path
for mapping; mismatched/edited selection fails locally and server-side.
A stale base or document change before send refuses, requiring a fresh
visible chip; an old citation remains resolvable through its already-sent
share while authorized. Null chip sends no editor context. Before readiness,
the chip says unavailable and sends no document ID, title, path or selection.
### 7.3 Host-only off-host remote (replaces the ban)
A range share permits only editor_resolve_citation for that exact locator
(or a range wholly contained within it with newly verified quote/digest).
It always refuses editor_read_committed, since rows would expose surrounding
text. A document exact share allows paging only its named publication,
parts/views; it never grants future saves. No implicit body-to-note/comment
or final-to-markup expansion occurs.
`editor.remoteURL` is a human-only setting, default unset. No same-host bare/filesystem/loopback
remote is the default or qualifies as off-host durability. The human selects an off-stack-host
private destination (for example private Gitea) and separately configures host-only auth.
Only the host process pushes. No seat token can list/fetch/clone/fork/push the writing repo,
no seat gets its credential, and no raw Gitea grant or store mount is created. Validate the
owned remote, TLS/host policy and off-host storage identity; reject URL userinfo, unexpected
remotes/object alternates and non-fast-forward updates. No silent fallback or force push.
conversation-current is an explicit human policy with a persistent visible
indicator and a required whole-document chip. The same chip fields bind
baseCommit and scope, but the share record has versionMode=latest: baseCommit
is the acknowledged publication at issuance, not a future-version ceiling.
The next authorized latest lookup after Saved follows currentCommit within
the granted part/view/history bounds. This exception to exact-version sharing
exists only in this explicit mode. Range chips are refused in this mode,
not silently upgraded. Pins and earlier exact shares never become latest.
Every writing-store commit pushes there. Agents read the local publication immediately;
receipts/UI distinguish local commit and remote pending/failed/synced. Missing remote blocks
production opening. Before source archive push, the staged DOCX must have remote acknowledgment.
A failed later push keeps local commits, queues bounded host retry/readback and shows failure;
it never rolls back the local story. Source archives push through their separate human
library writer. The configured retry deadline is human-only (default30s, 1s..300s).
Remote/provisioning changes require the normal owner authority, never this design execution.
The remote exception is for story git history/manifest; generic stack copies/releases are
not story backups and contain no host credentials. Normal Case 2 output/notes follow the
existing project's approved storage/sharing policy; the former blanket no-backup/no-remote
rule is withdrawn. PRD 1.17 explicitly presents this D7/C4 change to Jason.
Latest shares expire on explicit policy revocation, owner grant loss, O2
conversation close or **execution-session end/clear**. This means the new
O2 execution session, not the Console login, bus connection or a C2 thread
session. Relaunch/clear requires a fresh chip; a dropped socket/login does
not silently widen or prolong one. Exact shares may remain verifiable across
execution sessions through their same human conversation while live policy,
history permission and grants hold, but never supply replay context to a
new agent session without a new share. Their scope is citation verification
only after the issuing execution session ends. No cross-human context reuse.
### 7.4 Conversion events, rollback --restore and irreversible recovery
### 7.3 Stored share schema
W7 owns `writing_conversion_events`: a STRICT append-only table with seq INTEGER PRIMARY KEY,
at TEXT, business/project/document/operation_id/stage TEXT, record_ref/record_digest TEXT;
All fields except seq are NOT NULL; operation_id+stage is unique, no update/delete. The private host journal/record contains source
commit/blob/path, archive path/commit, DOCX commit and conversion date; the row is metadata
and never prose/credentials. Records are flushed before their bus references. Table rows
join stack-release COUNTED and T46 exactly once, so post-backup conversion events contribute
to --events and the existing restore approval/counting behavior. No new private conversation,
per-document grant or per-send share tables are created for D9.
The immutable writing_shares payload is exactly
`{version,id,restoreEpoch,business,project,document,conversation,executionSession,message,
human,agentInstance,mode,versionMode,baseCommit,blob,extractor,scope,selection,
authority,policyRevision,issuedAt}`. version=1; IDs use section 3 grammar;
mode is the policy at issuance; versionMode is exact/latest; baseCommit/blob
are the acknowledged snapshot and extractor is its exact pinned revision.
scope is `{parts,views,history}` as above; selection is the verified locator
for a range, otherwise null. authority is the complete revision vector at
issuance; its share field equals this row's seq (assigned atomically).
restoreEpoch must equal authority.restoreEpoch and the live independent
fence. issuedAt is canonical UTC millisecond ISO timestamp. policyRevision equals
authority.sharingPolicy. message names a new writing_messages row, not a C2
message. A unique `(conversation,message,document)` constraint and operation
idempotency key prevent duplicate grants on retry. No user-provided human,
agentInstance, authority, issuedAt, blob or share id is accepted.
A scope decrease/revocation denies even historical shares; an unrelated new
policy revision also invalidates prior policy-bound shares. A human who
needs them again explicitly reshares. Missing session/share is no context.
### 7.4 Dedicated storage and every output surface
Every turn receiving O2 context runs with the new O2-only allowlist of
read/resolve/flag plus its dedicated conversation reply. No generic file,
shell, git, supporting-doc writer or arbitrary outgoing tool is available.
This narrows only new O2 sessions, never C2 turns. All agent output from such
a turn is conservatively O2-derived; it stays in the dedicated conversation
with the union of the document provenance/scopes. No semantic quote detector
or agent self-labelling supplies this boundary. Human text and titles in that
conversation use the same dedicated storage, so a human pasting an O2 passage
cannot accidentally route it into an old renderer. O2 replies never enter
C2-06 processing. An O2 flag is metadata stored by W5, not a story edit.
| Existing surface | Required O2 behavior |
| --- | --- |
| threads/<business>/<thread>/session and claude-projects | no O2 content, prompt, model transcript, title or derived reply |
| threads/<business>/<thread>/sessions/<session>/session and claude-projects | same; O2 mode never binds these native stores |
| session-workspaces/<business>/<instance>/<session> | no O2 text or exports; no store mount |
| launches/<business>/<run> including bundle, turns, runner.log and session | opaque IDs/status only, never O2 prompts/output; host transient O2 adapter instead |
| bus events, messages, message_threads and existing sessions rows | no O2 text/titles/quotes; only fixed readiness/status audit with opaque IDs if needed |
| Console legacy thread view | no O2 rows; dedicated writing view reauthorizes every read |
| CLI thread, talk, follow and raw-session tools | no O2 content; unsupported mode refuses, not generic rendering |
| Discord journal.mjs/context.mjs and control-board Discord delivery | no O2 conversation subscriptions/context/output; fixed unavailable notice only |
| <dataRoot>/runs write-once evidence | never O2 text, prompts, titles, excerpts, stdout/stderr or derived output; only opaque run status |
| bus-host/sessions.json, launch logs and activity projections | IDs/liveness only, no document or conversation title |
Host-only `<dataRoot>/writing-context/` (0700 owner, no generic mount/remote)
holds the immutable dedicated message/share objects defined below, transient
O2 adapter material and any unavoidable encrypted native transcript. Object
files are regular 0600 owner files, no symlink/hardlink aliases. This is an
O2-classified content store; it is never included in the bus release backup,
post-attempt store, stack-copy, rehearse input or generic backup. Revocation
changes permission at read time, never rewrites a write-once run record. It is keyed to O2 session, not an alias/symlink into seat stores.
Release readiness requires an independently tested adapter that prevents
plaintext native persistence/logging outside this boundary, including crash
and timeout paths. If either Pi or Claude cannot meet it, that harness's O2
tools stay off; do not expose the session then attempt post-hoc redaction of
write-once run records. Readiness is per adapter. No O2 text enters immutable
legacy evidence, even briefly. Stored dedicated replies are authorized afresh
at read time; live grant loss hides cached UI content and prevents prompt
replay/fork. Revocation cannot erase already delivered knowledge and the UI
must not promise that it can.
O2-derived exports, extraction indexes, caches, previews, source copies and
git history retain document classification outside generic roots and C2
library mappings. O2 repositories have **no git remotes**. No push, mirror,
fork, plaintext backup or remote replication of an O2 repository is allowed,
including through human credentials; no seat Gitea account can discover,
list, fork or clone one. Backup transport is out of scope and disabled, not
an implied exception. Stack-copy/release copies source code and extractor
artifacts only, never writing/, writing-context/, writing-control/, O2 content
objects, exports or repository object directories. The separate release bus
backup retains metadata rows only, under the private-store rules below;
it is not a source copy or permission to read context. No raw O2 Gitea grant may be minted.
Validate absence of remotes at store creation, before save/read and activation;
a new remote or object alternate disables O2, rather than trusting a read-only
mount. Symlinks, hardlinks, alternate worktrees and parent mounts must not
expose it to generic tools. Native engine APIs remain human-only.
### 7.5 Named migration and rollback contract
W4b adds the following new STRICT append-only bus tables (seq INTEGER primary
key, at TEXT canonical timestamp, id TEXT unique, all other fields NOT NULL
unless stated). Each row also has restore_epoch TEXT NOT NULL, equal to the
independent current fence epoch at write time. Every update/delete/replace
refuses; latest seq **within that epoch** is state.
JSON columns contain the exact canonical schema named above, not arbitrary
unvalidated JSON. No existing C2 messages/events/sessions column changes.
| Table | Additional columns and key |
| --- | --- |
| writing_access_events | business TEXT, project TEXT, document TEXT, subject_kind TEXT (human/agent), subject_id TEXT, by_human TEXT, previous_revision INTEGER, scope_json TEXT, operation_id TEXT UNIQUE; latest per document/subject |
| writing_conversations | business TEXT, project TEXT, owner_human TEXT, agent_instance TEXT; one creation row per id, private O2 identity only |
| writing_conversation_events | conversation TEXT FK, op TEXT (close), by_human TEXT; latest membership/close revision, creation seq is initial threadAccess |
| writing_execution_sessions | conversation TEXT FK, execution_session TEXT UNIQUE, adapter_revision TEXT; no native transcript path |
| writing_session_events | execution_session TEXT FK, op TEXT (end/clear), by_principal TEXT; terminal event invalidates latest shares |
| writing_policy_events | conversation TEXT FK, mode TEXT, by_human TEXT, previous_revision INTEGER, operation_id TEXT UNIQUE |
| writing_messages | conversation TEXT FK, execution_session TEXT FK nullable for human-only messages, from_kind TEXT (human/agent), from_id TEXT, content_ref TEXT, content_digest TEXT, operation_id TEXT UNIQUE |
| writing_shares | conversation TEXT FK, message TEXT FK, document TEXT, payload_ref TEXT, payload_digest TEXT, operation_id TEXT UNIQUE; UNIQUE(conversation,message,document) |
Each table also has business/project columns if not otherwise listed; FK and
application checks require agreement, not just ID existence. The canonical message content object is exactly `{version:1,business,project,row,nonce,body,provenance}`:
row is the owning writing_messages row ID; nonce is fresh cryptographically
random 32-byte lowercase hex, stored only inside the private object, not its
metadata backup. body is
the complete human/agent message string; provenance is an array of `{document,share,commit,blob,extractor}`,
sorted by document/share; every entry requires live scopes. The share content
object is exactly `{version:1,business,project,row,nonce,payload}`, where row
is its writing_shares row ID, nonce has the same fresh/private rule, and
payload is the full section 7.3 record including any quoted selection.
Neither message bodies nor share payloads/quotes enter bus.sqlite, WAL,
events, SQL error logs or backup manifests. Human messages with no O2
attachment have provenance=[] but use the same private content store.
content_ref/payload_ref are opaque 64-hex object names, not paths; their
corresponding digest equals SHA-256 of the exact canonical UTF-8 object.
Objects reside at writing-context/objects/<business>/<row>/<digest>.json;
row/business/project must match both reference metadata and the authorized
caller. No cross-business or cross-row deduplication; retention is per owning
row and cannot delete another business's object. The private random nonce
prevents a metadata digest from confirming a guessed short body. No remote or
release-backup copy. Write/flush the immutable object before committing its
bus reference. A failed transaction leaves only an inaccessible orphan,
never a visible partial message/share; a missing or mismatched object refuses.
Object reads always require current authority, not knowledge of a digest.
No
existing renderer queries these new tables; generic bus recordEvent and
recordTask cannot write their rows. Access/policy/shares use only the trusted
handlers and atomic transactions. Grant seq values are table-specific, with
kind-qualified revision vector fields; never compare unrelated seq counters.
Sage allocates the schema edge; migration creates these empty tables and
triggers, with no backfill/conversion of C2 citations, messages or raw sessions.
Production adoption stays refused. The writer registers new documents without
creating grants; the owner explicitly sets both human/agent document scopes.
O2 session creation and its default policy are one atomic operation behind
owner write proof; receipt `{id,executionSession,policyRevision}` contains no
content. No legacy thread/session can be attached by guessed ID.
Old release rollback must not render O2 data. The new store schema version
normally makes old binaries refuse opening it, but security does not depend
on that: even a restored pre-W4b database reader can see only its unchanged
legacy tables with no O2 content. O2 messages/share/provenance remain in new
tables, never a tagged body in existing messages. Activating a pre-W4b copy
disables O2 context, never converts dedicated conversations to C2 or exports
transcripts for compatibility. O2 repositories/stores remain outside copies.
A rollback probe uses recognizable secret fixture text in O2-only content objects,
then invokes old Console/CLI/Discord/raw-session renderers: none may emit it,
its title, quote or prompt. No live rollback is part of acceptance testing.
#### Release backup, restore counting and independent revocation fence
The existing release behavior must be explicitly accounted for: at this base
`scripts/stack-release.sh:474-493` copies bus.sqlite into
`<dataRoot>/backups/stack/<ACT>/bus/`; restore_store at :498ff moves current
bus/ to `<dataRoot>/bus.post-attempt-<stamp><suffix>/`. rehearse reads backup
stores, and release never prunes them. These paths remain **O2-classified
metadata stores** even though content/quotes are outside SQLite. Directories
are 0700 owner and files (including SQLite/WAL/SHM if present) 0600 owner,
with ownership, symlink/hardlink and generic mount checks. No remote, mirror,
seat token access or generic export. rehearse may read metadata for validation
only and cannot load content objects, emit quotes or restore readiness.
Release does not silently delete evidence. A separate explicit human/ops
retention action may prune a stopped backup/post-attempt tree only after
checking it is not the selected recovery set or needed restore evidence,
recording its IDs/digests and a prune receipt. No automatic age-based prune,
no editing of retained metadata, no pruning runs/ under this policy. Until
that separately reviewed action exists, keep these private local metadata
trees indefinitely; it grants no content access. writing-context objects
have a distinct reviewed retention process and are never backed up by release.
W4b owns the release guard changes, not a follow-up: all eight writing_*
tables above join COUNTED in scripts/stack-release.sh:420 and the T46 table
classification in packages/cli/tests/stack-release.test.mjs:913. They are
non-event writers, not classified as ignorable tables or double-counted as
generic events. Jason's existing exact --events N confirmation remains
required when the combined post-backup row count is nonzero. A restore plan
also compares writing row identities with the proposed source store; equal
aggregate counts cannot conceal a dropped revocation replaced by another row.
A schema with any unclassified writing_* table refuses restore readiness.
An **independent host-private fence** lives outside bus/ and every backup:
`<dataRoot>/writing-control/restore-fence.json`, directory 0700, regular file
0600, no links/mounts/remote/source-copy/rehearse access. Its exact schema is
`{version:1,epoch,restoreOperation,droppedRowsDigest,confirmedBusinesses}`.
epoch is a host-generated opaque ID; restoreOperation is null before the
first restore, otherwise a host-generated opaque operation ID; droppedRowsDigest
is SHA-256 of the canonical sorted dropped `(table,id)` list (empty allowed);
confirmedBusinesses is a sorted unique array of authenticated business IDs.
Only the trusted release controller and existing owner-proof handler write
it by durable atomic replacement; agents/testers/request paths cannot.
It is not a human-editable readiness setting: verified release readiness is
still required, and this file is an additional independent deny fence.
**First creation:** the compatible release controller durably creates the
fence as part of W4b's first schema migration, before committing those empty
tables or enabling any handler. It uses a fresh epoch, restoreOperation=null,
droppedRowsDigest=SHA-256 of the canonical UTF-8 empty JSON array `[]`, and
confirmedBusinesses=[]. Every initial writing row uses that epoch. A normal
never-restored store does **not** require editor.restore.confirm: the
confirmation gate applies only when restoreOperation is non-null. Initial
live grants/shares and verified artifact readiness are still mandatory.
Initialization is allowed only when the pre-migration schema has no writing_*
tables/history and there is no existing fence. If a crash leaves a durable
initial fence but the schema transaction uncommitted, migration resumes with
that same epoch, not another initial state. If writing tables/history exist
and the fence is missing/malformed, fail closed; never repair by creating a
new initial fence. Creating an initial fence is not a way around a previous
restore or revocation. W4b acceptance exercises both migration crash orders.
Before **any** store swap/restore (not just one with nonzero counts), the
current compatible controller durably creates a fresh epoch, records dropped
writing rows and clears confirmedBusinesses. If fencing fails, restore
refuses before changing bus/. A crash anywhere keeps O2 denied. Old snapshot
rows have old restore_epoch; they cannot authorize even if their grant,
policy or share says enabled. Automatic failure recovery and rehearse-driven
restore use the same fence. Normal code rollback without store restore
keeps the fence and leaves O2 off on incompatible code. A pre-W4b controller
is not allowed to restore an O2-enabled data root: the active controller's
capability preflight refuses it; operations must use the compatible restore
controller even when the target runtime is old.
After restore, the verified W4b runtime remains unavailable for O2 in every
unconfirmed business. New human-only
`editor.restore.confirm({expectedEpoch})`, under the business owner's existing
trusted human proof, acknowledges that business in the independent fence
and returns `{ok:true,result:{business,epoch,confirmed:true}}`. It restores
**no old grant/share/policy/conversation**. Fresh current-epoch grants,
a fresh dedicated conversation/default policy and fresh human chips must
be created explicitly. Past rows may be inspected by an authorized recovery
operator as metadata, but never used as current authority or prompt replay.
Every read/resolve/cursor/share checks matching restore_epoch and, when
restoreOperation is non-null, that business's explicit confirmation; old object bytes remaining on disk are not capabilities.
Thus even owner confirmation cannot revive a previously revoked old share.
Missing/malformed fence state denies O2. This current-epoch file does not
claim to detect a privileged manual rollback of both fence and database;
the supported restore protocol never restores the fence from a backup and
always rotates it first. A same-uid operator bypassing that protocol is not
a boundary this file can prove. --events N approves a
store operation only and is never a substitute for this recovery protocol.
Acceptance seeds a secret in a content object and a share, takes a backup,
revokes access/sharing, then restores the backup with explicit row-loss
approval: the old share stays refused before and after owner confirmation;
a new grant/conversation/chip is needed. Sweep bus.sqlite, WAL/SHM, every
backups/stack and bus.post-attempt-* tree, rehearse output and release logs:
no secret/title/quote/body marker may appear. Only the explicitly named
private writing-context content object may contain it. Verify COUNTED/T46
covers each table and simulate failure before/after each fence/store step.
The XML/ZIP parser safety ceilings and absence-of-remote checks apply to
human store ingestion too where needed to prevent path escape and remote
replication; a projection-unsupported rich feature alone does not reject a
valid human save. Parser refusal codes have a fixed human-readable reason
category, never raw XML or a misleading empty context. Fields/hyperlinks/
text boxes may make real chapters agent-unreadable in v1; the UI explicitly
says this and never claims an empty successful projection.
Every extractor revision referenced by retained publication history or shares
is retained as a verified immutable host artifact. An activation missing one
refuses O2 readiness (no silent citation downgrade), including rollback to a
W4b-era release that drops a revision. A pre-W4b release can run with O2 wholly
off and cannot read those records. Retiring revisions requires a separately
reviewed retention/prune operation for all dependent O2 history/shares; not
an automatic stack-copy cleanup. Artifact caches are not authorization caches.
Three recovery cases are required: (1) crash after durable DOCX staging but before archive
push, (2) crash after archive push before publication, and (3) rollback --restore from a
pre-conversion bus backup while archive commits and O2 documents remain. The source is pinned
until final publication in cases1/2; after archive push state is visibly pending, never
'original untouched'. Case3 drops conversion rows only through the counted/approved restore
path. The durable host conversion journal/format registry is not restored from that older bus
backup. A compatible controller compares journal, both git histories, remote acknowledgment
and restored metadata before lookups/writes resume; it reconstructs missing metadata through
new recovery events without reviving Markdown or any old access grant. If reconciliation
cannot prove a single current identity/version, refuse that project's lookups and surface the
pending operation to the human. Do not replay chapter text into an old index or hide event loss.
Incompatible old binaries cannot activate converted projects through the supported controller;
raw privileged rollback tampering is outside the guarantee. Never rewrite git/run history.
Hermes retirement (#423 step5) is a production-opening prerequisite, with old story-write and
store-read access removed before any conversion/new-story operation. W7 checks its retirement
receipt and host capability preflight; no design seat retires services or accesses Melanie's
host. Independent tests use fixtures only. Preserve the existing stack grant/restore behavior;
the previous unimplemented W4 private-share restore-fence/table design is withdrawn with M1,
not backfilled into legacy rows. Conversion's durable registry is not an authorization token.
## 8. Acceptance matrix and release evidence
Each row needs a named fixture/test and a mutant or bypass probe in W4b's
packet. Independent fixtures construct OOXML directly and recompute Unicode
slices/digests outside the production extractor. Security checks fail closed;
no fixture-only readiness flag may exist in production APIs.
These are required future fixture gates, not implementation results.
| ID | Evidence required | Negative control / bypass |
| --- | --- | --- |
@@ -865,117 +593,49 @@ no fixture-only readiness flag may exist in production APIs.
| A03 | XML ID absent/duplicate and ordinal behavior | missing/mismatched paraId cannot silently choose another node |
| A04 | Emoji, combining characters, tabs, breaks and split runs | UTF-16 offsets, normalization and whitespace collapse fail |
| A05 | Literal split-run wiki forms retained | no path/URL fetch; malformed/cross-paragraph tokens stay text |
| A06 | Final/original/markup independently verified with moves/deletions | final-only grant cannot read deletion/comment; no document rewrite |
| A06 | Final/original/markup independently verified with moves/deletions | default final-body request cannot silently include deletion/comment stories; explicit part/view is separate, no document rewrite |
| A07 | Body/note/endnote/comment exact ownership | orphan/dangling/cross-document references and duplicate IDs refuse |
| A08 | Package allowlist, aliases, CRC, XML/ZIP limits | external rel, macro, traversal, symlink, entity, ZIP bomb and timeout refuse |
| A09 | Commit snapshot and publication ordering | next latest after Saved gets new commit; no worktree/draft fallback |
| A10 | Historical resolve stale label | old quote stays old; removed grant/history refuses without text |
| A11 | Pagination complete/deterministic and bounded | tampered/foreign/revoked cursor and oversized row refuse |
| A12 | Cross-user/business/project/universe denial | guessed IDs, same blob elsewhere and copied document grant nothing |
| A13 | Visible range chip exact boundaries and default off before gate | no selection/title/path metadata before readiness; draft mapping refused |
| A14 | Every lookup intersects live grant and share | pin, old send, revoked scope and range-to-whole-document escalation fail |
| A15 | Whole-document latest share explicit; save rechecked | exact share cannot reveal future edits; persistent policy visible |
| A16 | Generic file/search/git/export/index paths denied | source copy, symlink, hardlink, alternate object root and new O2 copy probes |
| A17 | Raw session, fork/resume and transcript boundaries | revoked context cannot replay via raw tool or harness history |
| A18 | Agent tools exactly read/resolve/flag after gates | native MCP, execute, save, replacement and story comment writes refuse |
| A19 | Existing C2 Markdown reads/line cites unchanged | no DOCX-as-lines conversion or redirect following adoption |
| A20 | Adoption gate before/after readiness and Q36 disposition | absent/revoked server readiness and required notice readiness refuse |
| A12 | Cross-user/business/project/universe denial | foreign project/universe IDs, same blob elsewhere and guessed unpublished objects grant nothing |
| A13 | Selection chip points only at committed text; ordinary lookup authority independent | draft mapping refuses; missing chip does not deny an authorized lookup |
| A14 | Normal project/universe/source-scope checks in every thread/room | pin, guessed ID, foreign universe and revoked holder cannot grant access |
| A15 | Latest after acknowledged save; explicit historical cite stays immutable | chip/per-send snapshot cannot force stale latest results |
| A16 | Host-only store and remote credentials; checked reader routes only | no worker store mount, arbitrary git/engine access or seat remote token |
| A17 | Existing Case 2 message/transcript policy | no O2-only/raw-session backdoor that skips existing thread/project checks; no false erasure promise |
| A18 | Reader/resolve/flag plus existing Case 2 lookups/supporting writer after gates | native MCP, execute, save, replacement and story comment writes refuse |
| A19 | Unconverted/support Markdown unchanged; converted path returns record and DOCX | no stale archive as chapter or DOCX-as-lines; historical line cite verifies by old commit/blob |
| A20 | Conversion/new story gates W4b/W7/PRD/post-10-27/Hermes/owner go | agent start, missing readiness, early release, fixture flag and missing first-real-book go refuse |
| A21 | Human full-feature save remains separate | unsupported projection yields no agent context, never lossy human save |
| A22 | Scope revocation during extraction | final pre-emission check prevents async stale-authority leak |
| A23 | Sibling chapter save | currentCommit/stale/cursor for untouched document remain unchanged |
| A24 | Full page and oversized paragraph | end before overflow; first oversized row refuses without text; no unreachable subrange claim |
| A25 | No O2 git remote or release copy | seat Gitea list/fork/clone fails; configured remote/object alternate disables O2; copies exclude stores |
| A26 | Every section 7.4 store/renderer | secret marker absent in each named legacy store, raw session, CLI/Discord/Console view and runs/ |
| A27 | Pre-W4b rollback reader | new O2 rows never rendered through legacy tables; missing retained extractor disables O2 |
| A28 | Dedicated mode vs C2 | mixed attachment/resume/fork denied; unchanged C2 supporting writes/replies fixtures pass |
| A29 | Two humans/viewer/grant revisions | viewer cannot share; second human cannot join or reuse context; all cursor authority fields checked |
| A25 | Every read/write/tool surface uses existing Case 2 isolation | same document ID across business/universe or replayed cursor refuses |
| A26 | Host-only off-host remote and per-save push | unset/local-only/unexpected remote refuses opening; push outage visible, local next lookup still fresh |
| A27 | Conversion restore accounting and durable journal reconciliation | pre-conversion --restore loses counted row but cannot revive Markdown; no lost-event concealment |
| A28 | Reopened M1 supports same-thread DOCX-read then supporting-note write | any room/project-authorized caller works; no per-document/share token; story/archive write refuses |
| A29 | Readiness is reviewed runtime/extractor plus approved PRD, not caller fields | no request/go/setting can assert release or Hermes retirement |
| A30 | Canonical schemas and complete selections | unknown keys, inconsistent ranges, xmlId null mismatch and noncanonical story IDs refuse |
| A31 | Closed leaf/revision table | every leaf/nested rule; mark-only deletion keeps text in final, mark-only insertion keeps text in original; markup mark spans zero-width; Word -1 separator/0 continuation-separator and older 0/1 reserved layouts validate, references to them refuse |
| A32 | Initial migration and revocation/restore | initial null-operation fence needs no restore confirmation but no grant is implicit; non-null restore fence needs confirmation and fresh authority; all tables counted/classified; old share stays refused; crash/backup-marker sweeps |
| A32 | Existing grant/restore controls and conversion metadata COUNTED | restore cannot revive grant, current Markdown or uncounted conversion event |
| A33 | Staging/archive/publish journal and operation readback | crash/sync at every boundary; pinned source until publish; post-push failure pending, not undone |
| A34 | Manifest order, typed index/links and format map | next_chapter never archive; draft scope retained; cross-universe/hybrid exclusions and R4/R5/R6 |
| A35 | Per-file fidelity, source history, human controls | all constructs/wiki/front matter preserved; no agent conversion; new DOCX has no Markdown precursor |
Implementation acceptance includes writing, bus, harness, business, WebUI,
seat mount/import closure and stack-copy/release fixtures; explicit production
adoption refusal/lift tests; independent parser/policy/tool review; G1 and
Sage's allocated schema edge. Evidence must name skips and refuse enablement
when an acceptance guard was not exercised. W4's review alone lifts nothing.
Implementation acceptance includes exact schemas/parser fixtures, live project/universe
revocation probes, same-thread supporting writes, remote denial/failure, counted-event restore
cases, Hermes gate, pinned-copy/sandbox closure and G1. All skips are named; an unexercised
guard blocks enablement. Old O2-only/private-share acceptance wording is replaced, not claimed
passed. No test, conversion or runtime experiment ran for this rewrite.
## 9. Handoff and unresolved decisions
## 9. Reopened review and handoff
Megavolt reviews this separate document and records a verdict in
`docs/reviews/2026-10-22_q195-docx-citations.md`. Revisions repeat direct
commit/blob handoff to Megavolt and Sage. Q36 remains with Jason through Mos;
no adoption, C2 reader/reply notice change or schema reinterpretation ships
on this document. W4b's brief must allocate ownership for every surface in
A16/A17 and section 7.4 before code: an unowned enforcement path is a blocker, not a deferred security
note. No live files, editor source imports, parser execution or tests were
used to write this design.
## Round 2 changes against review 08fcb8c1c
B1: exact selection wrapper, canonical paragraph identity, wiki/anchor schema,
chip/share and refusal records; ordinal-primary D25 refinement explicit.
B2: closed leaves and nested/paragraph-mark revision rules. B3: per-document
publication sequence and sibling-save acceptance. B4/B5: concrete append-only
authority, human proof, exact/latest share policy, revision vector and
single-human dedicated session lifetime. B6: complete-row paging; oversized
paragraph unsupported, no circular subrange API. B7: no remotes/backup push,
seat token and copy probes. B8: named native stores, renderers and immutable
run evidence, with O2-only storage/adapter readiness. B9: tables/columns,
rollback separation and extractor retention. M1: dedicated O2 conversations
only; mixed C2 context refused, no C2 requirement change. N1-N7 addressed in
canonical manifest, bound-business check, ratio/default wording, UI unavailable
reason, release-derived readiness and new A23-A31 acceptance rows. These are
proposed definitions, not passing implementation tests. Q36/adoption unchanged.
## Round 3 changes against review 0d627a2b3
R2-B1: paragraph-mark revisions preserve all run text; markup has a zero-width
mark span, never a fabricated whole-paragraph deletion. Reserved note leaves
are allowed only in reserved stories; whole-part structural validation is
explicit. A31 covers both mark-only cases and reserved notes.
R2-B2: bodies/quoted share payloads move out of SQLite into private content
objects; backup/post-attempt metadata paths are named with access/retention
rules. Every writing_* table is COUNTED/T46-classified. A non-restored deny
fence changes epoch before any store swap; owner confirmation cannot revive
old-epoch grants/shares. A32 tests revocation/restore and secret-marker sweeps.
R2-N1: agent grant subject means agentInstance. R2-N2: structural leaves have
one table. This remains design only; no code, restore, parser run or test.
## Round 4 changes against review 9fb999abf
R3-B1: reserved-note classification is by type with any canonical signed ID,
not sign; normal notes require nonnegative IDs, and references to reserved
notes refuse. A31 covers both actual Word layouts. R3-B2: first migration
creates the durable initial epoch before schema commit; null restoreOperation
needs no recovery confirmation, a restored epoch does. Missing established
fences never reinitialize. A32 covers first enable and post-restore states.
R3-N1: business/row/private random nonce are part of each content object and
its path, so no cross-business deduplication or guessed-body digest oracle.
R3-N2: removed the unsupported claim to detect privileged manual rollback
of the fence itself. R3-N3: restore_store anchor corrected to :498.
### Public Word fixture confirmation (not an extractor test)
Inspected only public Microsoft-maintained test packages in memory using
Python ZIP/XML readers, not the O2 parser or a live/user document. No binary
was imported into this repository. Source:
[OfficeDev/Open-Xml-PowerTools FootNote.docx](https://github.com/OfficeDev/Open-Xml-PowerTools/blob/a9187c5ec4199106562ec91c5ffd9f355251253b/NewDocxDocuments/FootNote.docx),
14,214 bytes; DOCX SHA-256
`40e2de78f0f70d8acf3db734df13f107c92986f1a40865cdf9772a8b55ab5e20`.
Its app properties identify Microsoft Office Word, version 15.0000.
footnotes.xml has separator=-1, continuationSeparator=0, normal note=1;
endnotes.xml has separator=-1 and continuationSeparator=0.
footnotes.xml SHA-256
`1da08d9c0a4a1af2706c035dfb176defd179686f81418134b796d49649e850a0`;
endnotes.xml SHA-256
`f83e7f1c9f91b44db26b147b22ca615a13488bc6a8084febb392ee00a6ca4f5e`.
This confirms the reviewer's -1/0 example in a Word-identified fixture.
An older independent Microsoft SDK fixture confirms why IDs cannot define
the class:
[Open-XML-SDK ForTestCase/footnote.docx](https://github.com/dotnet/Open-XML-SDK/blob/431ab05cf160248cc3885a4a766026d4f8243792/test/DocumentFormat.OpenXml.Tests.Assets/assets/TestDataStorage/v2FxTestFiles/ForTestCase/footnote.docx),
69,706 bytes, SHA-256
`26dc62a5d00e2c3c1df60e0d5bf1cac43b142a090ea595b9ad11ce73e3dab268`.
It identifies Microsoft Office Word 12.0000 and uses reserved IDs 0/1 in
both parts, with normal footnotes 2/3. No claim is made that the whole
packages pass v1's otherwise deliberately restrictive projection rules.
Fenton reviews this policy rewrite against mos-1368's A17–A41/Q1–Q16 and mos-1372 D1–D9,
including reopened M1 and A19/A20/A28, host-only remote and C5 restoration. Megavolt's old
APPROVE stays historical at the original blob; no author writes its verdict file. The
locator/projection facts remain source-backed, but the new authority/tool/runtime contract
requires its own independent approval and the Case 2 PRD approval, not a silent addendum.
Owner sends both design blobs/commit to Fenton, Mos and Sage. No code, private document,
conversion, source build/browser test, queue entry or live change is authorized here.