feat(manager): mint a DID from the whole task surface, and read its log - #247
Merged
Merged
Conversation
The New DID form sent 3 of the 20 members `vta/webvh/dids/create/1.0`
accepts, and the DIDs pane showed `logEntryCount` with no way to read an
entry — a number with nothing behind it. Anything past a server id and
Portable meant leaving the console for the CLI.
The form now covers the surface, grouped by the decision each member
makes rather than by the payload's field order:
- where it is published — server, domain, label, and the existing
`PathPicker` with its live DID preview and the hosting server's own
path rules
- what it commits to — portable and pre-rotation as choices with their
consequence beside them, since both are fixed in the first log entry
- which keys it is built on — `signingKeyId` / `kaKeyId`, offering only
active keys of the right type in the right context
- what it advertises — `addMediatorService` / `addTspService`, plus a
structured editor for `additionalServices`
- what it is stamped from — templates from `didTemplateList`, a field
per `requiredVars`, `defaults` seeding the controls above
`setPrimary` is now sent explicitly as `false`. The agent defaults it to
`true`, so the quiet path through the old form replaced the context's
identity on every mint — which anything resolving a context's DID to find
its keys, a room host at startup, would then follow.
Clicking a DID expands the row into `vta/webvh/dids/get` with
`includeLog`, fetched on open rather than with the listing: the log is the
DID's whole history and a listing that pulled every one would be every
history, to draw a table. A response carrying no log is drawn as a refusal
to read, never as an absence of history, and one unreadable line is one
unreadable entry rather than a blank panel.
New DID templates pane, full CRUD, built-ins inspect-only. Update is a
REPLACE, so the editor carries `optionalVars` and `defaults` it never
draws; without that a save cleared them while the agent reported success.
"Render the stored version" calls `didTemplateRender` so the operator sees
the agent's own substitution rather than this console's guess at it.
Three defects the new tests caught before they shipped:
- `templateDefaults` coerced its hint with `Number()`, and `Number(null)`
is 0 — a null `preRotationCount` hint would have silently switched
pre-rotation off, the one setting that makes a stolen key
unrecoverable
- the agent answers a context-scoped template listing with global
templates too, so asking both namespaces listed every global template
twice, under one React key; `mergeTemplates` keys on scope AND name,
which keeps two genuinely different templates of one name apart
- the key pickers said "Reading…" with no context selected, when nothing
had been asked for
Reuse of a key is offered but never quiet: two DIDs publishing one key are
provably one holder, so the form says so when a key is chosen and clears
the choice after a mint — leaving it set would build the next DID on the
same key by inaction.
`webvhDidCreate` threads the members it was dropping. `pathMode` is
exposed without the bare `path` shorthand, because sending both is an
error and one way to say it cannot be said twice. `preRotationCount` and
`addMediatorService` use `!== undefined` rather than a truthy test: 0 and
false are the caller's answers, not absence.
The four new `vta/did-templates/*` URIs join the admin guard in ci.yml —
operator surface outside that list is unchecked. Verified after a build
that all four appear only in `manager.js`, that the seeds guard is
untouched, and that the console is still one chunk.
Signed-off-by: Glenn Gore <glenn.g@affinidi.com>
…d-in one
`tests/task-surface-sync.mjs` drove `sync-task-surface.mjs` against a
fixture SDK, let it write the real `packages/core/task-surface.json`, and
restored the saved copy in a `finally`. That is a race, not a cleanup:
`node --test` runs test FILES in parallel processes, and
`tests/task-surface.mjs` reads that same file at module scope.
So the two raced, and the reader saw whatever the writer had got to:
task counts observed while the sync test ran: 256 1 3 2 0
Which is exactly the two shapes CI reported — "task-surface.json looks
truncated" plus "implements 0 of 3 canonical task families" on one
runner, and `SyntaxError: Unexpected end of JSON input` on the other.
It reproduced only where the two files overlap, which on a developer
machine with cores to spare is rarely and on a two-core CI runner is
often: it took out main at db16fbb on 2026-09-14 and both jobs of two
consecutive runs on this branch, while `npm test` stayed green locally
through all of it. The file is byte-identical in the tree either way,
which is what made it read as a corrupt checkout rather than a test
writing where it shouldn't.
`sync-task-surface.mjs` now takes the output path as an optional second
argument, and the fixture points it inside its own temp directory. The
checked-in snapshot is never opened for writing by a test.
One assertion changed with it. "The real snapshot is untouched after a
failed sync" was the only statement available while the fixture shared
that file; with a path of its own, a stopped sync leaves no snapshot at
all, which says the same thing directly rather than resting on a run not
having reached its write.
Signed-off-by: Glenn Gore <glenn.g@affinidi.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The New DID form sent 3 of the 20 members
vta/webvh/dids/create/1.0accepts, and the DIDs pane showed
logEntryCountwith no way to read anentry — a number with nothing behind it. Anything past a server id and
Portable meant leaving the console for the CLI.
The form
Grouped by the decision each member makes rather than by the payload's field
order:
domain,label, and the existingPathPickerwith its live DID preview and the hosting server's own path rulesportableandpreRotationCountas choices with their consequence beside them, since both are fixed in the first log entrysigningKeyId/kaKeyId, offering only active keys of the right type in the right contextaddMediatorService/addTspService, plus a structured id/type/endpoint editor foradditionalServicesdidTemplateList, a field perrequiredVars,defaultsseeding the controls abovesetPrimaryis now sent explicitly asfalse. The agent defaults it totrue, so the quiet path through the old form replaced the context's identityon every mint — which anything resolving a context's DID to find its keys,
a room host at startup, would then follow.
additionalServicesis validated here because the agent does not validate it:it is written into the document verbatim, and the document is a log entry,
which is append-only. A bad entry is published, not refused.
http://andcredentials-in-URL are rejected;
did:endpoints are accepted, since amediator entry names a DID.
Reading a log
Clicking the DID expands the row into
vta/webvh/dids/getwithincludeLog,fetched on open rather than with the listing — the log is the DID's whole
history, and a listing that pulled every one would be every history, to draw a
table. Each entry shows version id, time, proof count, parameters,
verification methods and services, with copy buttons at three levels: whole
log, one entry, the DID.
A response carrying no log is drawn as a refusal to read, never as an absence
of history:
includeLog's own spec says absence means it was not asked for.One unreadable line is one unreadable entry, with its raw text still copyable,
rather than a blank panel.
DID templates pane
Full CRUD; built-ins are inspect-only because the agent refuses the write and
a button that refuses first beats a refusal after the form was filled in.
Update is a REPLACE, so the editor carries
optionalVarsanddefaultsitnever draws — without that a save cleared them while the agent reported
success. Render the stored version calls
didTemplateRenderso the operatorsees the agent's own substitution rather than this console's guess at it; the
button says it renders the stored version, because
rendertakes a name andcannot see unsaved edits.
Three defects the new tests caught before they shipped
templateDefaultscoerced its hint withNumber(), andNumber(null)is0— a nullpreRotationCounthint would have silently switchedpre-rotation off, the one setting that makes a stolen key unrecoverable.
too, so asking both namespaces listed every global template twice, under
one React key.
mergeTemplateskeys on scope and name, which still keepstwo genuinely different templates of one name apart.
had been asked for.
Reuse of a key is offered but never quiet: two DIDs publishing one key are
provably one holder, so the form says so when a key is chosen and clears the
choice after a mint — leaving it set would build the next DID on the same key
by inaction.
Core
webvhDidCreatethreads the members it was dropping.pathModeis exposedwithout the bare
pathshorthand, because sending both is an error and one wayto say it cannot be said twice.
preRotationCountandaddMediatorServiceuse!== undefinedrather than a truthy test:0andfalseare the caller'sanswers, not absence.
Guards
The four new
vta/did-templates/*URIs join the admin guard inci.yml—operator surface outside that list is unchecked. Verified after a build that
all four appear only in
manager.js, that the seeds guard is untouched, thatmanager.jsis still a single chunk, and thatbackground.jsstill has nodynamic
import().Second commit: a pre-existing CI race, fixed en route
The first push went red on both Node versions, and it was not this change —
the identical failure took out
mainat db16fbb on 2026-09-14.tests/task-surface-sync.mjsdrovesync-task-surface.mjsagainst a fixtureSDK, let it write the real checked-in
packages/core/task-surface.json, andrestored the saved copy in a
finally. That is a race rather than a cleanup:node --testruns test files in parallel processes, andtests/task-surface.mjsreads that same file at module scope. Watching the filewhile the sync test ran against the original code:
— which is exactly the two shapes CI reported:
task-surface.json looks truncatedplusimplements 0 of 3 canonical task familieson one runner, andSyntaxError: Unexpected end of JSON inputon the other. One cause, twosymptoms, which is why it read like a corrupt checkout. The blob is
byte-identical in the tree; I checked the remote SHA against local before
suspecting the tests.
It reproduces only where the two files overlap — rarely on a many-core dev
machine, often on a two-core runner — so
npm teststayed green locally throughevery one of these failures.
sync-task-surface.mjsnow takes the output path as an optional secondargument and the fixture points it inside its own temp directory, so no test
opens the checked-in snapshot for writing. With the fix the same observation
loop sees only
256. One assertion changed with it: "the real snapshot isuntouched after a failed sync" was the only statement available while the
fixture shared that file; with a path of its own, a stopped sync leaves no
snapshot at all, which says it directly.
This bug is on
mainand will keep flaking other PRs until this merges. Ifyou would rather it landed separately it lifts out cleanly — it is the last
commit and touches only those two files.
Verification
tsc -bclean, 1452 tests pass / 0 fail (77 new across 7 files), buildclean. Also driven in a real Chrome tab against a shim — confirmed the template
defaultsseeding Portable + pre-rotation 3, the render round-trip, the logexpansion, and that the key pickers exclude an
x25519key from the signinglist, a
revokedkey from both, and a key belonging to another context.Pre-merge checklist