Skip to content

feat(cli): P1e credential re-grain + aikey use two-axis summary + provider(s) mislabel fix#6

Open
damonleelcx wants to merge 11 commits into
develop-v1.0.4from
feature/model-mapping-egress-v1.0.4
Open

feat(cli): P1e credential re-grain + aikey use two-axis summary + provider(s) mislabel fix#6
damonleelcx wants to merge 11 commits into
develop-v1.0.4from
feature/model-mapping-egress-v1.0.4

Conversation

@damonleelcx

Copy link
Copy Markdown

aikey-cli — CLI credential re-grain + aikey use two-axis

P1e credential re-grain: managed_virtual_keys_cache becomes one row per (vk, protocol, provider) with ciphertext per row; idempotent in-place re-grain migration (preserves data, proven idempotent).

  • 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 activation summary (PROTOCOL + PROVIDER as separate columns) + fix the Select protocol(s)Select provider(s) mislabel (provider ≠ protocol).
  • aikey doctor / aikey test: model-mapping health surface reading /v1/diagnostics/pipeline (P3.5 tail WARN).
  • New: 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.4 feature (7 repos)

pkg · aikey-proxy · aikey-cli · aikey-config-tool · aikey-control · aikey-control-master (→ develop-v1.0.4) and roadmap20260320 (docs → main).

Verification status (honest)

  • Live GLM L2 mapping — client claude-opus-4-8 → mapped glm-4.6 → GLM served → restored (streaming too); all TestLive_* pass with the real key.
  • Request-chain attribution — verified live: provider=zhipu (declared anthropic → attributed zhipu), protocol=anthropic, base_url open.bigmodel.cn/api/anthropic, real message_id + token usage.
  • aikey use two-axis summary + provider(s) mislabel fix — verified live against the real binary (new tests/e2e_use_two_axis_live.rs); PROTOCOL and PROVIDER render as separate columns (anthropic | zhipu(GLM)).
  • ✅ All test suites green (proxy full, cli 1084 lib, dbmigrate fixture-matrix, both web bundles).
  • ⚠️ Not yet verified — P7 infra (Windows/阿里云 three-env, fault injection) is blocked; L1/L3/L4 live-DB + real-backend browser render remain stub-only.

🤖 Generated with Claude Code

Damon and others added 3 commits July 19, 2026 23:34
… 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>
@damonleelcx

Copy link
Copy Markdown
Author

⏳ Live verification status — aikey use two-axis (NOT exercised this run)

The 2026-07-20 Aliyun live run (branch feature/model-mapping-egress-v1.0.4) focused on the proxy egress + attribution path. The Rust CLI was not built/driven, so this PR's surface — aikey use <alias> [--provider] two-axis summary (protocol + provider columns) and the Select protocol(s) mislabel fix — was not independently verified live.

Honest status: pending. What the CLI two-axis depends on is verified upstream:

  • The server-side attribution the summary reflects — provider zhipu, protocol anthropic, base_url open.bigmodel.cn/api/anthropic — is confirmed by real GLM requests (see the aikey-proxy PR: requested_model=claude-opus-4-8, effective=glm-4.6, provider=zhipu).

To close this: build aikey on a Linux/Windows box and run tests/e2e_use_two_axis_live.rs (seed team-VK cache rows → aikey use glm-team → assert the summary shows anthropic + zhipu(GLM) and no Select protocol(s) mislabel), plus the PTY multi-provider selector. Instances were torn down after the run; happy to stand this up in a follow-up.

Damon and others added 7 commits July 20, 2026 08:41
… 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>
…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>
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.

1 participant