Skip to content

docs(platform): durable-delivery ACL provisioning design - #3

Open
mattwilkinsonn wants to merge 7 commits into
mainfrom
cotal-durable-acl-design
Open

docs(platform): durable-delivery ACL provisioning design#3
mattwilkinsonn wants to merge 7 commits into
mainfrom
cotal-durable-acl-design

Conversation

@mattwilkinsonn

@mattwilkinsonn mattwilkinsonn commented Jul 8, 2026

Copy link
Copy Markdown

Design record (design only, no implementation) for the durable-delivery ACL provisioning gap: the cotal_acl_<space> read-ACL row is the keystone the delivery daemon re-authorizes durable @mention-wake deliveries against (absent ⇒ DEFER, never deliver), but neither of the launch paths agents actually use writes it — cotal mint is offline creds-only, and cotal spawn hard-codes durableMembership: false. Live evidence: 0 rows across 7 connected agents; an out-of-band commitAcl backfill self-healed 0→8 durable memberships with no reconnect, proving the row is the whole gate.

The record captures the 3-way fork (mint-online / bring-up-owned provisioning / mint --commit-acl flag) and recommends (ii): one idempotent TS routine provisioning ACL rows for every persona-dir agent, auto-run at the tail of cotal up and exposed as a re-runnable cotal provision-acl, keeping mint offline-pure — plus the completeness half: spawn switches to provision-when-daemon-live (delivery-lease probe) and its stale live-only rationale is rewritten. Plan carries global constraints (privileged writes only, row == minted allowSubscribe, atomic commitAcl CAS, TS-only, red→green against a live nats-server) and per-task interfaces; five open questions are batched for the freeze gate.

Co-Authored-By: seal noreply@sealedsecurity.com

Design record for closing the cotal_acl_<space> provisioning gap: recommends bring-up-owned ACL provisioning (cotal up / provision-acl) plus spawn's provision-when-daemon-live completeness fix.

Co-Authored-By: seal <noreply@sealedsecurity.com>
@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 34 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d2015bdc-051d-4b75-8f34-fdfb94561e7d

📥 Commits

Reviewing files that changed from the base of the PR and between fe18f5e and 757d270.

📒 Files selected for processing (1)
  • docs/designs/platform/durable-delivery-acl-provisioning.md
📝 Walkthrough

Walkthrough

Adds a design document describing durable-delivery ACL provisioning, recommending provisioning during cotal up, changing spawn behavior to depend on daemon liveness, and outlining implementation tasks, validation, and open questions.

Changes

Design Document

Layer / File(s) Summary
Problem statement and evidence
docs/designs/platform/durable-delivery-acl-provisioning.md
Defines the durable ACL registry gate, explains the missing-row behavior in current launch paths, and records live evidence of wake-blind durable memberships.
Proposed approach: cotal up provisioning and spawn changes
docs/designs/platform/durable-delivery-acl-provisioning.md
Compares provisioning approaches, recommends an idempotent cotal provision-acl path folded into cotal up, and updates spawn semantics to probe daemon liveness.
Task 1: shared policy helper
docs/designs/platform/durable-delivery-acl-provisioning.md
Specifies extracting a shared read-policy helper from mint and verifying parity with existing policy derivation.
Task 2–4: provisioning, spawn, and validation
docs/designs/platform/durable-delivery-acl-provisioning.md
Outlines provisionAcls, the cotal provision-acl command, the cotal up hook, spawn permission changes, end-to-end validation, checklist items, and remaining questions.

Estimated code review effort: 1 (Trivial) | ~5 minutes

Poem

I nibble at docs by moonlit light,
Where ACL rows now feel just right.
cotal up hops in, the burrows align,
And dormant dashes wake up fine.
🐇✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the design document about durable-delivery ACL provisioning.
Description check ✅ Passed The description directly matches the design-only ACL provisioning changes described in the pull request.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cotal-durable-acl-design

Comment @coderabbitai help to get the list of available commands.

Copy link
Copy Markdown
Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@seal-agent
seal-agent marked this pull request as ready for review July 8, 2026 19:52
@greptile-apps

greptile-apps Bot commented Jul 8, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds a design record for fixing the durable-delivery ACL provisioning gap — the missing cotal_acl_<space> KV row that leaves every agent minted via cotal mint or cotal spawn permanently wake-blind. The design went through an adversarial critic pass (SEA-1188) and surfaced four load-bearing open questions that are explicitly freeze-blocked.

  • Core finding captured: the commitAcl write is unreachable from both common launch paths (mint is offline-only; spawn hard-codes durableMembership: false), confirmed by 0 rows across 7 live agents and a self-healing backfill.
  • Recommended fix (option ii): a new idempotent provisionAcls routine auto-run at the tail of cotal up and exposed as cotal provision-acl, with four concrete tasks (shared read-policy helper, provisioning routine, spawn daemon-liveness probe, e2e smoke + doc updates).
  • Freeze status: the document's own Open Questions section declares a hard freeze-block on four unresolved load-bearing questions including SEA-1168 (CLI-layer vs core fix), which the Recommendation callout also marks as "record NOT frozen." Merging while these are open is in tension with the preamble's "frozen contract" language and may mislead implementers about the binding status of the Task 1–4 plan.

Confidence Score: 4/5

The PR adds a design-only document with no code changes, so there is no runtime risk; however the document explicitly states it is not ready to be treated as a binding contract.

The design record is thorough and the motivating analysis is well-grounded, but the preamble calls this a frozen contract while the body explicitly marks it unfrozen and declares a merge freeze-block until four open questions are resolved.

docs/designs/platform/durable-delivery-acl-provisioning.md — the preamble's frozen contract claim needs to be reconciled with the explicit NOT frozen status and freeze-block in the Open Questions section.

Important Files Changed

Filename Overview
docs/designs/platform/durable-delivery-acl-provisioning.md New 424-line design record for durable-delivery ACL provisioning gap. Contains an explicit freeze-block with four unresolved load-bearing open questions, yet the PR merges it as a "frozen contract" — a self-contradiction that could mislead implementers. Evidence numbers in the Problem section are internally inconsistent without explanation.

Reviews (7): Last reviewed commit: "docs(platform): fold design-critic pass ..." | Re-trigger Greptile

Comment thread docs/designs/platform/durable-delivery-acl-provisioning.md
Comment thread docs/designs/platform/durable-delivery-acl-provisioning.md
Comment thread docs/designs/platform/durable-delivery-acl-provisioning.md Outdated

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/designs/platform/durable-delivery-acl-provisioning.md`:
- Around line 94-99: Treat ACL provisioning as a required startup step, not a
log-and-continue task: in the `cotal up` flow after `postStart`, make the
idempotent ACL routine fail the bring-up when provisioning does not succeed, or
explicitly mark the process/status unhealthy instead of returning success. Keep
`cotal provision-acl` as the re-run entrypoint, but ensure the same underlying
routine used by `up` and the standalone command propagates failure so the broker
cannot appear healthy while ACLs are missing.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c530814d-f815-4f11-8678-f82ab5591be5

📥 Commits

Reviewing files that changed from the base of the PR and between 4b0553c and 59fd3e0.

📒 Files selected for processing (1)
  • docs/designs/platform/durable-delivery-acl-provisioning.md

Comment thread docs/designs/platform/durable-delivery-acl-provisioning.md Outdated

@cubic-dev-ai cubic-dev-ai Bot 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.

All reported issues were addressed across 1 file

Heads up: you’re close to your included review allowance. Set a flex budget so reviews don’t pause.

Fix all with cubic | Re-trigger cubic

Comment thread docs/designs/platform/durable-delivery-acl-provisioning.md Outdated
Comment thread docs/designs/platform/durable-delivery-acl-provisioning.md Outdated
Comment thread docs/designs/platform/durable-delivery-acl-provisioning.md
Comment thread docs/designs/platform/durable-delivery-acl-provisioning.md Outdated
mattwilkinsonn and others added 2 commits July 8, 2026 17:48
Drop the ephemeral "this session" anchoring (flagged by greptile P2 + cubic P3)
so the durable design record is self-contained: the mint.ts zero-match search
and the 0->8 self-heal evidence read as standing facts, not session-scoped
observations.

Co-Authored-By: seal <noreply@sealedsecurity.com>
Two freeze-prep clarifications from PR #3 review (mercator's supervisor rulings;
substantive design forks stay open for Matt):

- Soft-fail posture: mark the cotal-up auto-provision hook as the one deliberate,
  scoped exception to the 'No fallbacks' constraint (up orchestrates many agents;
  re-runnable; mirrors up.ts:284-285), with the standalone command staying
  hard-fail. Cross-referenced both sections so the spec isn't ambiguous
  (cubic P2, greptile P2).

- 'Row is the whole gate' caveat: true only for the spawn/manager-provisioned
  population (the 7-agent evidence set already held dm/dlv durables); a
  mint+exec-omp agent has neither, so the provisioning step writes the full
  footprint (dm+dlv+acl). Corroborated by Codex on PR #4 and cubic's creds-only
  finding here; the DLV fix landed on PR #4 (f40ab37).

Co-Authored-By: seal <noreply@sealedsecurity.com>
…aveat

PR #3 re-review internal-consistency fixes (greptile 4/5 + cubic P2, both
bot-only):

- Task 2 step 3 said commitAcl-only, contradicting the scope caveat (a
  mint+exec-omp agent needs dm_<id> + dlv_<id> + the ACL row). Expand it to the
  full durable footprint (provisionDmInbox + provisionDlvInbox + commitAcl), all
  idempotent — matching what provisionAgent writes and what the DLV fix landed on
  PR #4 (f40ab37). Interfaces 'produces' line updated to match.

- The fork's option (ii) claimed the standalone command covers a persona-file-less
  out-of-band agent 'via its creds file', but both paths enumerate listPersonas
  (agent-files only, no creds/ scan). Corrected to state it honestly as a known,
  deferred gap rather than false coverage.

Co-Authored-By: seal <noreply@sealedsecurity.com>

@cubic-dev-ai cubic-dev-ai Bot 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.

All reported issues were addressed across 1 file (changes from recent commits).

Heads up: you’re close to your included review allowance. Set a flex budget so reviews don’t pause.

Tip: Review your code locally with the cubic CLI to iterate faster.

Fix all with cubic | Re-trigger cubic

Comment thread docs/designs/platform/durable-delivery-acl-provisioning.md
Comment thread docs/designs/platform/durable-delivery-acl-provisioning.md Outdated
… assertions

Two review-accuracy fixes on the frozen contract (cubic on #3):

- Step 3 said 'exactly what provisionAgent writes for a spawned agent', but a
  bare cotal spawn passes durableMembership:false and skips the ACL row
  (provision.ts:272). Clarify it's the durableMembership:true footprint, not the
  live-only path — so an implementer doesn't assume the row is already written.

- Task 2 test cycle asserted only the ACL row (readAcl); a row-only impl would
  pass while still leaving dm_<id>/dlv_<id> absent → pumpDlv no-ops → still
  wake-blind. Add explicit dm/dlv durable-existence assertions (the checks that
  actually guard the incomplete-footprint bug the design identifies).

Refs #3.

Co-Authored-By: seal <noreply@sealedsecurity.com>
seal-agent added a commit that referenced this pull request Jul 8, 2026
…rseable creds

Two review findings on the reuse logic (cubic P1 + greptile P2 on #7):

- Reuse now requires profile === "agent". The durable-ACL-orphan rationale is
  agent-specific (persona-refresh workflow + dm/dlv durables keyed to the id);
  observer/admin creds have neither, and silently preserving a privileged admin
  key across re-mints would extend its lifetime unexpectedly. Observer/admin now
  always rotate, as before.

- A present-but-unparseable creds file (empty, truncated, not a user creds file)
  now fails loud with an actionable error naming --force, instead of letting
  identityFromCreds throw a raw parse exception. Silently rotating there would
  orphan the durable row the existing id may still own, so fresh-mint is not a
  safe fallback — the operator must opt in via --force or remove the stale file.

Verified: agent re-mint keeps id; admin/observer re-mint rotate; corrupt creds
at the out path errors naming --force (no raw crash, no silent rotate).

Refs #3.

Co-Authored-By: seal <noreply@sealedsecurity.com>
seal-agent added a commit that referenced this pull request Jul 8, 2026
Layer 1 — packages/core/smoke/identity.smoke.ts (offline, 4 asserts): identityFromCreds
round-trips {id, seed} unchanged; agrees with idFromCreds (one-id-everywhere); rejects
spliced creds (seed vs foreign JWT subject) and a missing seed block.

Layer 2 — implementations/cli/smoke/mint-reuse.smoke.ts (hermetic, exercises the real
mint() against a tmp .cotal root, 8 checks): re-minting an agent reuses the SAME id;
--force rotates; a persona ACL change refreshes the baked sub.allow WITHOUT rotating the
id; first mint of a new name mints fresh (absent-creds path, no crash); observer + admin
re-mint rotate (reuse is agent-only); an unparseable existing creds file fails loud naming
--force (no silent rotate, no raw crash).

Red-green verified: reverting the reuse block to unconditional newIdentity() fails checks
1, 3, and 6 (two mints → two ids; the exact #3 cubic-P1 durable-orphan bug); the fix turns
them green. Registered both as smoke:identity + smoke:mint-reuse and wired into smoke:ci.

Refs #3.

Co-Authored-By: seal <noreply@sealedsecurity.com>
seal-agent added a commit that referenced this pull request Jul 8, 2026
cubic P2 on #7: the corrupt-creds check only asserted the error message, so a
future mint() refactor that wrote fresh creds *before* surfacing the parse error
would still pass while silently rotating the id — the exact regression the test
guards. Add a filesystem-state assertion: after the failed mint, the corrupt
file must be byte-unchanged (still empty), proving no silent fresh-mint.

Refs #3.

Co-Authored-By: seal <noreply@sealedsecurity.com>
seal-agent added a commit that referenced this pull request Jul 9, 2026
cubic P3 on #8: the ESC regression asserted the inbox drains on the next clean
turn-end, but FakeHost never modeled the held nextTurn content actually being
delivered into a turn — so it proved held-AND-acked, not held-AND-delivered (the
drain came purely from the connector's surfaced bookkeeping). A future regression
where the hidden queue never redelivers would still pass.

Model OMP's deferred continuation (#promptQueuedHiddenNextTurnMessages,
agent-session.ts:7323-7346) with FakeHost.flushHeld(): it drains the hidden
next-turn queue into consumed turn content, exactly as a clean redelivery does.
Test 9 now flushes the held message into a real turn and asserts it was consumed
BEFORE the clean turn-end acks it — so the ack is proven to follow an actual
delivery, closing cubic's gap and mirroring the real deliver-then-ack ordering.

Refs #3.

Co-Authored-By: seal <noreply@sealedsecurity.com>
seal-agent added a commit that referenced this pull request Jul 9, 2026
…sage

Capture the coalescing invariant at the ack site (greptile P1 on #8): a future
reader touching ackSurfaced must see WHY a nextTurn message parked during an ESC
unwind can't be acked before it's consumed. OMP holds the wire-level agent_end
while #promptInFlightCount > 0 and lets a later one supersede it
(agent-session.ts:2787-2799), so a wire-level subscriber sees one agent_end at
the true settle — the interrupted turn and the deferred continuation collapse into
a single post-consume event. Backstop: ackSurfaced drains only front-matching ids,
so an unconsumed survivor redelivers (fails safe). Comment only, no behavior change.

Refs #3.

Co-Authored-By: seal <noreply@sealedsecurity.com>
Matt's calls (asked directly): spawn auto-detect via delivery lease (D1),
cotal up mint-if-absent (D2) — folded from OQ3/OQ4 to Decisions. OQ2/OQ5
fold as explicit non-load-bearing deferrals (D3/D4).

The (ii)-vs-core fork is NOT picked: Matt ruled it a core Cotal issue to
deliberate before a CLI-layer patch. Filed SEA-1168. Open Questions now
carries one explicit FREEZE BLOCKER pointing at it — record must not
merge-freeze until SEA-1168 decides CLI-layer vs core fix.

Refs SEA-1168

Co-Authored-By: seal <noreply@sealedsecurity.com>
Adversarial read-only critic pass (SEA-1188). Two clear improvements folded,
three code-grounded forks surfaced as load-bearing Open Questions that compound
the existing SEA-1168 freeze block. D1/D2 ratification text left untouched;
their challenges surfaced as OQs for Matt's ruling.

Forks (now OQ#2/#3/#4, blocking freeze):
- OQ#2: enumerate creds/*.creds, not persona files. listPersonas scans .md only
  (personas.ts:33), but the target recipe `cotal mint --profile agent` writes
  only creds (mint.ts:88-92), so every plain-minted agent is invisible to both
  provisioning paths and stays wake-blind. The earlier "edge case" deferral is
  false against the code; it is the primary path.
- OQ#3 (reopens D1): lease-conditional row-write strands spawn agents wake-blind
  on a daemon-restart race. The short-lived provisioner (prov.stop right after,
  spawn.ts:257) never backfills; no self-heal path exists. Alternative: always
  write (DEFER-inert, accretion already accepted by D4) or explicit --live-only.
- OQ#4 (reopens D2): up-minting orphans the id-keyed footprint on every normal
  bring-up (fresh id per mint; operator's later `cotal mint` mints a different
  id, orphaning up's). Alternative: decouple identity-minting from bring-up.

Folded improvements:
- F4: the up soft-fail is not analogous to the daemon soft-fail (daemon self-
  heals via reconcile, provisioning does not) -> make residual blindness
  observable/actionable, not just logged.
- F5: derive the committed ACL from the decoded JWT sub.allow (single source)
  and add a wildcard round-trip test; the shared helper alone doesn't close the
  creds-exists cross-matcher seam.
- Task 3 grant corrected: STREAM.INFO on the delivery bucket is already granted
  via streamSetup; only MSG.GET was missing (record over-specified).

Co-Authored-By: seal <noreply@sealedsecurity.com>
@sealedsecurity-bot

Copy link
Copy Markdown

Design-critic pass (SEA-1188) — folded, 3 freeze-blocking forks for your call

Ran the mandated adversarial critic pass before freeze (read-only, grounded against the core + CLI source on this branch). Pushed the fold in 757d270. This record was already freeze-blocked on SEA-1168 (OQ#1); the critic added 3 code-grounded forks that compound that block. Two reopen your ratified Decisions — I left D1/D2's text exactly as you ratified them and surfaced the challenges as OQs rather than rewriting:

  • OQ#2 — enumerate creds/*.creds, not persona files. The Approach keys provisioning on listPersonas (.cotal/agents/*.md), but the target recipe cotal mint <name> --profile agent writes only a creds file (mint.ts:88-92), no persona file. So every plain-minted agent is invisible to both provisioning paths → wake-blind: the exact bug this design fixes. The earlier "edge case" deferral is false against the code — it's the primary path. Fix: enumerate creds/*.creds (idFromCreds already yields the id; the JWT carries the read set). Materially changes Task 2.
  • OQ#3 — reopens D1 (lease-conditional row-write). Conditioning a persistent privileged row on a transient lease strands a spawn agent permanently wake-blind if its probe races a daemon restart: the short-lived provisioner (prov.stop() right after, spawn.ts:257) never backfills, and nothing self-heals. Alternative: always-write (DEFER-inert, and D4 already accepts row accretion) or an explicit --live-only flag.
  • OQ#4 — reopens D2 (up mints creds). Every cotal mint makes a fresh id (mint.ts:88), and the footprint is id-keyed — so on your own recipe (up + per-agent cotal mint), up provisions id A and the later mint makes id B, orphaning A's dm_/dlv_/ACL on every bring-up (D4's "edge case" becomes the common path), and up gains a secret-writing side effect. Alternative: decouple identity-minting from bring-up.

Folded without needing you:

  • F4: the up soft-fail isn't analogous to the daemon soft-fail (daemon self-heals via reconcile; provisioning doesn't) → make residual blindness observable/actionable, not just logged.
  • F5: derive the committed ACL from the decoded JWT sub.allow (single source) + a wildcard round-trip test — the shared helper alone doesn't close the creds-exists cross-matcher seam (channelInAllow names vs chatSubject subjects). assertValidChannel already forecloses aliasing, so this is defense-in-depth.
  • Task 3 grant over-spec corrected: STREAM.INFO on the delivery bucket is already granted via streamSetup; only MSG.GET was missing.

Confirmed TRUE against source: listPersonas never scans creds/; agents denied CONSUMER.CREATE on DM/DLV (only a provisioner creates the durables); mint.ts is offline-only (zero CotalEndpoint/connect); the delivery-lease read grant was missing. Survived: the Task-2 full-footprint requirement, readDeliveryLease(0) (N=1 is the only shipped mode), the smoke-harness reuse.

Net: nothing here changes that SEA-1168 gates the freeze — but when it rules "CLI layer," OQ#2–#4 need answering too. All three are on the same single freeze gate.

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