diff --git a/package-lock.json b/package-lock.json index aa40ac1..d2429d8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2304,9 +2304,9 @@ "link": true }, "node_modules/@openvtc/trust-tasks": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@openvtc/trust-tasks/-/trust-tasks-0.19.1.tgz", - "integrity": "sha512-p0e1mbmhI+JJ/vFaG5Y+X1LvrK8bB4vi+JF622wFRyyym2Brcvffa5Kfc5N5VPqi5d9E0HF8fza4SCHTbJU3gg==", + "version": "0.19.5", + "resolved": "https://registry.npmjs.org/@openvtc/trust-tasks/-/trust-tasks-0.19.5.tgz", + "integrity": "sha512-zNL532p+NSZ73MkoyZz9kegDRxQXRF7NzTpZBfSVP/PgS6DBUoi48UjzdEHqjVWhHHMtiKlaaYc2vKZzicSiQA==", "license": "Apache-2.0" }, "node_modules/@openvtc/vti-didcomm-js": { @@ -8034,7 +8034,7 @@ "dependencies": { "@cfworker/json-schema": "^4.1.1", "@noble/curves": "^2.4.0", - "@openvtc/trust-tasks": "^0.18.3", + "@openvtc/trust-tasks": "^0.19.5", "@openvtc/vti-didcomm-js": "^0.8.0", "@openvtc/vti-tsp-js": "^0.2.0", "@scure/base": "^2.2.0", @@ -8046,12 +8046,6 @@ "typescript": "^7.0.2" } }, - "packages/core/node_modules/@openvtc/trust-tasks": { - "version": "0.18.3", - "resolved": "https://registry.npmjs.org/@openvtc/trust-tasks/-/trust-tasks-0.18.3.tgz", - "integrity": "sha512-iECnJWi9LJ1DqMjYdunJC1BdaTzVkFRU7ngD2DBl3oPxpPOfiayCSKboKFv3aF3/lBBOnpSIV76emVAgs4yjSg==", - "license": "Apache-2.0" - }, "packages/demo-rp": { "name": "@openvtc/pnm-demo-rp", "version": "0.1.0", diff --git a/packages/core/package.json b/packages/core/package.json index 59fc799..20d151c 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,7 +1,7 @@ { "name": "@openvtc/pnm-core", "version": "0.9.1", - "description": "Browser-side bridge between WebAuthn passkeys and VTA-managed DIDs. Wire types, WebAuthn ceremony helpers, COSE→Multikey conversion, DID verificationMethod builder, REST + DIDComm transports, mediator client, SIOP / RP-login / provision-integration flows.", + "description": "Browser-side bridge between WebAuthn passkeys and VTA-managed DIDs. Wire types, WebAuthn ceremony helpers, COSE\u2192Multikey conversion, DID verificationMethod builder, REST + DIDComm transports, mediator client, SIOP / RP-login / provision-integration flows.", "license": "Apache-2.0", "repository": { "type": "git", @@ -130,7 +130,7 @@ "dependencies": { "@cfworker/json-schema": "^4.1.1", "@noble/curves": "^2.4.0", - "@openvtc/trust-tasks": "^0.18.3", + "@openvtc/trust-tasks": "^0.19.5", "@openvtc/vti-didcomm-js": "^0.8.0", "@openvtc/vti-tsp-js": "^0.2.0", "@scure/base": "^2.2.0", diff --git a/packages/core/src/webvh/dids.ts b/packages/core/src/webvh/dids.ts index 666bd93..2363d3d 100644 --- a/packages/core/src/webvh/dids.ts +++ b/packages/core/src/webvh/dids.ts @@ -33,6 +33,12 @@ import { type Payload as DidsListPayload, type Response as DidsListResponse, } from "@openvtc/trust-tasks/vta/webvh/dids/list/1.0/payload"; +import { + TYPE_URI as DIDS_REALIGN_KEYS, + RESPONSE_TYPE_URI as DIDS_REALIGN_KEYS_RESPONSE, + type Payload as DidsRealignKeysPayload, + type Response as DidsRealignKeysResponse, +} from "@openvtc/trust-tasks/vta/webvh/dids/realign-keys/1.0/payload"; import { TYPE_URI as DIDS_UPDATE, RESPONSE_TYPE_URI as DIDS_UPDATE_RESPONSE, @@ -274,3 +280,32 @@ export async function webvhDidRegisterWithServer( DIDS_REGISTER_WITH_SERVER_RESPONSE, "vta/webvh/dids/register-with-server/1.0", payload); } + +/** + * Rename a DID's key records onto the verification-method ids its published + * document declares. + * + * **The repair for records the agent named without reading its own document.** + * A key record's id is a verification-method id — it is what a mediator matches + * an inbound JWE recipient against, and what anyone reading the document would + * hand to a signing task. Where the two disagree, the agent holds a key the + * document addresses under a name the agent does not answer to. + * + * `keys/rename` cannot fix that and is not meant to: its identifier gate refuses + * `:` and `#` so that a rename is not a way to write verification-method-shaped + * names into a key store. So this call names nothing — `did` and `dryRun` are + * the whole payload, and the agent derives every target from that DID's own + * published log, matching records to methods by public key. + * + * **Send it once with `dryRun` before sending it without.** The response is the + * same shape either way and says which it is, so a caller can show the plan and + * then apply exactly what was shown. + */ +export async function webvhDidRealignKeys( + sender: TrustTaskSender, + params: WebvhCall & DidsRealignKeysPayload, +): Promise { + const { holder, service, ...payload } = params; + return send(sender, { holder, service }, DIDS_REALIGN_KEYS, DIDS_REALIGN_KEYS_RESPONSE, + "vta/webvh/dids/realign-keys/1.0", payload); +} diff --git a/packages/core/task-surface.json b/packages/core/task-surface.json index e0052c4..6697439 100644 --- a/packages/core/task-surface.json +++ b/packages/core/task-surface.json @@ -2,7 +2,7 @@ "$comment": "Generated by scripts/sync-task-surface.mjs from a vta-sdk checkout. Do not hand-edit: re-run the script. Checked by tests/task-surface.mjs.", "source": { "crate": "vta-sdk", - "version": "0.35.0", + "version": "0.38.0", "scanned": "vta-sdk/src/**/*.rs" }, "tasks": [ @@ -56,6 +56,12 @@ "TASK_AUDIT_LIST_0_1" ] }, + { + "uri": "https://trusttasks.org/spec/audit/verify/0.1", + "consts": [ + "TASK_AUDIT_VERIFY_0_1" + ] + }, { "uri": "https://trusttasks.org/spec/auth/authenticate/0.1", "consts": [ @@ -1021,6 +1027,26 @@ "TASK_VAULT_UPSERT_0_3" ] }, + { + "uri": "https://trusttasks.org/spec/vetting/decline/0.1", + "consts": [ + "VETTING_DECLINE_TYPE" + ] + }, + { + "uri": "https://trusttasks.org/spec/vetting/request/0.1", + "consts": [ + "VETTING_REQUEST_RESPONSE_TYPE", + "VETTING_REQUEST_TYPE" + ] + }, + { + "uri": "https://trusttasks.org/spec/vetting/session/0.1", + "consts": [ + "VETTING_SESSION_RESPONSE_TYPE", + "VETTING_SESSION_TYPE" + ] + }, { "uri": "https://trusttasks.org/spec/vta/app-state/delete/1.0", "consts": [ @@ -1381,6 +1407,12 @@ "TASK_WEBVH_DIDS_LIST_1_0" ] }, + { + "uri": "https://trusttasks.org/spec/vta/webvh/dids/realign-keys/1.0", + "consts": [ + "TASK_WEBVH_DIDS_REALIGN_KEYS_1_0" + ] + }, { "uri": "https://trusttasks.org/spec/vta/webvh/dids/register-with-server/1.0", "consts": [ @@ -1443,6 +1475,13 @@ "JOIN_REQUEST_MANIFEST_TYPE" ] }, + { + "uri": "https://trusttasks.org/spec/vtc/join-requests/manifest/0.2", + "consts": [ + "JOIN_REQUEST_MANIFEST_0_2_RESPONSE_TYPE", + "JOIN_REQUEST_MANIFEST_0_2_TYPE" + ] + }, { "uri": "https://trusttasks.org/spec/vtc/join-requests/status/0.1", "consts": [ @@ -1494,6 +1533,41 @@ "MEMBER_VMC_RESPONSE_TYPE", "MEMBER_VMC_TYPE" ] + }, + { + "uri": "https://trusttasks.org/spec/vtc/vetting/revoke-statement/0.1", + "consts": [ + "VETTING_REVOKE_STATEMENT_RESPONSE_TYPE", + "VETTING_REVOKE_STATEMENT_TYPE" + ] + }, + { + "uri": "https://trusttasks.org/spec/vtc/vetting/vetters/grant/0.1", + "consts": [ + "VETTING_VETTER_GRANT_RESPONSE_TYPE", + "VETTING_VETTER_GRANT_TYPE" + ] + }, + { + "uri": "https://trusttasks.org/spec/vtc/vetting/vetters/list/0.1", + "consts": [ + "VETTING_VETTER_LIST_RESPONSE_TYPE", + "VETTING_VETTER_LIST_TYPE" + ] + }, + { + "uri": "https://trusttasks.org/spec/vtc/vetting/vetters/profile/0.1", + "consts": [ + "VETTING_VETTER_PROFILE_RESPONSE_TYPE", + "VETTING_VETTER_PROFILE_TYPE" + ] + }, + { + "uri": "https://trusttasks.org/spec/vtc/vetting/vetters/resend/0.1", + "consts": [ + "VETTING_VETTER_RESEND_RESPONSE_TYPE", + "VETTING_VETTER_RESEND_TYPE" + ] } ] } diff --git a/packages/core/tests/task-surface.mjs b/packages/core/tests/task-surface.mjs index 573c60e..d10340d 100644 --- a/packages/core/tests/task-surface.mjs +++ b/packages/core/tests/task-surface.mjs @@ -347,7 +347,20 @@ test("coverage against the agent's surface is recorded, not discovered", () => { // address (dtgwg-trust-tasks-tf#426). The third is `rooms/keys/present/0.2` // and `rooms/owner/issue-authority/0.2` counting as new families beside the // 0.1 this library had drifted behind. - const expected = 204; + // + // 204 → 205 is `vta/webvh/dids/realign-keys/1.0` — the repair for a DID whose + // key records are not named after the verification methods it publishes + // (dtgwg-trust-tasks-tf#456, VTI#1466 and #1470). + // + // **The canonical total jumps 227 → 235 in the same commit, and only one of + // those eight is this task.** The other seven were always in the SDK and + // invisible to the scanner: `vta-sdk` derives a growing number of constants + // from the generated payload type rather than writing the URI out, and + // `sync-task-surface.mjs` matched only literals until it was taught to + // resolve them. So this is not the agent growing seven families — it is a + // snapshot that had been under-counting the denominator, and with it the gap + // this number exists to keep reviewable. + const expected = 205; assert.equal( implemented.size, expected, diff --git a/packages/extension/src/manager/destructive.tsx b/packages/extension/src/manager/destructive.tsx index 6ecc9f3..ea6584d 100644 --- a/packages/extension/src/manager/destructive.tsx +++ b/packages/extension/src/manager/destructive.tsx @@ -14,6 +14,20 @@ // because the agent refuses a non-empty deletion on purpose and overriding that // refusal is a second decision. // +// ## `nature`, and why a second component was not written +// +// Not every two-step action is a destruction. Realigning a DID's key records +// asks the agent for the same thing — what would change, before it changes — +// and then applies it, but nothing is lost: records are renamed and the key +// material behind them is untouched. +// +// Drawing that in red would be a lie of exactly the kind `manager-theme.css` +// forbids, where `--w-danger` is one of three colours that mean something. So +// the *shape* of the decision is shared and the **voice** is a parameter: +// `nature: "corrective"` asks what would change rather than what would be +// destroyed, and draws in the accent rather than in danger. It gets no `force` +// tick, because there is no refusal to override. +// // ## Why `consentRequired` renders here // // The agent may answer "a human must approve this first". That is not a @@ -77,6 +91,15 @@ export function ConsentCeremony({ pending }: { pending: ConsentRequiredError }) export interface DestructiveProps

{ /** Button copy for the action itself, e.g. "Delete context". */ label: string; + /** + * What kind of action this is, which decides the voice and the colour. + * + * `destructive` (the default) is the one this component was written for: + * something is lost, so the preview asks what would be destroyed and is drawn + * in danger. `corrective` is a repair — the preview asks what would change, + * and drawing it in danger would overstate it. + */ + nature?: "destructive" | "corrective"; /** Disabled reason, or null when the action is available. Shown rather than * hiding the control — see `hasRole` in `use-vta.ts`. */ disabledReason?: string | null; @@ -112,6 +135,7 @@ type Phase

= */ export function Destructive

({ label, + nature = "destructive", disabledReason = null, preview, renderPreview, @@ -122,6 +146,7 @@ export function Destructive

({ }: DestructiveProps

) { const [phase, setPhase] = useState>({ kind: "idle" }); const [force, setForce] = useState(false); + const corrective = nature === "corrective"; const reset = useCallback(() => { setPhase({ kind: "idle" }); @@ -162,7 +187,7 @@ export function Destructive

({ if (phase.kind === "idle") { return (