Skip to content

fix(server): root a room only on a write it retains (C99) - #403

Open
vieiralucas wants to merge 6 commits into
mainfrom
server/c99-empty-batch-creator
Open

fix(server): root a room only on a write it retains (C99)#403
vieiralucas wants to merge 6 commits into
mainfrom
server/c99-empty-batch-creator

Conversation

@vieiralucas

@vieiralucas vieiralucas commented Aug 9, 2026

Copy link
Copy Markdown
Member

ensure_creator fired on any Ok from Hub::ingest, and an empty batch answers Ok(vec![]) — while the ingest's entry(room).or_insert_with(Room::new) materialises the room on the way through. The first authenticated actor to send a no-op Ops frame at an unestablished room became its creator: the doc-ACL authority root owning /, over a room at sequence zero, having authored no byte the room retains. Reserve-by-no-op, the shape C23 (#389) closed one tier down for the replica-identity claim.

Two conditions close it, at different seams, because they are different statements.

  • The room must have reached a sequencemay_stand_as_root, in Hub::ensure_creator, which a client write, a peer's Replicate and the metadata-only ReplicateMeta all compose through, so the replication path is judged by it rather than left with the hole. This forces the shape: "only a batch that landed" (C23's rule) is not statable at ReplicateMeta, which has no batch beneath it by construction (C55, fix(server): carry a room's authority root where no op batch is going that way (C55) #397).
  • The batch must have presented an op — at the client write seam alone. Found by falsification after the room rule shipped: being a rule about the room, it says nothing once the room has reached a sequence, and a room holding content with no root is a real state (C55's own two routes — an anonymous establishing commit, and a replica whose metadata write was lost). There a frame carrying nothing took / over content its sender had no part in, then read through the room author's deny by the creator's exemption — permanently, since set-once locks the content's authors out.

The line is what the batch presented, not what landed. A resend the room dedups whole still roots a room that holds ops: presenting the room's content is something only a replica holding it can do, where an empty frame presents nothing. Refusing the resend too would buy attributability, at the cost of C55's route 1, against an attacker who pays one op to route around it.

Where the room rule stops was measured, not chosen. A first cut applied it at the snapshot install and the store load too; falsification measured both as worse than the defect — content installed at floor zero came up rootless (every tuple inert), and a room a state transfer left at sequence zero lost a legitimately-established root on reload, over content it still holds. Both are C29's inert-deny hole from the other side. Both are now pinned as tests.

  • Mutation sweep under statement deletion over both conditions, the call site, and the two seams left alone: 0 survivors.
  • Four falsification passes, each in its own worktree. Two measured the reload strip; one found the consequence test root-independent (a deny beats a schema-tier grant from any grantor — only the creator's exemption discriminates); the fourth, against the final sha, found the empty frame still took a rootless room's authority and the reload fixture false about itself.
  • Spec: crates/server/tests/creator_establishment.rs (14). ARCHITECTURE §Owner; DECISIONS 2026-08-09. Residue: C140, C141.

@vieiralucas
vieiralucas requested a lite review from Copilot August 9, 2026 13:08

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

`ensure_creator` fired on any `Ok` from `Hub::ingest`, and an empty batch
answers `Ok(vec![])` while the ingest materialises the room on the way
through — so the first authenticated actor to send a no-op `Ops` frame at
an unestablished room became its doc-ACL authority root, owning `/`
without authoring a byte the room retains.

The rule is stated once, in `may_stand_as_root`, for every seam that
installs a root: the client write, a peer's `Replicate`/`ReplicateMeta`,
a snapshot install, and the record read back off the store. It is a rule
about the room rather than the batch — no actor roots a room at sequence
zero — because `ReplicateMeta` carries no batch to judge by construction.
A deduped write still roots a room that already holds ops, deliberately.
…s (C99)

A first cut applied `may_stand_as_root` at every seam that installs a
root. Falsification measured two of them as worse than the defect: a
state full of content whose frame names floor zero came up rootless,
leaving every doc-ACL tuple in it with no authority to be decided under,
and a room a state transfer left at sequence zero lost a legitimately
established root on its next reload — after which the next authenticated
writer takes `/`. Both are C29's inert-deny hole from the other side.

The rule now sits only at `Hub::ensure_creator`, which a client write, a
peer's `Replicate` and the metadata-only `ReplicateMeta` all compose
through — the seam whose sequence is this node's own count of what it
took, rather than a number that arrived beside the root. Both measured
states are pinned as tests so it cannot creep back.

The consequence test proved the wrong thing: a deny beats a schema-tier
grant from any grantor, so its ACL half held whoever owned `/`. What the
root actually carries is the creator's exemption, so the deny now names
the squatter and the squatter reads.
The room-level rule closed only half the defect. Being a rule about the
room, it says nothing once the room has reached a sequence — and a room
holding content with no root is a real state, left by an anonymous
establishing commit or by a replica whose best-effort metadata write was
lost, which are C55's own two routes. There a frame carrying nothing
satisfied the rule and took `/` over content its sender had no part in,
then read straight through the room author's deny by the creator's
exemption, permanently: set-once locks the content's authors out.

The condition that refuses it is a statement about what the batch
presented, so it sits at the client write seam. The replication callers
state no such condition and must not: they adopt a root established
elsewhere, and `ReplicateMeta` is by construction the frame with no batch
beneath it. A resend the room dedups whole still roots a room that holds
ops — presenting the room's content is something only a replica holding
it can do, where an empty frame presents nothing.

Also corrects the reload test, whose fixture installed an empty state and
so pinned a root over nothing rather than the inert-deny harm it cited.
@vieiralucas
vieiralucas force-pushed the server/c99-empty-batch-creator branch from d2a8b4d to 3059301 Compare August 9, 2026 19:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants