feat(cli): P1e credential re-grain + aikey use two-axis summary + provider(s) mislabel fix#6
feat(cli): P1e credential re-grain + aikey use two-axis summary + provider(s) mislabel fix#6damonleelcx wants to merge 11 commits into
aikey use two-axis summary + provider(s) mislabel fix#6Conversation
… per-binding sync, aikey use two-axis Model-mapping-egress CLI (targets develop-v1.0.4): - P1e: managed_virtual_keys_cache -> one row per (vk,protocol,provider) with ciphertext per row; idempotent in-place re-grain migration - per-binding change-password re-encrypt (fixes cross-binding ciphertext clobber; corruption fence proven red) - per-binding sync: apply_snapshot preserve/prune + DeliveryPayload.binding_for pulls each binding's own material - aikey use: two-axis summary (protocol + provider) + fix 'Select protocol(s)' mislabel (provider != protocol) - aikey doctor/test: model-mapping health surface reading /v1/diagnostics/pipeline (P3.5) - fences: change_password_reencrypts_each_binding_independently, regrain_migration_preserves_data_and_is_idempotent Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…slabel fix Drives the REAL compiled aikey binary against a real seeded vault.db (team VK written via the same storage::upsert_virtual_key_cache the member-rail sync uses). Closes the one verification gap on e9fa37f — the two-axis activation summary and the "Select protocol(s)"->"Select provider(s)" mislabel fix were previously covered only by lib/unit tests, never exercised end-to-end. - Live A: `aikey use glm-team` renders PROTOCOL and PROVIDER as SEPARATE columns (anthropic | zhipu(GLM)) — asserts the axes never re-collapse. - Live B: `aikey use multi-team` under a PTY renders the multi-provider prompt as "Select provider(s) to set as Primary" — asserts the fixed label and the absence of the old "Select protocol(s)" wording. No live platform backend required (P7 infra blocked); the cache row is seeded locally through the real sync write path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…g collapse `aikey list` showed a single column mislabeled PROTOCOLS that actually held providers, and — after the P1e per-binding cache re-grain — rendered a team VK with N bindings as N duplicate alias rows. - Add a PROVIDER column (`code(alias)`, e.g. `zhipu(GLM)`) and fix the PROTOCOL column to show the wire protocol (same endpoint-truth as the vault two-axis read model + `aikey use`). - Group managed cache rows by virtual_key_id so a team VK is ONE row; each multi-valued axis renders `primary (+N more)` (primary = the active-binding value, else the first). Team count now counts distinct VKs. - Status: post-P1e ciphertext is per-binding, so "delivered" = ANY binding has local material. - JSON: managed_keys now include protocol_type + base_url. - New live e2e `tests/e2e_list_two_axis_live.rs` drives the real binary against a seeded multi-binding VK and asserts one grouped row + the `(+N more)` collapse. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
⏳ Live verification status —
|
… parity)
Removes the loose contains("-{role}") arm (kept exact/prefix/suffix/infix),
documents the fixed-order KNOWN_ROLES determinism + the ASCII case-fold parity
note with Go. Adds role_of_model_anchored + resolve_model_anchored_roles vectors
(incl. the claude-haikuish-1 negative) via a #[cfg(test)] from_yaml_str ctor.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…el-mapping-egress-v1.0.4
…se fence develop-v1.0.4 enforces `make fmt-check` (rustfmt clean: 0 violations there). This branch had 44 violations across 7 tracked files, so release.sh Step "CLI fmt tests" failed and no cluster bundle was produced. Formatting only — no logic change; build + provider_fingerprint parity (24) + both two-axis live e2e tests still pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`make lint` (= cargo clippy -D warnings, enforced by develop-v1.0.4) failed with 2 derivable_impls errors on RegistryProvenanceWire / MappingHealthWire — the manual Default impls produce exactly what derive(Default) does. Replaced with #[derive(Default)] and dropped the hand-written impls. No behavior change; clippy + fmt clean, parity (24) + both two-axis live e2e still pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…corrupted
On a machine with no vault, `aikey key sync` (and `aikey use` / the web-session
path, which share the same helper) died with:
Error: Salt not found in vault. Vault may be corrupted.
right after walking the user through "No vault found — setting up for the first
time", setting a master password, confirming it, and choosing a session backend.
Nothing was corrupted: the vault had never been created, so the message sent
users hunting for damaged data — or deleting a vault — over a fresh install.
Cause: two ways to reach a vault key had drifted apart.
- `executor::derive_vault_key` → `VaultContext::new`, which auto-initializes.
- `commands_account::derive_vault_key` (a private duplicate) read
`storage::get_salt()` straight away.
The first-run password prompt (`prompt_vault_password_fresh`'s no-vault branch)
only COLLECTS a password — it never writes the vault — so whichever path ran
first had to bootstrap. The duplicate didn't, and every fresh install that hit
`key sync` before any VaultContext-based command got the corruption error.
`AIKEY_MASTER_PASSWORD` doesn't help: it unlocks, it does not initialize.
Fix: extract the bootstrap out of `VaultContext::new` into
`executor::ensure_vault_initialized` (idempotent; no-op once master_salt
exists) and call it from both paths. One definition of "first run" — a second
inline salt-generation is exactly how this drifted the first time.
Fences (both verified red without the fix):
- derive_vault_key_bootstraps_a_brand_new_vault — from a dir with NO vault,
deriving must succeed and persist master_salt.
- bootstrap_is_idempotent_and_never_resalts — a second call must not re-salt;
re-salting would change the derived key and orphan every existing entry.
Verified on the real binary against staging master2 (v1.0.1-alpha.8): fresh
HOME → `key sync` now prints "Sync complete", login completes, and the
two-axis output works end to end —
PROTOCOL PROVIDER KEY
anthropic anthropic(claude) → key-admin-anthropic-2providers [team]
anthropic zhipu(GLM) → key-admin-anthropic-2providers [team]
`make fmt-check` + `make lint` (the release.sh gates) clean; full lib suite
1091 passed / 0 failed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…abeller) Follow-up to the two-axis work. `aikey list` printed the PROVIDER column as `zhipu(GLM)` but USING FOR — the SAME axis — as a bare `zhipu`, and the `aikey use` title / `key sync` auto-activate lines did the same. Worth stating plainly, because my first read of this was wrong: those bare lists were NOT mixing the two axes. `ProviderBinding.provider_code` is the provider axis throughout, so `USING FOR: anthropic,zhipu` was two PROVIDERS, and "Primary for zhipu" is accurate. The defect is that Anthropic's provider code is spelled exactly like the anthropic wire protocol, so an unlabelled list of providers reads as though a protocol had been mixed in — which is precisely how I misread it. Labelling the axis consistently removes the ambiguity without changing any semantics. - New `provider_registry::display_label(code)` — canonical `code(alias)`, bare code when unknown/alias-less, empty stays empty. The alias never replaces the code (the code is what bindings/events/config speak). - Five render sites now share it: `aikey list` PROVIDER + USING FOR columns, `aikey use` summary rows + title, `key sync` auto-activate lines. Two of them carried private copies of the same match expression; those are gone. Fences: aliased code keeps its code, unknown code falls back to bare (custom providers must never blank a cell), empty stays empty (a "()" would materialize a bogus cell). Verified on the real binary against staging: USING FOR now reads `anthropic(claude),zhipu(GLM)` and the use title `Primary for zhipu(GLM)`. fmt + lint clean; lib suite 1094 passed / 0 failed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ng vault ACL
Three defects from Windows real-machine verification.
1. `aikey use` printed a PROVIDER in its PROTOCOL column. `resolve_binding_protocol`
fell back to `provider_registry::family_of()` whenever the entry had no stored
base_url — every key made by `aikey add --provider x` without `--base-url` — so
the column read `zhipu` / `openai`, and disagreed with `aikey list`, which has
a third step: the provider's own route row. Both now call one
`provider_fingerprint::protocol_for()`; it resolves endpoint → declared →
provider route, and returns empty rather than ever naming the other axis.
The summary's PROTOCOL column was also a fixed `{:<12}`, too narrow for
`openai_compatible` (17 chars) — both axis columns are auto-width now.
2. The interactive multi-provider picker printed bare codes while the title, the
table and `aikey list` all said `zhipu(GLM)`. Both call sites now use the
shared labeller, so the list you choose FROM and the confirmation you get BACK
spell the provider the same way.
3. `enforce_owner_only` ran `icacls /inheritance:r` FIRST and granted after. On a
path with no explicit ACEs yet that leaves an EMPTY DACL, so any failure in
the grant loop left a file nobody — SYSTEM included — could open or delete.
Running as a service, USERNAME is the machine account (`HOST$`), its grant
fails, and vault.db was left 0 bytes with an empty DACL: that vault path was
then permanently poisoned, every later run dying at "unable to open database
file" unable even to remove it. Reproduced on Windows; recovery needed
`takeown`.
Grants now come first and `/inheritance:r` last, so a failure leaves the path
still-inherited and recoverable instead of unreachable. A current-user grant
that can't resolve is a warning, not a fatal — SYSTEM and Administrators are
already granted and SYSTEM is the identity such a process runs as. Vault init
also removes a 0-byte vault.db (never a real SQLite DB) and cleans up a
half-created file on any error in the init path.
`aikey add` as LocalSystem now succeeds where it used to abort.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
aikey-cli — CLI credential re-grain +
aikey usetwo-axisP1e credential re-grain:
managed_virtual_keys_cachebecomes one row per(vk, protocol, provider)with ciphertext per row; idempotent in-place re-grain migration (preserves data, proven idempotent).apply_snapshotpreserve/prune;DeliveryPayload.binding_forpulls each binding's own material.aikey use: two-axis activation summary (PROTOCOL + PROVIDER as separate columns) + fix theSelect protocol(s)→Select provider(s)mislabel (provider ≠ protocol).aikey doctor/aikey test: model-mapping health surface reading/v1/diagnostics/pipeline(P3.5 tail WARN).tests/e2e_use_two_axis_live.rs— drives the real binary against a seeded vault to lock both the two-axis summary and the mislabel fix.Part of the
model-mapping-egress-v1.0.4feature (7 repos)pkg·aikey-proxy·aikey-cli·aikey-config-tool·aikey-control·aikey-control-master(→develop-v1.0.4) androadmap20260320(docs →main).Verification status (honest)
claude-opus-4-8→ mappedglm-4.6→ GLM served → restored (streaming too); allTestLive_*pass with the real key.zhipu(declared anthropic → attributed zhipu), protocol=anthropic, base_urlopen.bigmodel.cn/api/anthropic, real message_id + token usage.aikey usetwo-axis summary +provider(s)mislabel fix — verified live against the real binary (newtests/e2e_use_two_axis_live.rs); PROTOCOL and PROVIDER render as separate columns (anthropic | zhipu(GLM)).🤖 Generated with Claude Code