Skip to content

fix(manager): a key's new name is a name, and the field is where the key is - #243

Merged
stormer78 merged 1 commit into
mainfrom
fix/key-rename-name-rule
Sep 14, 2026
Merged

stormer78 merged 1 commit into
mainfrom
fix/key-rename-name-rule

Conversation

@stormer78

Copy link
Copy Markdown
Contributor

Renaming a key in the console could not succeed. The field opened pre-filled with the key's current id, which for every DID-bound key is a DID URL — and keys/rename will not take one. The agent's gate is vti_common::identifier::validate_identifier: [A-Za-z0-9._-], at most 64 bytes, because a key id lands as a store key and a caller who can inject : collides with an adjacent keyspace. The only thing pressing Save could produce was:

keys/rename/0.1 failed: malformed request: validation error: new_key_id is 86 bytes; maximum is 64

…with nothing on screen saying what would have worked. The specification cannot help: keys/rename/0.1 declares newKeyId as a non-empty string and leaves the shape to the agent, so the narrower rule is the agent's and this console has to know it.

The rule, applied before the send

key-name.ts is that rule, phrased for the person typing — a pre-flight, not the decision. The agent still answers and its refusal still renders; nothing here may widen the rule, because a value this accepts and the agent refuses is a message the operator can act on, while the reverse is a name the agent would have taken and the console pretended it would not.

A DID-bound key opens with an empty field and a derived name as its placeholdervdr-host-key-0 from did:webvh:QmRqx…:webvh.storm.ws:vdr-host#key-0. A placeholder rather than a value on purpose: a pre-filled suggestion is one press away from a rename nobody chose, and this rename is a one-way door. The id a DID document addresses the key by contains : and #, so the agent cannot be given it back, and the editor says so rather than leaving it to be discovered.

And the field is under the key now

It rendered inside the actions column, the narrowest in the table, which showed about eight characters of an 86-byte value. Table already has expanded for exactly this — a detail rendered in the flow beneath the row it belongs to — so the editor goes there, full width, with focus moved into the field and Escape closing it.

Tests

keys-pane.render.test.mts is rendered rather than unit, because both defects are ones a person sees and a model does not: the type checker was happy with a field that could only fail, and key-name.ts's own tests would pass with the editor still drawing in a 190px column.

manager-form-state.test.mts's prop-seed detector only matched a prop read as the whole useState argument, so useState(nameable ? record.keyId : "") read as a component with no state at all. Widened to find the prop anywhere in the call — it flags no existing component, which is the point: the narrow match was missing seeds, not tolerating them.

Noted, not fixed here

rooms-create.tsx gains a note on something found while confirming the above: for a templated mint the key record's id and the DID document's verification method disagree by one — room / room-host number VMs from #key-1, the create path saves records from #key-0 — so the document's #key-1 names the signing key and the keystore's names the x25519 one. That is a VTA-side fix and is being opened against verifiable-trust-infrastructure. The offer in rooms-create.tsx is by {did}# prefix and keyType !== "x25519", never by fragment number, so it is unaffected.

Checks

npm test — 620 extension tests pass (1338 across the workspace); npm run build clean.

…key is

Renaming a key in the console could not succeed. The field opened pre-filled
with the key's current id, which for every DID-bound key is a DID URL — and
`keys/rename` will not take one. The agent's gate is
`vti_common::identifier::validate_identifier`: `[A-Za-z0-9._-]`, at most 64
bytes, because a key id lands as a store key and a caller who can inject `:`
collides with an adjacent keyspace. So the only thing pressing Save could
produce was

    keys/rename/0.1 failed: malformed request: validation error:
    new_key_id is 86 bytes; maximum is 64

with nothing on screen saying what would have worked. The specification is no
help here and cannot be: `keys/rename/0.1` declares `newKeyId` as a non-empty
string and leaves the shape to the agent, so the narrower rule is the agent's
and this console has to know it.

## The rule, applied before the send

`key-name.ts` is the agent's rule, phrased for the person typing — a
**pre-flight, not the decision**. The agent still answers and its refusal still
renders; nothing here may widen the rule, because a value this accepts and the
agent refuses is a message the operator can act on, while the reverse is a name
the agent would have taken and the console pretended it would not.

A DID-bound key now opens with an **empty** field and a derived name as its
*placeholder* — `vdr-host-key-0` from
`did:webvh:QmRqx…:webvh.storm.ws:vdr-host#key-0`. A placeholder rather than a
value on purpose: a pre-filled suggestion is one press away from a rename
nobody chose, and this rename is a one-way door. The id a DID document
addresses the key by contains `:` and `#`, so the agent cannot be given it
back, and the editor says so rather than leaving it to be discovered.

## And the field is under the key now

It rendered inside the actions column, the narrowest in the table, which showed
about eight characters of an 86-byte value. `Table` already has `expanded` for
exactly this — a detail rendered in the flow beneath the row it belongs to —
so the editor goes there, full width, with focus moved into the field and
Escape closing it.

## Tests

`keys-pane.render.test.mts` is rendered rather than unit, because both defects
are ones a person sees and a model does not: the type checker was happy with a
field that could only fail, and `key-name.ts`'s own tests would pass with the
editor still drawing in a 190px column.

`manager-form-state.test.mts`'s prop-seed detector only matched a prop read as
the whole `useState` argument, so `useState(nameable ? record.keyId : "")` read
as a component with no state at all. Widened to find the prop anywhere in the
call — it flags no existing component, which is the point: the narrow match was
missing seeds, not tolerating them.

`rooms-create.tsx` gains a note on something found while confirming the above
and reported upstream: for a templated mint the key record's id and the DID
document's verification method disagree by one (`room`/`room-host` number VMs
from `#key-1`; the create path saves records from `#key-0`), so the document's
`#key-1` names the signing key and the keystore's names the x25519 one. The
offer there is by prefix and key type, never by fragment number, which is why
it is unaffected.

Signed-off-by: Glenn Gore <glenn.g@affinidi.com>
@stormer78
stormer78 merged commit b5a0c3b into main Sep 14, 2026
4 checks passed
@stormer78
stormer78 deleted the fix/key-rename-name-rule branch September 14, 2026 15:35
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