docs: specify O2 projection marks and restore revocation fence
This commit is contained in:
1 parent
019a0ea5ba
commit
54246cb32b
1 file changed
+164
-30
@@ -273,15 +273,20 @@ every revision container (nested spans may overlap). w:t and w:delText use
|
||||
the same ancestor rule, never special substring searches. Revision IDs are
|
||||
canonical nonnegative decimals; malformed or unmatched move ranges refuse.
|
||||
|
||||
Paragraph-mark revisions under pPr/rPr are not inline text containers. An
|
||||
inserted paragraph mark makes that paragraph's projected text empty in
|
||||
original; a deleted paragraph mark makes it empty in final. If both apply,
|
||||
both views are empty. Markup preserves its text and adds the corresponding
|
||||
whole-paragraph revision spans. Paragraphs are never merged, split or
|
||||
renumbered by a view; even an empty projection keeps its ordinal. Neither
|
||||
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 is omitted from agent responses in v1.
|
||||
Paragraph-mark revisions under pPr/rPr mark only the paragraph boundary,
|
||||
not the runs' text. A mark-only w:ins or w:del changes **no projected text**
|
||||
in final or original: run content follows the ancestor rule alone. Although
|
||||
accepting a mark deletion in Word joins paragraphs, this locator projection
|
||||
never merges, splits or renumbers XML paragraphs in any view. It preserves
|
||||
the text on both sides as separate ordinal records. In markup a paragraph
|
||||
mark contributes a zero-width span at that paragraph's code-point length,
|
||||
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
|
||||
is omitted from agent responses in v1.
|
||||
|
||||
Closed story/run leaf table (all names in w namespace unless stated):
|
||||
|
||||
@@ -293,6 +298,7 @@ Closed story/run leaf table (all names in w namespace unless stated):
|
||||
| noBreakHyphen | U+2011 |
|
||||
| softHyphen | U+00AD |
|
||||
| annotationRef | zero text; only allowed in comment story |
|
||||
| separator, continuationSeparator, continuationNotice | structural only, allowed only inside reserved footnote/endnote stories; never projected |
|
||||
| footnoteRef, endnoteRef | zero text; only in matching note story |
|
||||
| footnoteReference, endnoteReference | zero text; matching anchor at current offset with canonical story ID |
|
||||
| commentRangeStart, commentRangeEnd, commentReference | zero text; matching comment-start/end/reference anchor |
|
||||
@@ -302,6 +308,15 @@ Closed story/run leaf table (all names in w namespace unless stated):
|
||||
| fldChar, instrText, delInstrText, fldSimple | refuse requested story, including field results |
|
||||
| drawing, pict, object, txbxContent, subDoc, altChunk | refuse requested story |
|
||||
|
||||
Leaf validation covers **the entire selected XML part**, including all its
|
||||
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
|
||||
leaf in a normal note/body/comment refuses. A valid separator in a reserved
|
||||
note does not make readable footnotes/endnotes unsupported.
|
||||
|
||||
The table is exhaustive for story/run leaves; unknown leaves refuse, including
|
||||
nontext leaves not explicitly named. Formatting properties belong only to
|
||||
the separately closed manifest list. Anchors/range marks under invisible
|
||||
@@ -327,9 +342,9 @@ properties) and sdtContent only. Hyperlink containers are unsupported even
|
||||
without a relationship. Unknown text-bearing containers refuse. Nontext
|
||||
subtrees pPr, rPr, tblPr, tblGrid, trPr, tcPr and sdtPr may be ignored only
|
||||
if they contain no story text elements, field codes, external references,
|
||||
objects or unknown namespaces. Structural leaves bookmarkStart/bookmarkEnd,
|
||||
proofErr, lastRenderedPageBreak and the reference marks below contribute no
|
||||
text. w:sectPr is layout-only, with relationship targets validated normally.
|
||||
objects or unknown namespaces. Structural-leaf behavior is defined only by
|
||||
the closed table above. w:sectPr is layout-only, with relationship targets
|
||||
validated normally.
|
||||
Formatting-property descendants are a closed W4b release artifact: its full
|
||||
expanded-name list is included in the extractor manifest and independently
|
||||
reviewed before enablement; any name absent from that list refuses. This
|
||||
@@ -378,7 +393,8 @@ the verified quote is the locator's quote, with no separate unverified text.
|
||||
There is no default "first part" or automatic descent into notes/comments. The broker-bound business must equal
|
||||
locator.business; a mismatch is editor-access-refused before object lookup.
|
||||
Markup additionally requires `revisionSpans` on each
|
||||
row: array of `{kind,id,start,end}`, kind insert/delete/move-from/move-to,
|
||||
row: array of `{kind,id,start,end}`, kind insert/delete/move-from/move-to/
|
||||
paragraph-mark-insert/paragraph-mark-delete (mark spans have start=end),
|
||||
with null-free canonical decimal IDs and valid boundaries; other views omit
|
||||
that key. Limits cap output at 64 KiB UTF-8 JSON per response, including envelope and cursor. End a page before the first complete
|
||||
row that would overflow, and return a cursor for that row. This is normal
|
||||
@@ -456,7 +472,9 @@ a human cannot share a part or view they lack.
|
||||
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}` and scope exactly
|
||||
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;
|
||||
@@ -480,11 +498,12 @@ 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.
|
||||
|
||||
Each access decision records an authority vector exactly
|
||||
`{humanAccess,agentAccess,projectAccess,threadAccess,sharingPolicy,share}`.
|
||||
`{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
|
||||
@@ -563,7 +582,7 @@ only after the issuing execution session ends. No cross-human context reuse.
|
||||
### 7.3 Stored share schema
|
||||
|
||||
The immutable writing_shares payload is exactly
|
||||
`{version,id,business,project,document,conversation,executionSession,message,
|
||||
`{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
|
||||
@@ -571,7 +590,8 @@ 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).
|
||||
issuedAt is canonical UTC millisecond ISO timestamp. policyRevision equals
|
||||
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,
|
||||
@@ -607,8 +627,12 @@ C2-06 processing. An O2 flag is metadata stored by W5, not a story edit.
|
||||
| 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 transient O2 adapter memory and any unavoidable encrypted native
|
||||
transcript. It is keyed to O2 session, not an alias/symlink into seat stores.
|
||||
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
|
||||
@@ -626,8 +650,10 @@ 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/, an O2 database snapshot,
|
||||
exports or repository object directories. No raw O2 Gitea grant may be minted.
|
||||
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
|
||||
@@ -637,7 +663,9 @@ expose it to generic tools. Native engine APIs remain human-only.
|
||||
|
||||
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). Every update/delete/replace refuses; latest seq is state.
|
||||
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.
|
||||
|
||||
@@ -649,14 +677,25 @@ unvalidated JSON. No existing C2 messages/events/sessions column changes.
|
||||
| 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, body TEXT, provenance_json TEXT, operation_id TEXT UNIQUE |
|
||||
| writing_shares | conversation TEXT FK, message TEXT FK, document TEXT, payload_json TEXT, operation_id TEXT UNIQUE; UNIQUE(conversation,message,document) |
|
||||
| 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. O2 message
|
||||
provenance_json is exactly an array of `{document,share,commit,blob,extractor}`,
|
||||
sorted by document/share; every entry requires live scopes. Human messages
|
||||
with no O2 attachment use [] but remain private dedicated messages. No
|
||||
application checks require agreement, not just ID existence. The canonical message content object is exactly `{body,provenance}`: 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.
|
||||
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
|
||||
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
|
||||
@@ -677,10 +716,90 @@ 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 messages,
|
||||
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 :496ff 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 and restoreOperation are host-generated opaque IDs; 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.
|
||||
|
||||
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 both business confirmation and matching
|
||||
restore_epoch; 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
|
||||
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
|
||||
@@ -736,7 +855,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 | independent projection covers every leaf, nested revisions and paragraph-mark empties in all views |
|
||||
| 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 |
|
||||
|
||||
Implementation acceptance includes writing, bus, harness, business, WebUI,
|
||||
seat mount/import closure and stack-copy/release fixtures; explicit production
|
||||
@@ -771,3 +891,17 @@ 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.
|
||||
Reference in new issue
Block a user