Support per-target bearers in pi-provider #36

Closed
opened 2026-09-01 19:47:23 +01:00 by vnprc-agent · 4 comments

Teach pi-provider to give a single target its own bearer for a provider, so an operator can adopt the per-target credentials that allod/secrets issue #16 makes expressible without hand-editing a registry or an Age ciphertext.

Primary goals:

  • Add a target with its own bearer — one command puts a machine on an existing provider and prompts for a bearer only that machine will hold, instead of extending a shared credential's target list.
  • Migrate a shared credential — a documented, resumable path from one credential fanned out across several targets to one credential per target, since the operation needs every bearer in hand at once.
  • Keep the ciphertext narrow — each per-target ciphertext is encrypted to that machine's host keys and the hypervisor's, and the tool refuses to widen a recipient set it did not intend.
  • Preserve the existing shapes — a genuinely shared service credential stays a first-class arrangement; per-target is an option, not a replacement.

Current state

retarget is the only command that changes which machines reach a provider, and it is whole-credential by construction: scripts/pi-provider:119 documents --target as a replacement list, the retarget branch plans an encrypted token for every token of the credential, and prompt_and_encrypt at :1185 takes one hidden prompt per planned token before writing anything. collect_recipients at :1086 then reads one recipient set for the credential and asserts every token shares it, which is exactly right while a credential has one bearer for all its targets and has no meaning once each target has its own.

So there is currently no way to say "add this machine, with this bearer, leave the others alone". Extending the target list re-encrypts the one shared ciphertext to a wider recipient set, which is the behaviour the linked contract issue exists to make optional.

Ordering

This cannot be designed before allod/secrets issue #16 settles the registry shape, because the command surface follows from it: whether a target carries a credential reference, and whether a provider's credentials are addressed by target, determines whether this is a new flag on an existing command or a new command. Filed now so the tool half is tracked rather than discovered late; do not start it while the contract shape is open.

Scope

In scope: the command surface for per-target bearers, the recipient assertions that keep each ciphertext narrow, the migration path for an existing shared credential, and the dry-run output that shows an operator exactly which machines gain and lose access before anything is written.

Out of scope: the contract change itself, tracked in allod/secrets issue #16. Also out of scope: any change to how a shared credential behaves for deployments that keep using one.

Teach `pi-provider` to give a single target its own bearer for a provider, so an operator can adopt the per-target credentials that `allod/secrets` issue #16 makes expressible without hand-editing a registry or an Age ciphertext. Primary goals: - **Add a target with its own bearer** — one command puts a machine on an existing provider and prompts for a bearer only that machine will hold, instead of extending a shared credential's target list. - **Migrate a shared credential** — a documented, resumable path from one credential fanned out across several targets to one credential per target, since the operation needs every bearer in hand at once. - **Keep the ciphertext narrow** — each per-target ciphertext is encrypted to that machine's host keys and the hypervisor's, and the tool refuses to widen a recipient set it did not intend. - **Preserve the existing shapes** — a genuinely shared service credential stays a first-class arrangement; per-target is an option, not a replacement. ### Current state `retarget` is the only command that changes which machines reach a provider, and it is whole-credential by construction: `scripts/pi-provider:119` documents `--target` as a replacement list, the retarget branch plans an encrypted token for every token of the credential, and `prompt_and_encrypt` at `:1185` takes one hidden prompt per planned token before writing anything. `collect_recipients` at `:1086` then reads one recipient set for the credential and asserts every token shares it, which is exactly right while a credential has one bearer for all its targets and has no meaning once each target has its own. So there is currently no way to say "add this machine, with this bearer, leave the others alone". Extending the target list re-encrypts the one shared ciphertext to a wider recipient set, which is the behaviour the linked contract issue exists to make optional. ### Ordering This cannot be designed before `allod/secrets` issue #16 settles the registry shape, because the command surface follows from it: whether a target carries a credential reference, and whether a provider's credentials are addressed by target, determines whether this is a new flag on an existing command or a new command. Filed now so the tool half is tracked rather than discovered late; do not start it while the contract shape is open. ### Scope In scope: the command surface for per-target bearers, the recipient assertions that keep each ciphertext narrow, the migration path for an existing shared credential, and the dry-run output that shows an operator exactly which machines gain and lose access before anything is written. Out of scope: the contract change itself, tracked in `allod/secrets` issue #16. Also out of scope: any change to how a shared credential behaves for deployments that keep using one.
Author

Delivered — branch agent/per-provider-target-cli at 97c172c, one commit on master 44c18dc, built in a worktree on nix-dev, unpushed. Nothing merged; the relay hands the commit to this repo.

What lands, against this issue's primary goals:

  • pi-provider target claim <credential> <dev-vm> <name> — adds a target with a bearer only it holds: one hidden prompt, one new ciphertext encrypted to that machine's keys plus the hypervisor, existing ciphertexts and recipient sets untouched.
  • pi-provider target rotate <credential> <dev-vm> <name> — moves a claim to a fresh named token; the new ciphertext lands before the registry repoints, and the old one is retired once nothing claims or defaults to it. The all-claimed/default-claimed edge reassigns the deployment default in the same pass (dry-run prints Deployment default moves: <old> -> <new>).
  • A pre-write assertion refuses to land if any ciphertext this operation did not name changes recipients — the "refuse to widen a set it did not intend" guarantee; narrowness itself stays the contract's job (lib.piCredentialRecipients), not duplicated in bash.
  • The ported registry validation now matches the landed contract clause for clause, including per-(provider, target) uniqueness; provider-scoped commands fail loud, naming the split, on the ambiguous case.
  • retarget, token remove, and discovery handle mixed plain/claimed credentials; shared-credential behavior is unchanged.
  • Migration path documented in docs/credentials.md — composed, resumable, with its one known limitation tracked in allod/nexus#83 (the last hop to zero shared targets).

Validation: bash tests/pi-provider.sh — 387 assertions, exit 0 (the pre-change suite was 336; every pre-existing case still passes, two fixture expectations updated where they encoded per-target-impossible behavior). One cold cross-family review pass (codex gpt-5.6-sol) found three should-fix defects — the schema port's stale global-uniqueness rule, a raw-object comparison that locked add/discovery out of mixed credentials, and an undeliverable rotate-on-claimed-default — all reproduced, fixed in the same commit, plus comment trims.

Handoff — from nexus:

allod patch receive nix-dev:~/wt-nexus ~/work/allod/nexus

(wt-nexus is a symlink on nix-dev to the worktree; the registry-id form alone would read the main checkout's HEAD.) Then push agent/per-provider-target-cli and open the PR against master: suggested body — Closes allod/nexus#36 and Closes allod/secrets#16 (per that issue's scope note, the PR landing the tool closes the contract issue), Refs allod/nexus#83. ## Risk R3 (secret custody, cross-repo tool surface); ## Validation the suite line and review summary above.

**Delivered** — branch `agent/per-provider-target-cli` at `97c172c`, one commit on master `44c18dc`, built in a worktree on nix-dev, unpushed. Nothing merged; the relay hands the commit to this repo. What lands, against this issue's primary goals: - `pi-provider target claim <credential> <dev-vm> <name>` — adds a target with a bearer only it holds: one hidden prompt, one new ciphertext encrypted to that machine's keys plus the hypervisor, existing ciphertexts and recipient sets untouched. - `pi-provider target rotate <credential> <dev-vm> <name>` — moves a claim to a fresh named token; the new ciphertext lands before the registry repoints, and the old one is retired once nothing claims or defaults to it. The all-claimed/default-claimed edge reassigns the deployment default in the same pass (dry-run prints `Deployment default moves: <old> -> <new>`). - A pre-write assertion refuses to land if any ciphertext this operation did not name changes recipients — the "refuse to widen a set it did not intend" guarantee; narrowness itself stays the contract's job (`lib.piCredentialRecipients`), not duplicated in bash. - The ported registry validation now matches the landed contract clause for clause, including per-(provider, target) uniqueness; provider-scoped commands fail loud, naming the split, on the ambiguous case. - `retarget`, `token remove`, and discovery handle mixed plain/claimed credentials; shared-credential behavior is unchanged. - Migration path documented in `docs/credentials.md` — composed, resumable, with its one known limitation tracked in allod/nexus#83 (the last hop to zero shared targets). Validation: `bash tests/pi-provider.sh` — 387 assertions, exit 0 (the pre-change suite was 336; every pre-existing case still passes, two fixture expectations updated where they encoded per-target-impossible behavior). One cold cross-family review pass (codex `gpt-5.6-sol`) found three should-fix defects — the schema port's stale global-uniqueness rule, a raw-object comparison that locked `add`/discovery out of mixed credentials, and an undeliverable rotate-on-claimed-default — all reproduced, fixed in the same commit, plus comment trims. **Handoff** — from nexus: ``` allod patch receive nix-dev:~/wt-nexus ~/work/allod/nexus ``` (`wt-nexus` is a symlink on nix-dev to the worktree; the registry-id form alone would read the main checkout's HEAD.) Then push `agent/per-provider-target-cli` and open the PR against master: suggested body — `Closes allod/nexus#36` and `Closes allod/secrets#16` (per that issue's scope note, the PR landing the tool closes the contract issue), `Refs allod/nexus#83`. `## Risk` R3 (secret custody, cross-repo tool surface); `## Validation` the suite line and review summary above.
Author

Two owner-ruled changes to the record above, landed before relay — the branch is now 3186172 (still one commit, still unpushed):

  1. Surface renamed. The CLI is now provider-keyed verbs, no target noun group: pi-provider grant <provider> <machine> [token-name] (token named after the machine by default) and pi-provider rotate <provider> <machine> [new-token-name] (no new name: rotate in place; with one: rename-and-retire, default moving with the claim). Read grant/rotate wherever the record above says target claim/target rotate. Suite now 410 assertions, exit 0, overseer-run on 3186172.
  2. Commit message cut to a subject and three sentences.

Receive line, PR guidance, risk and the review history are unchanged. The suggested PR body's Validation line should read: bash tests/pi-provider.js... bash tests/pi-provider.sh, 410 assertions, exit 0; one cold codex review round plus a rename round, overseer-verified.

Two owner-ruled changes to the record above, landed before relay — the branch is now `3186172` (still one commit, still unpushed): 1. **Surface renamed.** The CLI is now provider-keyed verbs, no `target` noun group: `pi-provider grant <provider> <machine> [token-name]` (token named after the machine by default) and `pi-provider rotate <provider> <machine> [new-token-name]` (no new name: rotate in place; with one: rename-and-retire, default moving with the claim). Read `grant`/`rotate` wherever the record above says `target claim`/`target rotate`. Suite now 410 assertions, exit 0, overseer-run on `3186172`. 2. **Commit message cut to a subject and three sentences.** Receive line, PR guidance, risk and the review history are unchanged. The suggested PR body's Validation line should read: `bash tests/pi-provider.js`... `bash tests/pi-provider.sh`, 410 assertions, exit 0; one cold codex review round plus a rename round, overseer-verified.
Author

One more owner pass before relay, docs-only: the prose in docs/credentials.md now uses one vocabulary — grant/rotate as the acts, shared token/claimed token as the states (defined once; claimed survives only because the tool's error strings use it). Final branch state: 8330432, one commit, still unpushed. Receive line and PR guidance unchanged; suite 410 assertions, overseer-run.

One more owner pass before relay, docs-only: the prose in `docs/credentials.md` now uses one vocabulary — `grant`/`rotate` as the acts, `shared token`/`claimed token` as the states (defined once; `claimed` survives only because the tool's error strings use it). Final branch state: `8330432`, one commit, still unpushed. Receive line and PR guidance unchanged; suite 410 assertions, overseer-run.
Member

Landed as allod/nexus#95 from allod-dev: the delivered commit relayed by patch artifact, rebased onto master past allod/nexus#94 (one seam fix: master's classify_add called provider_credential with one argument; and add --models-file now runs on a split provider, with a test), suite 443 assertions, provisioning-contract check green, one more cold codex round. The PR carries Closes allod/secrets#16.

Landed as allod/nexus#95 from allod-dev: the delivered commit relayed by patch artifact, rebased onto master past allod/nexus#94 (one seam fix: master's `classify_add` called `provider_credential` with one argument; and `add --models-file` now runs on a split provider, with a test), suite 443 assertions, provisioning-contract check green, one more cold codex round. The PR carries `Closes allod/secrets#16`.
vnprc closed this issue 2026-10-21 22:53:01 +01:00
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
allod/nexus#36
No description provided.