spec(v2.3): multi-cell fabric RFCs — cross-org set, cell-quorum, human-escalate (refs #53 #54 #55) - #57
spec(v2.3): multi-cell fabric RFCs — cross-org set, cell-quorum, human-escalate (refs #53 #54 #55)#57Tailor-AUS wants to merge 6 commits into
Conversation
…e (refs #53, refs #54, refs #55) Mechanical copy of spec/v2.2/ into spec/v2.3/ plus a DRAFT banner (README + SPECIFICATION.md header). No normative change in this commit; spec/v2.2/ is untouched per AGENTS.md rule 4. NOT FOR CITATION until maintainer sign-off. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… cross-org (refs #53) Defines cell as a vendor-neutral isolation grouping identified by a cell DID (SHOULD did:key); membership carries cell_id; the participant cell set is fixed at fabric establish. Adds the fifth §15.4 cross-organisation trigger: different cell_id on the same fabric => cross-org, so §17.6 authorization_proof and the §17.9 Authorization-Required checks fire on the cross-cell wire. Cell boundaries are never inferred from DID method. Design record: docs/v2-prep/rfc-multi-cell-quorum.md Change 1. Complementary to the v2.1-line §25 work (#41 / PR #45), not a substitute. DRAFT — no normative merge without maintainer sign-off (AGENTS.md rule 5). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…merge on multi-cell fabrics (refs #54) Adds the cell-quorum ApprovalPolicy (§5): >=1 counted approval from each cell in the fixed participant cell set, bucketed by cell_id, not head-count. Self-approval rule still applies within a cell. A dark cell does not shrink the set — fail closed. human-only on a multi-cell fabric is evaluated as human-only AND cell-quorum. When the participant cell set has cardinality > 1, objection-based, auto, single, majority, and unanimous MUST NOT be configured; establish and policy changes that would violate this are refused (fail closed, never a silent downgrade). §10.5 notes objection-based merge is a single-cell mechanism. §15.1 gains the cellQuorum capability flag. Design record: docs/v2-prep/rfc-multi-cell-quorum.md Changes 2-3. Builds on the §15.4 cell trigger (#53). Complementary to v2.1-line §25 (#41 / PR #45): sovereignty, not signature law. DRAFT — no normative merge without maintainer sign-off (AGENTS.md rule 5). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
§5 Conflict Detection: when the participant cell set (§15.4) has cardinality > 1, the conflict strategy MUST be human-escalate; first-wins and merge-both MUST NOT be configured (refused, fail closed). vote is not a substitute unless it is itself cell-quorum AND still escalates when any cell in the fixed participant set is dark. Design record: docs/v2-prep/rfc-multi-cell-quorum.md Change 4. Lands after the §15.4 cell trigger (#53) and cell-quorum (#54). DRAFT — no normative merge without maintainer sign-off (AGENTS.md rule 5). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
| | `Observer` | Read content, sections, events. Set salience. | | ||
| | `Suggester` | All Observer permissions + propose, declare intent, publish constraints. | | ||
| | `Collaborator` | All Suggester permissions + approve, reject, object, lock sections. | | ||
| | `Autonomous` | All Collaborator permissions + proposals auto-merge (bypass approval policy). | |
There was a problem hiding this comment.
Autonomous trust bypasses cell quorum
High Severity
The new multi-cell bans close auto and silence-merge so one cell cannot write into another without per-cell approval, but Autonomous still auto-merges and bypasses approval policy. A human in one cell can also raise a peer to Autonomous. That reopens the isolation back-door this line exists to close.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit 1163fa4. Configure here.
| - **Edit the document directly** (creates a `pact.proposal.merged` event with `ActorKind = Individual`) | ||
| - **Change an agent's trust level** (upgrades/downgrades autonomy) | ||
| - **Remove an agent** (force `agent.leave`) | ||
| - **Change approval policy** (e.g., switch from `majority` to `human-only`) |
There was a problem hiding this comment.
Human policy change evades cell bans
Medium Severity
Section 5 refuses any policy change that would put objection-based, auto, single, majority, or unanimous on a multi-cell fabric. Section 9.2 still lets a human change approval policy at any time, including toward those banned policies, so the fail-closed rule and the human-override list disagree.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 1163fa4. Configure here.
Adversarial spec review — verdict: FIX FIRSTIndependent review against the three change contracts (#53 / #54 / #55), stable What passed
FIX FIRST findingsF1 (blocking for sign-off): F2: the design record cited everywhere is not in the PR. F3: capability-gated enforcement contradicts the unconditional MUSTs. F4 (draft-polish, non-blocking): establish-time set declaration is circular. The participant cell set is "fixed at fabric establish" (1565) but is defined as the distinct Heads-up (not a PR #57 defect): branch Verdict: FIX FIRST — F1 (or at minimum flagging F1 as a promotion blocker), F2, and F3 before sign-off; F4 at leisure within the draft line. |
…enforcement, cell-set constitution (refs #53, refs #54, refs #55) Addresses PR #57 adversarial review findings F1, F3, F4: - F1: cell_id MUST be bound to proof of control of the cell DID — a cell membership attestation (signature over {fabric id, member DID, cell_id} verifiable against the cell DID) carried in join/_onboard, or a cell-scoped invite minted under the cell DID's authority. Host/initiator-minted invites are explicitly not cell-scoped. Unproven cell_id claims MUST be rejected, or at most bucketed as the joining operator's own attested cell — never the claimed one. Notes the residual limit (proof-of-control cannot prove distinct controllers; the cell set is verified out-of-band at establish). - F3: enforcement binds to behaviour, not the capabilities.cellQuorum flag — the Sec.5 MUSTs are unconditional; a server not implementing the multi-cell rules MUST reject multi-cell establishes and cell_id-bearing joins. README Extended claim and Sec.15.1 flag text re-cut to match; promotion-blocker list now names the attestation envelope, participant_cells field, and negative vectors. - F4: participant cell set constituted via participant_cells at establish, with first-join binding as the compatibility fallback; omitted-cell_id memberships form one implicit cell for every cardinality test ({no cell_id, X} is two cells); human approvals bucket by the membership through which they are recorded. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…g addendum (refs #53, refs #54, refs #55) Fixes PR #57 review F2: the design record cited by the spec/v2.3 banner, README, PR body and all three issues existed only untracked in a local checkout. Committed with a landing addendum recording: issue numbers as filed, single-PR landing vehicle, the Sec.25-is-v2.1-line correction, and the F1 proof-of-control hardening the landed text adds beyond the RFC (plus the OQ2 resolution). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Findings addressed in d8fa365 (spec fixes in 57d3b8a, design record in d8fa365).
The |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.
There are 4 total unresolved issues (including 2 from previous reviews).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit d8fa365. Configure here.
| - Proof of control binds a membership to a cell key; it cannot prove that two cell DIDs have **distinct controllers** — a single operator can mint any number of `did:key` cell DIDs. The participant cell set is therefore part of what the parties agree to at establish (next bullet): counterparties SHOULD verify out-of-band that each cell DID in the set is controlled by the party they believe controls it, exactly as they verify counterparty `principal_id`s today. `cell-quorum` (§5) is only as strong as this establishment-time verification. | ||
| - **Participant cell set constitution.** The participant cell set is declared at fabric establish via a **`participant_cells`** field on the establish document / fabric manifest — an array of cell DIDs. Every membership MUST declare (and prove) a `cell_id` present in that array. For fabrics established without `participant_cells` (single-cell operation and pre-v2.3 compatibility), the set is constituted at **first-join binding**: it becomes fixed at the moment the first membership bearing a proven `cell_id` distinct from the existing members' cell is accepted, and from that event on it is closed exactly as if it had been declared at establish. In either mode, every membership added later MUST declare a `cell_id` already in the set, and extending the set is a re-establishment of the fabric, not a membership operation. | ||
| - Implementations MUST NOT infer cell boundaries from DID method or DID contents. Two `did:key` agents in one cell is normal; the first four triggers above continue to operate independently of cells. | ||
| - **Omitted `cell_id` and cardinality.** All memberships that omit `cell_id` are bucketed together as one **implicit cell** — the establishing operator's cell. For every cardinality test in this line the implicit cell counts as exactly one cell: a fabric whose memberships are `{no cell_id, cell X}` has a participant cell set of cardinality 2, and every multi-cell rule applies to it. Once the set has cardinality > 1, a *new* `join` / `_onboard` without a `cell_id` is invalid and MUST be rejected; memberships accepted while the fabric was still single-cell retain their implicit-cell bucketing. On single-cell fabrics `cell_id` MAY be omitted (the fabric is then a one-cell fabric by construction and this line changes nothing for it). |
There was a problem hiding this comment.
Join-time cells skip policy bans
High Severity
first-join binding and an omitted-cell_id join can raise participant-cell cardinality above 1 after establish, but §5 only refuses a banned approval policy or conflict strategy at establish or later policy/strategy change. A fabric that started single-cell under objection-based, majority, or first-wins can become multi-cell with that policy still in force, so silence-merge or agent-count merge can still write across a new cell boundary.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit d8fa365. Configure here.
| - a **cell-scoped invite**: the invite token redeemed by the join (§15.6) was itself minted under the cell DID's authority and names that `cell_id`, so token redemption is the proof. Invite tokens minted by the fabric host or initiator are **not** cell-scoped and prove nothing about the joiner's cell. | ||
| - A `join` / `_onboard` whose `cell_id` carries no valid proof of control **MUST be rejected**. As the only permitted alternative, an implementation MAY accept the membership with the unproven claim **discarded** and the membership bucketed under a cell the joining operator itself demonstrably controls (its own attested cell, or the implicit cell below when the joiner is the establishing operator); it MUST NOT record or act on the unproven `cell_id`. In no case may an unproven claim place a membership in another party's approval bucket — the attack this rule closes is the initiator onboarding its own agents under the counterparty's `cell_id` to fill that cell's quorum bucket (or to keep a dark cell artificially "alive"). | ||
| - Proof of control binds a membership to a cell key; it cannot prove that two cell DIDs have **distinct controllers** — a single operator can mint any number of `did:key` cell DIDs. The participant cell set is therefore part of what the parties agree to at establish (next bullet): counterparties SHOULD verify out-of-band that each cell DID in the set is controlled by the party they believe controls it, exactly as they verify counterparty `principal_id`s today. `cell-quorum` (§5) is only as strong as this establishment-time verification. | ||
| - **Participant cell set constitution.** The participant cell set is declared at fabric establish via a **`participant_cells`** field on the establish document / fabric manifest — an array of cell DIDs. Every membership MUST declare (and prove) a `cell_id` present in that array. For fabrics established without `participant_cells` (single-cell operation and pre-v2.3 compatibility), the set is constituted at **first-join binding**: it becomes fixed at the moment the first membership bearing a proven `cell_id` distinct from the existing members' cell is accepted, and from that event on it is closed exactly as if it had been declared at establish. In either mode, every membership added later MUST declare a `cell_id` already in the set, and extending the set is a re-establishment of the fabric, not a membership operation. |
There was a problem hiding this comment.
Quorum set ignores first-join cells
High Severity
First-join binding now constitutes the participant cell set after establish, but cell-quorum still says the set used for bucketing and dark-cell fail-closed is the one fixed at establish. A cell added only at first-join can be left out of the denominator, so the new cell never has to approve and going dark does not block merge.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit d8fa365. Configure here.


DO NOT MERGE without Knox sign-off
This PR is normative spec text and per AGENTS.md rule 5 it MUST NOT merge without explicit maintainer sign-off. It opens the
spec/v2.3/DRAFT line and lands the three multi-cell fabric RFCs as one coherent change set — quorum without a silence back-door.Refs #53, refs #54, refs #55 (deliberately not
Closes— the issues stay open until sign-off + merge).What's here (one commit per RFC)
spec/v2.3/opened as a mechanical copy of stablespec/v2.2/(v2.0.3 body + §24 Matters), with a DRAFT / NOT-FOR-CITATION banner inREADME.mdand theSPECIFICATION.mdheader.spec/v2.2/is untouched (AGENTS.md rule 4).did:key); membership carriescell_id; participant cell set fixed at fabric establish. Differentcell_idon the same fabric ⇒ cross-organisation, so §17.6authorization_proofand the §17.9 Authorization-Required checks fire on the cross-cell wire. Cell boundaries are never inferred from DID method (twodid:keyagents in one cell is normal).cell-quorum+ policy bans. NewApprovalPolicy: ≥1 counted approval from each cell, bucketed bycell_idnot head-count; self-approval rule applies inside a cell; a dark cell fails closed (never shrinks the set);human-onlyon multi-cell = human-only AND cell-quorum. When participant cells > 1:objection-based/auto/single/majority/unanimousMUST NOT be configured — establish/policy-change refused, fail closed. §10.5 notes silence-merge is single-cell only; §15.1 gains thecellQuorumcapability flag.first-winsandmerge-bothMUST NOT be configured across cells;voteis not a substitute unless it is itself cell-quorum and still escalates when any cell is dark.Scope and non-collisions
spec/v2.3/only — frozenspec/v2.0/and stablespec/v2.2/are not edited; draftspec/v2.1/is not touched.docs/v2-prep/rfc-multi-cell-quorum.mdChanges 1–4.cell_idmembership schema field and multi-cell vectors are flagged inspec/v2.3/README.mdas required before promotion.Review order
#53 → #54 → #55 (each builds on the prior; #53 must land first or HAL never applies on the path #54/#55 protect).
Adversarial review response (F1–F4, addressed in d8fa365)
cell_idis no longer self-asserted: §15.4 now requires the claim to be bound to a proof of control of the cell DID — a cell membership attestation (signature over{fabric id, member DID, cell_id}verifiable against the cell DID) carried injoin/_onboard, or a cell-scoped invite minted under the cell DID's authority (host/initiator-minted invites explicitly prove nothing). Unproven claims MUST be rejected, or at most bucketed as the joining operator's own attested cell — never the claimed one — closing the bucket-stuffing and dead-cell-kept-"alive" attacks. The residual limit (one operator minting Ndid:keycell DIDs) is named in the text: proof-of-control cannot prove distinct controllers, so the cell set is verified out-of-band at establish, like counterpartyprincipal_ids. The attestation envelope +participant_cellsschema work and negative vectors are on the README promotion-blocker list.docs/v2-prep/rfc-multi-cell-quorum.mdis now committed on this branch, with a landing addendum recording issue numbers, the single-PR vehicle, the "§25 is v2.1-line" correction, and the F1 hardening the landed text adds beyond the RFC.cell_id-bearing joins. README Extended claim and the §15.1cellQuorumflag description re-cut to match (the flag advertises support only).participant_cellsestablish field, with first-join binding as the compatibility fallback; omitted-cell_idmemberships form one implicit cell for every cardinality test ({∅, X}is two cells; new no-cell_idjoins on a multi-cell fabric are rejected, pre-existing ones keep implicit-cell bucketing); human approvals bucket by the membership through which they are recorded (an approval carried by no membership satisfies no bucket).Branch
spec/consensus-not-signature(14bc68a) also scaffoldsspec/v2.3/(carrying §25). Whichever line promotes first wins the scaffold; the other MUST rebase onto it rather than bring its own copy — two independentspec/v2.3/directories will conflict file-by-file. Flagging here for whoever promotes first.🤖 Generated with Claude Code
Note
Cursor Bugbot is generating a summary for commit 1163fa4. Configure here.