docs: define reserved notes and initial O2 restore fence

This commit is contained in:
Rocko committed 2026-10-22 18:47:01 -05:00
1 parent 54246cb32b
commit 994c3f7fb1
1 file changed
+88 -14
+88 -14
View File
@@ -224,9 +224,13 @@ extractor supports them; never guess equivalent namespaces.
Body/note/comment ownership comes solely from relationships in that committed
package, not filenames supplied by a request. A note/comment belongs to one
DOCX, never the book or an adjacent chapter. Reserved separator/continuation
notes (negative IDs and type separator/continuationSeparator) are structural,
not readable stories. Duplicate story IDs, dangling references or overlapping
DOCX, never the book or an adjacent chapter. Classify reserved notes by
w:type in {separator, continuationSeparator, continuationNotice}, with any
canonical signed integer ID (`0` or `-?[1-9][0-9]*`, no plus/leading zeroes),
not by sign. A normal note has absent type or type normal and a canonical
nonnegative ID; unknown types refuse. Reserved notes are structural and never
readable stories. Any document reference to a reserved note refuses, including
an id-0 continuation separator; the reference must resolve to a normal note. Duplicate story IDs, dangling references or overlapping
ambiguous comment ownership refuse extraction. Unreferenced comment/note
stories are not agent-readable. Cross-paragraph comment anchors may be
recorded, but never concatenate their text with body text.
@@ -313,7 +317,7 @@ normal and reserved stories, before any requested story is projected. Reserved
separator/continuation notes are structurally validated against this same
closed vocabulary, with separator/continuationSeparator/continuationNotice
permitted only there, never a readable locator story. Each reserved ID/type
must be internally consistent; duplicate reserved IDs refuse. A separator
must be internally consistent; duplicate IDs within a part refuse. A separator
leaf in a normal note/body/comment refuses. A valid separator in a reserved
note does not make readable footnotes/endnotes unsupported.
@@ -681,16 +685,25 @@ unvalidated JSON. No existing C2 messages/events/sessions column changes.
| 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 `{body,provenance}`: body is
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 the full section 7.3 payload, including any quoted selection.
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/<digest>.json, with no remote or
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.
@@ -724,7 +737,7 @@ its title, quote or prompt. No live rollback is part of acceptance testing.
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 :496ff moves current
`<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
@@ -756,7 +769,8 @@ 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 and restoreOperation are host-generated opaque IDs; droppedRowsDigest
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
@@ -764,6 +778,23 @@ 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
@@ -785,10 +816,14 @@ and returns `{ok:true,result:{business,epoch,confirmed:true}}`. It restores
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 both business confirmation and matching
restore_epoch; old object bytes remaining on disk are not capabilities.
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/rolled-back fence state denies O2; --events N approves a
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,
@@ -855,8 +890,8 @@ no fixture-only readiness flag may exist in production APIs.
| 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 |
| 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; reserved note leaves validated but never projected |
| A32 | Revocation after backup, then restore | all writing_* counted/classified; old share refuses before/after owner confirmation; fence survives crash and no secret marker in backup/post-attempt/rehearse paths |
| 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 |
Implementation acceptance includes writing, bus, harness, business, WebUI,
seat mount/import closure and stack-copy/release fixtures; explicit production
@@ -905,3 +940,42 @@ 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.