Skip to content

fix(cli): cotal mint reuses the existing identity unless --force#7

Open
sealedsecurity-bot wants to merge 4 commits into
mainfrom
cotal-mint-reuse-identity
Open

fix(cli): cotal mint reuses the existing identity unless --force#7
sealedsecurity-bot wants to merge 4 commits into
mainfrom
cotal-mint-reuse-identity

Conversation

@sealedsecurity-bot

@sealedsecurity-bot sealedsecurity-bot commented Jul 8, 2026

Copy link
Copy Markdown

Problem

cotal mint <name> called newIdentity() unconditionally on every invocation, so re-minting an agent rotated its mesh id. Because the durable ACL row and the per-agent dm/dlv delivery durables are all keyed by the nkey public key, rotation orphaned them: the re-minted agent came up with a fresh id the broker had no ACL row or consumer for, and went @mention-wake-blind (consumer not found) until re-provisioned. This reproduced live 6× during a fleet relaunch.

Re-minting is the normal way to refresh an agent's channels (mint's read/post ACLs are read from the persona file), so an operation that's supposed to be routine was silently breaking delivery.

Fix (option B — maintainer ruling on design PR #3, cubic P1)

Re-mint reuses the same identity by default:

  • packages/core/src/identity.ts — new identityFromCreds(creds: string): Identity, the id-preserving sibling of idFromCreds. It returns { id, seed } from a creds file, reusing idFromCreds for the id + JWT-subject cross-check (no duplicated parse, no new inline cast) and extracting the seed block.
  • implementations/cli/src/commands/mint.ts — the agent path now computes the out creds path before minting. If a creds file already exists there and --force is not passed, it re-signs that same id+seed with the fresh ACLs from the persona file; otherwise it mints a new identity. --force keeps the rotation escape hatch (compromised key / deliberate new identity). The output line now reports (reused — re-mint kept the identity) vs (new).

Review refinements (agent-only + fail-loud)

  • Reuse is gated to profile === "agent" — observer/admin creds always rotate (they carry no persona-refresh workflow or durable footprint, and silently extending a privileged admin key's lifetime would be surprising).
  • A present-but-unparseable creds file at the out path fails loud with an actionable error naming --force, rather than crashing on a raw parse exception or silently rotating (which would orphan the durable row the existing id may still own).

Closes the #3 cubic-P1

This directly resolves the re-mint-orphans-the-ACL-row P1 that cubic raised on design PR #3 (durable-delivery-acl-provisioning.md) — it is cubic's option (b) verbatim, per Matt's ruling. One fix retires both: #3's design note that re-mint orphans are "out of scope" no longer applies, because the canonical cotal up + cotal mint recipe is now safe under re-mint.

Scope

Orthogonal to the still-open mint-strategy fork in #3: this changes which id mint uses, not where the ACL write triggers. Pairs with the PR #4 DLV-durable fix (that one makes fresh mints land their durables; this one stops re-mints from needing to).

Verification

  • @cotal-ai/core build + core/cli typecheck clean.
  • Offline round-trip smoke: newIdentity()mintCredsidentityFromCreds returns the same {id, seed}; identityFromCreds(creds).id === idFromCreds(creds); re-mint of a reused identity stays STABLE.
  • Live CLI smoke via mint(): agent re-mint keeps the id; admin + observer re-mint each rotate; a corrupt creds file at the out path errors naming --force (no raw crash, no silent rotate).
  • Red-green regression (double-mint-same-id must fail on the pre-fix unconditional newIdentity() and pass after) is being added on this branch.

Closes the #3 cubic-P1 (re-mint orphans ACL row). Refs #3.

mint.ts unconditionally called newIdentity() on every mint, so re-minting an
agent (e.g. to refresh its channels from the persona file) rotated the mesh id.
The durable ACL row and the dm/dlv delivery durables are all keyed by the nkey
public key, so rotation orphaned them → the agent went @mention-wake-blind with
'consumer not found' until re-provisioned (hit live 6x during a fleet relaunch).

Option B (maintainer ruling on design PR #3, cubic P1): re-mint reuses the same
identity. New core helper identityFromCreds(creds): Identity — the id-preserving
sibling of idFromCreds (which it reuses for the id + JWT-subject cross-check),
returning { id, seed } from the creds' seed block. mint computes the out path
before minting; if a creds file exists there and --force is not passed, it
re-signs that SAME id+seed with fresh ACLs; otherwise it mints a new identity.
--force keeps the rotation escape hatch (compromised key / deliberate new id).

Orthogonal to the still-open mint-strategy fork (#3): changes WHICH id mint uses,
not WHERE the ACL write triggers. Pairs with the PR #4 DLV-durable fix.

Refs #3.

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

@seal-agent, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 35 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: f0ad882a-d010-4d77-86aa-0565015f3614

📥 Commits

Reviewing files that changed from the base of the PR and between 4161ddd and 994e85c.

📒 Files selected for processing (3)
  • implementations/cli/smoke/mint-reuse.smoke.ts
  • package.json
  • packages/core/smoke/identity.smoke.ts
📝 Walkthrough

Walkthrough

The mint CLI now reuses an identity from an existing output creds file for the agent profile when --force is not set, otherwise it creates a fresh identity. A new core helper parses creds into an Identity for reuse, and the CLI now reports whether the minted id was reused or new.

Changes

Mint identity reuse

Layer / File(s) Summary
identityFromCreds helper
packages/core/src/identity.ts
New exported function extracts the NKEY seed block from creds, throws if missing, and returns an Identity with id from idFromCreds and trimmed seed.
Mint command conditional identity reuse
implementations/cli/src/commands/mint.ts
Adds creds-file reuse logic for agent outputs when not forcing, falls back to newIdentity otherwise, updates the --force comment, and appends reuse status to the printed id.

Estimated code review effort: 2 (Simple) | ~10 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CLI as mint command
  participant FS as Filesystem
  participant Core as identity.ts

  CLI->>FS: check if creds file exists at output path
  alt creds exist and not --force
    CLI->>FS: readFileSync(existing creds)
    CLI->>Core: identityFromCreds(creds)
    Core-->>CLI: reused Identity {id, seed}
  else no creds or --force
    CLI->>Core: newIdentity()
    Core-->>CLI: new Identity
  end
  CLI->>CLI: mint creds with selected identity
  CLI->>CLI: log id (reused vs new)
Loading

Poem

A rabbit hops with creds in paw,
Reuse the seed, obey the law.
If force appears, a fresh one shines,
If not, the old id neatly aligns.
Hop, mint, log — all crisp and bright 🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
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.
Title check ✅ Passed The title clearly summarizes the main change: mint now reuses an existing identity unless --force is used.
Description check ✅ Passed The description accurately explains the bug, the agent-only reuse fix, the --force behavior, and the new helper.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cotal-mint-reuse-identity

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@greptile-apps

greptile-apps Bot commented Jul 8, 2026

Copy link
Copy Markdown

Greptile Summary

This PR makes agent re-minting keep the same identity by default.

  • Adds identityFromCreds() to recover an id and seed from existing creds.
  • Reuses existing agent creds unless --force asks for rotation.
  • Keeps observer and admin re-mints on the fresh-identity path.
  • Fails with an actionable error when existing agent creds cannot be parsed.
  • Adds smoke coverage for identity round-trips and mint reuse.

Confidence Score: 5/5

This looks safe to merge.

  • No blocking issues found in the changed code.

Important Files Changed

Filename Overview
implementations/cli/src/commands/mint.ts Adds agent-only identity reuse, forced rotation, and parse-error handling for existing creds.
packages/core/src/identity.ts Adds a creds reader that returns the existing identity while keeping the seed and JWT subject checks.
implementations/cli/smoke/mint-reuse.smoke.ts Adds CLI smoke coverage for agent reuse, forced rotation, ACL refresh, non-agent rotation, and corrupt creds.
packages/core/smoke/identity.smoke.ts Adds smoke coverage for identity round-trip, reader consistency, spliced creds, and missing seed handling.
package.json Adds the new identity and mint-reuse smokes to the CI smoke script chain.

Reviews (4): Last reviewed commit: "test(cli): assert corrupt-creds mint lea..." | Re-trigger Greptile

Comment thread implementations/cli/src/commands/mint.ts Outdated
Comment thread implementations/cli/src/commands/mint.ts 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 2 files

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 implementations/cli/src/commands/mint.ts Outdated
Comment thread implementations/cli/src/commands/mint.ts Outdated
…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>
Comment thread implementations/cli/src/commands/mint.ts
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>

@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 3 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

Comment thread implementations/cli/smoke/mint-reuse.smoke.ts Outdated
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>
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