feat(cli): cotal provision-acl + spawn provision the full durable-delivery footprint - #4
feat(cli): cotal provision-acl + spawn provision the full durable-delivery footprint#4mattwilkinsonn wants to merge 7 commits into
Conversation
…emon live Add `cotal provision-acl`: a re-runnable privileged pass that writes the durable read-ACL row (`cotal_acl_<space>`) for every persona that already has creds, closing the `cotal mint` + `exec omp` gap where an agent had creds but no ACL row and was @mention-wake-blind. Derives each row to match the creds' baked read scope (drift is fail-loud skipped, never rewritten); a credless persona is skipped (this command never mints). Gate `cotal spawn`'s durable membership on a live delivery daemon (lease probe) instead of hardcoding live-only; grant the provisioner cred the delivery-lease read verb. Mint stays byte-identical. Co-Authored-By: seal <noreply@sealedsecurity.com>
This stack of pull requests is managed by Graphite. Learn more about stacking. |
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 7 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the 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 configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughAdds durable read-ACL provisioning with a new CLI command and planner, daemon-gated durable membership in spawn, updated provisioner lease read access, and a live smoke test wired into CI. ChangesDurable ACL Provisioning
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant User as CLI User
participant CLI as provisionAcl
participant Lib as acl-provision
participant NATS as NATS/JetStream
User->>CLI: cotal provision-acl --space ...
CLI->>Lib: planAclProvision(root, space)
Lib-->>CLI: per-persona plan
alt dry-run
CLI-->>User: print plan and exit
else live
CLI->>Lib: provisionAcls(root, space, server, auth)
Lib->>NATS: start endpoint with provisioner creds
Lib->>NATS: create dm_<id> and dlv_<id>
Lib->>NATS: commit ACL row and read back
Lib-->>CLI: provisioned/skipped result
CLI-->>User: print summary
end
sequenceDiagram
participant Spawn as cotal spawn
participant Prov as short-lived provisioner
participant KV as delivery lease KV
participant Core as provisionAgent
Spawn->>Prov: start provisioner endpoint
Prov->>KV: readDeliveryLease(0)
KV-->>Prov: lease present or absent
Prov-->>Spawn: daemonLive boolean
Spawn->>Core: provisionAgent(durableMembership = daemonLive)
Core-->>Spawn: ACL row written or skipped
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
Greptile SummaryThis PR adds durable-delivery provisioning for personas and foreground spawns. The main changes are:
Confidence Score: 5/5This looks safe to merge.
Important Files Changed
Reviews (4): Last reviewed commit: "docs(cli): document the dry-run prune di..." | Re-trigger Greptile |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7bd7574f84
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
implementations/cli/src/lib/acl-provision.ts (1)
30-38: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy liftTriplicated business logic — drift risk despite the safety net.
agentReadAcldeliberately re-implements theallowSubscribe ?? subscribe+["general"]fallback logic that already lives inmint.ts:84andprovision.ts:411(per the comment). The runtime drift check mitigates silent divergence, but a shared helper in@cotal-ai/corewould remove the need to keep three call sites "in lockstep" by convention/comment alone.🤖 Prompt for 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. In `@implementations/cli/src/lib/acl-provision.ts` around lines 30 - 38, The ACL selection logic in agentReadAcl is duplicated and should be centralized to avoid drift across mint and provision paths. Move the shared allowSubscribe ?? subscribe plus ["general"] fallback into a reusable helper in `@cotal-ai/core`, then have agentReadAcl and the existing mint/provision call sites use that helper instead of re-implementing the same logic. Keep the helper aligned with the current semantics of allowSubscribe, subscribe, and empty-list handling.
🤖 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 `@implementations/cli/src/commands/provision-acl.ts`:
- Around line 24-31: The CLI flag handling in provision-acl parsing is missing
support for --mint-missing, causing parseArgs to reject a documented option.
Update the options map in the parseArgs call inside provision-acl.ts to include
mint-missing (or remove the flag from the CLI registration/help in index.ts if
it is not meant to be supported), and make sure the existing command flow in
provision-acl uses the new parsed value consistently if needed.
In `@implementations/cli/src/index.ts`:
- Around line 151-158: The `provision-acl` command help text is advertising the
unimplemented `--mint-missing` flag, which causes `parseArgs` to reject it with
a crash. Update the command definition in `index.ts` so the `summary` for
`provisionAcl` no longer mentions `--mint-missing`, and keep the documented
options aligned with what `provision-acl.ts` actually supports (`--dry-run` and
`--space` only).
In `@implementations/cli/src/lib/acl-provision.ts`:
- Around line 67-94: `planAclProvision()` currently lets `idFromCreds(creds)`
and `credChatSubAllow(creds, space)` throw, which aborts the whole ACL plan on
one bad `.creds` file. Add per-persona error handling in the same loop that
already handles `p.error`: catch failures around reading/parsing creds, then
push an entry for that persona with `hasCreds` false or an `error` field and
continue to the next persona. Keep the existing `planAclProvision`,
`idFromCreds`, and `credChatSubAllow` flow intact, but make malformed creds a
skipped persona instead of a fatal error.
---
Nitpick comments:
In `@implementations/cli/src/lib/acl-provision.ts`:
- Around line 30-38: The ACL selection logic in agentReadAcl is duplicated and
should be centralized to avoid drift across mint and provision paths. Move the
shared allowSubscribe ?? subscribe plus ["general"] fallback into a reusable
helper in `@cotal-ai/core`, then have agentReadAcl and the existing mint/provision
call sites use that helper instead of re-implementing the same logic. Keep the
helper aligned with the current semantics of allowSubscribe, subscribe, and
empty-list handling.
🪄 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: 97c9a916-aa2e-49a6-b2f9-845b2e6904ab
📒 Files selected for processing (7)
implementations/cli/smoke/provision-acl-live.smoke.tsimplementations/cli/src/commands/provision-acl.tsimplementations/cli/src/commands/spawn.tsimplementations/cli/src/index.tsimplementations/cli/src/lib/acl-provision.tspackage.jsonpackages/core/src/provision.ts
There was a problem hiding this comment.
All reported issues were addressed across 7 files
Heads up: you’re close to your included review allowance. Set a flex budget so reviews don’t pause.
Re-trigger cubic
- planAclProvision: isolate a bad/unreadable creds file as a per-persona error entry instead of throwing and aborting the whole catalog pass (a later valid persona would otherwise be left @mention-wake-blind). - provisionAcls: compare read-back ACL as a sorted set, not order-sensitive. - provision-acl: scan target.root (the resolved mesh) not cwd, so --space / out-of-checkout invocations provision the right catalog. - index: drop stale [--mint-missing] from the provision-acl summary (the command never parsed it). - smoke: replace Promise.withResolvers (Node 20 compat, repo engines >=20), move server setup inside try/finally, add nats-server spawn error listener. Co-Authored-By: seal <noreply@sealedsecurity.com>
Adds smoke case #9: a persona whose on-disk creds file is corrupt is isolated as an 'unreadable creds' skip entry while a valid sibling is still provisioned — the catalog pass completes instead of throwing. 4 of the 6 assertions flip red when the planAclProvision try/catch guard is removed, proving the regression teeth. Co-Authored-By: seal <noreply@sealedsecurity.com>
There was a problem hiding this comment.
All reported issues were addressed across 4 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
Fix all with cubic | Re-trigger cubic
Codex flagged (and mercator reproduced live this session) that provision-acl authorized the owner but never created the agent's bind-only dlv_<id> DELIVER durable — the @mention-wake path the daemon fans out to. The agent is denied CONSUMER.CREATE on DLV, so only a provisioner can make it, and pumpDlv silently no-ops when it is absent. A `cotal mint` + `exec omp` agent (this command's target) has neither the ACL row nor the dm/dlv durables, so committing only the row left its wake messages piling undrained. provisionAcls now mirrors provisionAgent's footprint: provisionDmInbox(id) + provisionDlvInbox(id) alongside commitAcl. All three are idempotent, so the re-runnable contract holds and a spawn-provisioned agent's existing durables are untouched. The provisioner cred already grants DM/DLV CONSUMER.CREATE. 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>
cubic flagged (PR #4) that --dry-run planned against cwd cotalRoot() + the raw --space flag, while the live path resolves the target and uses target.root/ target.space. A --space or out-of-checkout invocation resolves a registered mesh whose root differs from cwd, so the dry-run preview could show a different persona/drift/credless set than the command actually provisions. Add resolveTargetNoConnectOrExit — an offline sibling of resolveTargetOrExit that resolves the target from the registry with the same one-sentence error render but no connect and no prune (an offline preview must not mutate the registry). The --dry-run path now plans against the resolved target.root/space, so the preview is faithful to the live run. Co-Authored-By: seal <noreply@sealedsecurity.com>
… the ACL row Regression test for the delivery-footprint fix (f40ab37): a #10 block asserts that after provisionAcls, the persona's bind-only dlv_<id> DELIVER and dm_<id> DM durable consumers EXIST (via the provisioner cred's CONSUMER.INFO on DM/DLV), and that a second run is a no-op (idempotent re-create). Red-green proven: dropping the two provisionDmInbox/provisionDlvInbox calls fails all three #10 checks (got: undefined); restoring passes. 21 passed, 0 failed (was 18). 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>
There was a problem hiding this comment.
All reported issues were addressed across 4 files (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
cubic (PR #4) noted resolveTargetNoConnectOrExit's comment claimed --dry-run previews the SAME catalog as the live run, but the live path calls pruneStaleMeshes() first (an online reachability probe that mutates the registry) and the offline preview cannot — so a since-dead registered mesh may still be resolved by --dry-run where the live run would prune it and fall back. Pruning is inherently online + mutating, so it can't run in an offline, side-effect-free preview. Corrected the JSDoc to document this one deliberate divergence (the preview errs toward the recorded target; the live run reconciles) rather than overclaim parity, and pointed the call-site comment at it. Comment-only. 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>

Problem
Durable @mention-wake delivery to a dormant agent needs two pieces of registry/JetStream state, and two launch paths leave both unwritten:
cotal_acl_<space>— the delivery daemon authorizes fan-out against it (absent row ⇒ DEFER, never deliver).dlv_<id>DELIVER durable (anddm_<id>for DMs); the agent binds+pumps it (endpoint.tspumpDlv) but is deniedCONSUMER.CREATEon DLV — only a provisioner may createdlv_<id>.pumpDlvsilently no-ops when the durable is absent, so even a correct ACL row delivers nothing if the durable was never created.The two launch paths:
cotal mintwrites creds but is offline (noCotalEndpoint/connectat all), so an agent brought up viacotal mint+exec omphas creds but neither the ACL row nor the durables — it's @mention-wake-blind until something provisions the full footprint.cotal spawnhard-codeddurableMembership: false, so every foreground-spawned agent stayed live-only even when a delivery daemon was serving.Change
Implements the option-agnostic correctness core that honors the already-frozen ACL model in the coordination-structure / service-owner records. The mint-strategy decision (best-effort-in-mint vs
cotal upauto-hook vs flag) remains OPEN on design PR #3 — this PR implements only the mechanism common to all three branches; the branch selection is still Matt's on #3.cotal provision-acl(new command) — a re-runnable, privileged pass that provisions the full durable-delivery footprint for every persona that already has creds: the bind-onlydm_<id>+dlv_<id>mailboxes and the read-ACL row (exactly whatprovisionAgentwrites for a spawned agent). It derives each agent's read ACL to match what its creds were minted with (so durable read scope never diverges from live).--dry-runprints the plan offline; a credless persona is skipped (this command never mints — minting iscotal mint's job, and mint-if-absent is the open docs(platform): durable-delivery ACL provisioning design #3 fork); a persona whose creds' baked read scope diverges from its file ACL is fail-loud skipped with a re-mint hint, never silently rewritten. Every write is idempotent (durable create-if-absent +commitAclatomic CAS put), so the command is re-runnable.implementations/cli/src/lib/acl-provision.ts—planAclProvision(offline plan) +provisionAcls(connect, then per personaprovisionDmInbox+provisionDlvInbox+commitAcl, with ACL read-back verification). The inbox-provisioning is consolidated into the singleprovisionAclsroutine alongside the ACL write, so the whole footprint is one privileged pass.implementations/cli/src/commands/provision-acl.ts, registered inindex.ts.cotal spawndaemon-gate — replaces the unconditionaldurableMembership: falsewith a shard-0 delivery-lease probe (readDeliveryLease(0) !== undefined): daemon live ⇒ provision the durable membership; absent ⇒ stay live-only (absent-row = live-onlyis the registry's load-bearing semantic). Matches design PR docs(platform): durable-delivery ACL provisioning design #3's Task 3.cotal join's bare console stays intentionally live-only.provisionerPermissionsgainsSTREAM.MSG.GETon the delivery bucket so the spawn/provision provisioner cred cankv.getthe lease. (DM/DLVCONSUMER.CREATEwas already granted —provision.ts— so the new durable-create calls need no further grant.)Mint is byte-identical — untouched.
provision-aclreplicates mint's read-ACL derivation inline (allowSubscribe ?? subscribe, then?.length ? it : ["general"]) rather than refactoring a shared helper, so there's no mint diff and no front-running the #3 fork.Verification
Typecheck + full workspace build clean. Red-green smoke tests (real
nats-server, isolated port) defend: the full footprint is created (dm_<id>+dlv_<id>durables exist after a run, not just the ACL row), ACL row written + reads back, creds-parity, drift-skip-not-rewrite, credless-skip,["general"]default, idempotency (re-run is a no-op), bad-creds isolation (one corrupt persona never aborts its siblings), and the spawn daemon-gate decision (lease present ⇒ durable membership, absent ⇒ live-only). Verified passing locally against a live broker.Co-Authored-By: seal noreply@sealedsecurity.com