docs: close W4 locator and access review gaps
This commit is contained in:
1 parent
15e91538f2
commit
019a0ea5ba
1 file changed
+445
-112
@@ -52,6 +52,21 @@ 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.
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
@@ -66,7 +81,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 reading view described below. These are proposed O2-only records.
|
||||
except the explicitly listed request defaults below. These are proposed O2-only records.
|
||||
|
||||
A locator is exactly:
|
||||
|
||||
@@ -90,23 +105,68 @@ when absent. A textView is always recorded in every locator; there is no
|
||||
resolver default. quote is a nonempty valid Unicode string bounded to 16,384
|
||||
code points and 64 KiB UTF-8. Unknown enum values or unexpected types refuse.
|
||||
|
||||
The ordinal-primary rule below deliberately refines D25's wording ("IDs
|
||||
when present, otherwise ordinal"): a possibly duplicated paraId is a required
|
||||
cross-check, never the lookup key. This is an O2-only design choice for Sage's
|
||||
implementation brief, not an amendment to Case 2.
|
||||
|
||||
`paragraph.ordinal` is the zero-based paragraph index within the selected
|
||||
story, in deterministic XML document order, including empty paragraphs and
|
||||
table-cell paragraphs. `xmlId` is null or the literal eight-hex `w14:paraId`,
|
||||
table-cell paragraphs. `xmlId` is null exactly when the selected node has no w14:paraId, otherwise the literal eight-hex `w14:paraId`,
|
||||
canonicalized uppercase. Ordinal is always present: paraId alone is not
|
||||
unique or stable enough. If provided, xmlId must equal the selected node's
|
||||
attribute; missing, duplicated or mismatched IDs do not trigger a search.
|
||||
unique or stable enough. The key is mandatory: passing null for a node that has the attribute refuses.
|
||||
The non-null value must equal that node's attribute; duplicated IDs do not
|
||||
trigger a search, since ordinal selects the node. Mismatches refuse.
|
||||
Thus identical paragraphs remain distinct. `start < end <= codePointLength`
|
||||
selects one paragraph, end exclusive. A multi-paragraph selection is an
|
||||
ordered nonempty list of these ranges, with no cross-paragraph offsets.
|
||||
No auto-merging or silent clipping. Empty paragraph references may be shown
|
||||
in a reading page but cannot manufacture a nonempty quote locator.
|
||||
selects one paragraph, end exclusive. A multi-paragraph selection has the exact wrapper below, not a bare array:
|
||||
|
||||
```json
|
||||
{
|
||||
"kind":"docx-selection", "version":1,
|
||||
"business":"acme", "project":"book", "document":"chapter_1",
|
||||
"commit":"<40 lowercase hex>", "blob":"<40 lowercase hex>",
|
||||
"extractor":{"id":"mosaic-docx-text-v1","revision":"<64 lowercase hex>"},
|
||||
"part":{"class":"body","name":"word/document.xml","story":"main"},
|
||||
"textView":"final",
|
||||
"ranges":[
|
||||
{"paragraph":{"ordinal":0,"xmlId":null},"start":1,"end":5,"quote":"ello","quoteDigest":"<64 lowercase hex>"},
|
||||
{"paragraph":{"ordinal":1,"xmlId":null},"start":0,"end":5,"quote":"World","quoteDigest":"<64 lowercase hex>"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
It has 2..32 ranges, all sharing the wrapper's exact document, publication,
|
||||
blob, extractor, view and part. Ordinals are consecutive and increasing.
|
||||
First range ends at its paragraph length, last starts at zero; intermediate
|
||||
ranges cover whole paragraphs. Each range verifies its own quote/digest;
|
||||
no inserted newline is included in any digest. Empty intermediate paragraphs
|
||||
are represented by start=end=0, quote="" and SHA-256 of empty UTF-8; the first
|
||||
and last are nonempty. Total quote budget is 16,384 code points and 64 KiB
|
||||
UTF-8 for the entire selection. A one-paragraph selection uses docx-range
|
||||
only: there is one canonical representation. No auto-merging, cross-story
|
||||
selection or clipping. Resolver's locator union and a range chip accept
|
||||
either exact kind. An atomic selection fails entirely if any range fails;
|
||||
a response never returns only the valid fragments.
|
||||
|
||||
`extractor.revision` is the digest of a canonical build manifest containing
|
||||
extractor source blob IDs, projection rules revision and locked parser/ZIP
|
||||
library versions. The release pins that manifest and independently verifies
|
||||
its digest. It is not caller-supplied executable code or a mutable version
|
||||
label. Resolver requires that exact available revision; changed extraction
|
||||
label. Its canonical manifest is exactly `{manifestVersion:1,id,rulesRevision,
|
||||
sources,dependencies,formattingProperties}`. id is mosaic-docx-text-v1,
|
||||
rulesRevision is integer 1; sources is a path-sorted array of `{path,blob}`
|
||||
for every extractor/transitive source input; dependencies is a name-sorted
|
||||
array of `{name,version,integrity}` from the lock, including ZIP/XML parsers;
|
||||
formattingProperties is a sorted unique array of expanded XML names.
|
||||
Duplicate keys/entries refuse. Canonical bytes are compact JSON, UTF-8,
|
||||
no BOM/newline, recursively lexicographic ASCII object-key order; strings
|
||||
escape only quote/backslash and U+0000..001F (lowercase four-hex \u escapes),
|
||||
with other scalar values literal. Arrays preserve the specified sorted order,
|
||||
integers are decimal without leading zeroes. revision is SHA-256 of those
|
||||
bytes. Independent tooling must reproduce it. Server readiness is derived
|
||||
from the verified release artifact containing that manifest and its reviewed
|
||||
formatting list, not any human-authored config field.
|
||||
Resolver requires that exact available revision; changed extraction
|
||||
rules create a new revision, never silently reinterpret an old locator.
|
||||
|
||||
A paragraph extraction record is exactly:
|
||||
@@ -123,10 +183,20 @@ A paragraph extraction record is exactly:
|
||||
|
||||
Anchors have the exact shape `{kind,story,offset}`; allowed
|
||||
kinds are `footnote-reference`, `endnote-reference`, `comment-start`,
|
||||
`comment-end`, `comment-reference`; story is a canonical decimal signed XML
|
||||
ID string, offset a code-point boundary in this record. They never grant
|
||||
access to their target. wikiTokens are exact source slices matching the
|
||||
literal syntax in section 5, not instructions or external links. All text
|
||||
`comment-end`, `comment-reference`; story is a canonical decimal nonnegative XML
|
||||
ID string (`0` or `[1-9][0-9]*`, the same as note/comment locator stories),
|
||||
offset a code-point boundary in this record. They never grant
|
||||
access to their target. wikiTokens have exactly `{start,end,literal,form,target,label,heading}`.
|
||||
form is link/label/heading/embed; label and heading are null except in their
|
||||
corresponding form. target is 1..256 code points without `[`, `]`, `|`, `#`,
|
||||
control characters or newline; a label/heading is 1..256 code points without
|
||||
brackets, `|`, `#`, controls or newline. literal is respectively `[[target]]`,
|
||||
`[[target|label]]`, `[[target#heading]]` or `![[target]]`. start/end are exact
|
||||
paragraph code-point bounds, including the exclamation for embed. No nested
|
||||
forms or combined label+heading/embed variants are interpreted in v1; they
|
||||
remain text. Tokens are maximal nonoverlapping complete forms in left-to-right
|
||||
order, with their literal equal to the text slice. They are not instructions
|
||||
or external links. All text
|
||||
and metadata are untrusted document data. Part/story IDs, excerpts and
|
||||
comment content never enter system prompts as instructions.
|
||||
|
||||
@@ -184,7 +254,7 @@ retain a full document without it becoming readable to agents.
|
||||
Parse XML without DTDs, external entities, XInclude or network/file resolution.
|
||||
Reject DOCTYPE/entity declarations. Bound before allocation: compressed blob
|
||||
32 MiB, total expanded bytes 128 MiB, each entry 16 MiB, 2048 entries,
|
||||
expansion ratio 100:1, XML depth 128, 1,000,000 nodes, 100,000 paragraphs,
|
||||
expansion ratio 100:1 both per entry and for the total (nonempty expanded data with zero compressed bytes refuses), XML depth 128, 1,000,000 nodes, 100,000 paragraphs,
|
||||
1,000,000 code points per paragraph and 10,000,000 per document. Extraction
|
||||
has a 5 s host deadline and bounded worker memory of 256 MiB. Exceeding any
|
||||
bound yields `editor-document-unsupported`, no truncated success. These are
|
||||
@@ -194,20 +264,56 @@ changing them requires reviewed parser evidence. CRC/size mismatches refuse.
|
||||
## 5. Deterministic reading views
|
||||
|
||||
`textView` enum is `final`, `original`, `markup`. Default requests use final.
|
||||
Final is the accepted-reading projection: include w:t in normal runs and
|
||||
w:ins/w:moveTo; exclude w:del/w:moveFrom, including w:delText. Original does
|
||||
the reverse. This does not accept/reject changes or modify the DOCX. Markup
|
||||
includes both in source order and returns explicit revision spans with kind,
|
||||
ID and code-point offsets; deletion/source text is data, not an instruction.
|
||||
Original/markup and comments require separately enabled scopes; final body
|
||||
permission alone cannot reveal hidden deletions or comment text. Revision
|
||||
author/date metadata is omitted from agent responses in v1.
|
||||
Final is the accepted-reading projection, not an accept-changes operation.
|
||||
A text run is visible in final iff no ancestor is w:del or w:moveFrom; in
|
||||
original iff no ancestor is w:ins or w:moveTo. This ancestor veto applies to
|
||||
nested revisions, so an insertion inside a deletion is absent in both final
|
||||
and original. Markup includes both in source order with revision spans for
|
||||
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.
|
||||
|
||||
Closed story/run leaf table (all names in w namespace unless stated):
|
||||
|
||||
| Leaves | Text/anchor contribution |
|
||||
| --- | --- |
|
||||
| t, delText | decoded text subject to view/ancestor rule |
|
||||
| tab | U+0009 |
|
||||
| br, cr | U+000A, regardless of page/column type; layout kind does not affect offsets |
|
||||
| noBreakHyphen | U+2011 |
|
||||
| softHyphen | U+00AD |
|
||||
| annotationRef | zero text; only allowed in comment story |
|
||||
| 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 |
|
||||
| moveFromRangeStart, moveFromRangeEnd, moveToRangeStart, moveToRangeEnd | zero text; validate paired IDs, do not duplicate revision text |
|
||||
| bookmarkStart, bookmarkEnd, permStart, permEnd, proofErr, lastRenderedPageBreak | zero text; structural only, never access grants |
|
||||
| sym, ptab | refuse requested story (font/position dependent) |
|
||||
| fldChar, instrText, delInstrText, fldSimple | refuse requested story, including field results |
|
||||
| drawing, pict, object, txbxContent, subDoc, altChunk | refuse requested story |
|
||||
|
||||
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
|
||||
revision ancestors are omitted in that view but still structurally validated.
|
||||
Comment author data and ACL-like Word permission markers never supply Mosaic
|
||||
authority. Unknown extension/AlternateContent text is unsupported, not ignored.
|
||||
|
||||
Walk direct story paragraphs and table cells in XML order, including nested
|
||||
tables in their containing cell order. Paragraph ordinals do not change with
|
||||
view. Concatenate visible text across adjacent runs with no invented spaces.
|
||||
Preserve decoded Unicode as stored: no NFC, case folding or whitespace
|
||||
collapse. w:tab yields U+0009; w:br/w:cr yields U+000A; xml:space text remains
|
||||
collapse. xml:space text remains
|
||||
literal. Offsets count Unicode scalar values, not UTF-16 units or graphemes;
|
||||
unpaired surrogates/invalid XML characters refuse. Field instructions,
|
||||
hidden/vanished text and text boxes are not silently omitted: their presence
|
||||
@@ -233,7 +339,8 @@ permissive descendant-text scraper.
|
||||
|
||||
Reference marks contribute anchors at the current text boundary, not invented
|
||||
note numbers or inline comment bodies. Each note/comment is separately paged.
|
||||
A quote cannot straddle a view, paragraph or story. Extract literal
|
||||
A single range cannot straddle a view, paragraph or story; the selection wrapper
|
||||
may cover contiguous paragraphs only under section 3. Extract literal
|
||||
`[[target]]`, `[[target|label]]`, `[[target#heading]]` and `![[target]]`
|
||||
after concatenating visible runs within one paragraph, so split-run tokens
|
||||
survive formatting changes. Preserve complete literal bytes as UTF-8 after
|
||||
@@ -268,28 +375,33 @@ raw package attributes. `nextCursor` is null at end; `currentCommit` is the
|
||||
current published document commit; stale is exactly commit != currentCommit.
|
||||
A resolver response is exactly `{version:1,locator,currentCommit,stale}`:
|
||||
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.
|
||||
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,
|
||||
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. Never split a
|
||||
paragraph silently: if one row exceeds the page budget, return
|
||||
`editor-range-too-large`; a caller may request a checked subrange through a
|
||||
separately bounded citation, not receive a truncated paragraph masquerading
|
||||
as complete. Response overflow never returns partial data with success.
|
||||
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
|
||||
paging, not partial-row success. If that first row cannot fit even on an
|
||||
otherwise empty page, return editor-range-too-large with no text. Such a
|
||||
paragraph is unsupported for agent reading in v1; no subrange read exists.
|
||||
A human-visible "paragraph exceeds agent reading limit" reason appears only
|
||||
after authorization. The human editor/save is unaffected. All empty paragraphs
|
||||
still produce rows and advance the cursor. No page can return the same cursor
|
||||
without progress.
|
||||
|
||||
Cursor is a host-authenticated opaque token binding principal, session,
|
||||
project/document, commit/blob, extractor, view, part, next ordinal, grant
|
||||
revision and expiration (5 minutes). It carries no plaintext content.
|
||||
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.
|
||||
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 current principal and historical-view scope; load
|
||||
Resolver steps: authorize the current principal tuple, live scopes and exact/historical share (there is no separate undefined historical-view scope); load
|
||||
that commit's manifest/blob; validate package; run the exact pinned extractor;
|
||||
select exact part/story/ordinal and optional XML ID; select code-point range;
|
||||
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
|
||||
blob and commit membership, not digest alone. Any mismatch refuses
|
||||
`editor-citation-invalid` without guessed text. After newer publication the
|
||||
@@ -304,91 +416,286 @@ for absent ownership/grant/share (same response for nonexistent or foreign IDs);
|
||||
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.
|
||||
Return no source text, XML, path or untrusted parser error in refusals.
|
||||
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
|
||||
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,
|
||||
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. Grants, sharing and all read surfaces
|
||||
## 7. Authority data, dedicated sessions and sharing
|
||||
|
||||
Access is the intersection of bound role tool grant, human-configured
|
||||
business/project/document scope, principal's live project/universe rights,
|
||||
requested part/view scope, server readiness and a valid share context for
|
||||
this conversation. No wildcard across businesses/universes. Human/session
|
||||
identity comes from existing trusted transport; a request cannot name a human.
|
||||
The six creative role tool declarations grant the *operation*, never every
|
||||
chapter. An agent's own supporting-document writer grants no story access.
|
||||
### 7.1 Scope and principal records
|
||||
|
||||
Human-only sharing policy defaults `visible-chip-per-send`; the chip is
|
||||
visible and removable and binds document, committed version and selected
|
||||
verified locators. Before readiness it says editor context unavailable and
|
||||
sends no document ID, title, path, selection, quote or source metadata.
|
||||
The server resolves selection against the saved projection; if it cannot
|
||||
map exactly (including a live edited selection), refuse context attachment.
|
||||
Never send unsaved text to try to find a match. Free chat can still send with
|
||||
no editor context if the human removes the unavailable chip.
|
||||
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.
|
||||
|
||||
A successful send creates a server-owned immutable share record scoped to
|
||||
that message/conversation, human principal, document and exact committed
|
||||
ranges/views. It narrows the live grant; it does not widen it or authorize a
|
||||
later version. Every subsequent lookup supplies the server-resolved share
|
||||
context and intersects current grants. A future send requires another visible
|
||||
chip under the default policy. A human may explicitly change sharing to
|
||||
`off` or `conversation-current`: off revokes future reads; conversation-current
|
||||
permits the named conversation's authorized latest lookups until human
|
||||
revocation/session end, with a persistent visible indicator. Such an explicit
|
||||
policy change cannot add document scopes or hide the indicator. Pins only
|
||||
organize human UI; they never create a share record or turn on sharing.
|
||||
A range share permits only its ranges, not an entire paragraph/page or comments.
|
||||
Latest whole-document reads require a whole-document share, never a range chip.
|
||||
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.
|
||||
|
||||
Historical exact shares resolve their old citations while live authorization
|
||||
holds. A successful save makes the next authorized *latest* lookup use its
|
||||
new commit, but never silently upgrades an exact earlier share. The UI says
|
||||
"Saved newer version; share again" where only an old range is shared. No
|
||||
future edit is disclosed on the strength of an earlier send. Share records
|
||||
are resolved by broker session/context, not caller-minted IDs.
|
||||
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
|
||||
`{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.
|
||||
|
||||
Before returning raw conversation/session history, replaying a prompt,
|
||||
resuming/forking a worker or exporting an agent transcript, reauthorize each
|
||||
O2 attachment/content block. Keep source text out of generic raw session
|
||||
stores and logs: persist opaque checked references, not rich prompt text.
|
||||
Host prompt assembly resolves content in memory for the authorized turn.
|
||||
Agent harness transcripts containing expanded content must stay host-owned,
|
||||
outside worker/generic roots, encrypted or withheld from raw session tools;
|
||||
W4b must prove the selected harness does not persist readable O2 text into a
|
||||
worker-readable store. Otherwise that harness's editor tools remain disabled.
|
||||
Revocation cannot erase an agent's memory or already delivered output; it
|
||||
prevents subsequent fetch/replay, and the UI must not promise retrospective
|
||||
secrecy. Copying an authorized quote into free agent prose is not a new grant:
|
||||
O2-derived stored outputs retain the same provenance/access envelope.
|
||||
This is conservative turn-level provenance, not content recognition: every
|
||||
output of a turn receiving O2 context, and any resumed/forked descendant,
|
||||
inherits the union of those document scopes. It can be emitted only into a
|
||||
host-controlled destination enforcing that envelope. Generic supporting-doc
|
||||
writes, git writes, exports and arbitrary outgoing tools from that turn
|
||||
refuse unless their destination has the identical enforcement; normal C2
|
||||
turns without O2 context keep their existing tool semantics. W4b must enforce
|
||||
this broker-side before dispatch, not ask an agent to self-label a quote.
|
||||
If the runner cannot isolate that tool set for an O2-context turn, it cannot
|
||||
receive O2 context. The separate story-write ban always applies.
|
||||
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.
|
||||
|
||||
All O2-derived exports, caches, extraction indexes, previews, temporary files,
|
||||
source copies and git history are classified with their document, outside
|
||||
generic read/grep roots and outside Case 2 library mappings. No raw O2 git
|
||||
clone/read grant; no source copy under a worker's project root. Generic git
|
||||
or shell tools cannot reach it through shared parent mounts, symlinks,
|
||||
hardlinks, alternate worktrees, object alternates or exports. Read-only is
|
||||
insufficient: no store mount in workers. Validate canonical roots, reject
|
||||
symlink components, and enforce ownership/mode and sandbox mount closure.
|
||||
A new import/copy needs fresh document grants and sharing. Don't expose
|
||||
manifest names/document lists through metadata routes before authorization.
|
||||
Native engine routes remain human-only and cannot be proxied by agent tools.
|
||||
Each access decision records an authority vector exactly
|
||||
`{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.
|
||||
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.
|
||||
|
||||
W4b is blocked if any raw session or generic surface cannot enforce these
|
||||
rules. Do not compensate by UI hiding, retroactively classifying all Case 2
|
||||
Markdown as secret or widening its schema. Store and bus migrations are
|
||||
reviewed together; revoke/invalidate caches by grant revision and verify live
|
||||
scope again before emission. Cache keys include document, commit/blob,
|
||||
extractor, part and view; shared extraction cache is never an authorization
|
||||
cache. Host audit events contain IDs/digests/status only, never content.
|
||||
### 7.2 One human per context; settings and chip requests
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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:
|
||||
|
||||
- `{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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.3 Stored share schema
|
||||
|
||||
The immutable writing_shares payload is exactly
|
||||
`{version,id,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).
|
||||
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 transient O2 adapter memory and any unavoidable encrypted native
|
||||
transcript. 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/, an O2 database snapshot,
|
||||
exports or repository object directories. 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). Every update/delete/replace refuses; latest seq 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, 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) |
|
||||
|
||||
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
|
||||
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 messages,
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
## 8. Acceptance matrix and release evidence
|
||||
|
||||
@@ -421,6 +728,15 @@ no fixture-only readiness flag may exist in production APIs.
|
||||
| A20 | Adoption gate before/after readiness and Q36 disposition | absent/revoked server readiness and required notice readiness 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 |
|
||||
| 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 |
|
||||
|
||||
Implementation acceptance includes writing, bus, harness, business, WebUI,
|
||||
seat mount/import closure and stack-copy/release fixtures; explicit production
|
||||
@@ -435,6 +751,23 @@ Megavolt reviews this separate document and records a verdict in
|
||||
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 before code: an unowned bypass is a blocker, not a deferred security
|
||||
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.
|
||||
Reference in new issue
Block a user