Skip to content

feat(manager): worlds — the parts of a life, and the faces in them - #212

Merged
stormer78 merged 3 commits into
mainfrom
feat/persona-worlds
Sep 9, 2026
Merged

feat(manager): worlds — the parts of a life, and the faces in them#212
stormer78 merged 3 commits into
mainfrom
feat/persona-worlds

Conversation

@stormer78

Copy link
Copy Markdown
Contributor

The plugin's half of persona/facet/*, completing the chain: spec #405@openvtc/trust-tasks 0.17.9 / trust-tasks-rs 0.18.9 → VTI #1338 → here.

A holder who uses this model for a while does not end up with three faces. They end up with twenty, and a flat list of twenty is a list nobody reads. A world is the arrangement over them — Work, Home, Play.

Three commits: the core client, the palette, the pane.

The delete confirm is the whole screen

A grouping that looks like a folder is assumed to behave like one, so the confirm names what survives before what goes:

Work stops being a part of your life on this screen.
All 2 faces in it — Acme, LinkedIn — stay exactly as they are. They will simply belong to no world.
Nothing already shared is affected — that has left.

A holder who believes Delete takes the faces with it will not press it; one who believes it and is wrong has lost nothing but will never trust the screen again. There is no cascade on the wire and none is implied — the render test asserts both the survival sentence and the absence of any "also delete" copy.

Three more things worth review

A face already elsewhere is greyed before the save. A checkbox that looked available and then failed is a refusal the holder had no way to anticipate. worldOfFace excludes the world being edited — without it a world becomes uneditable the moment it holds anything, which is the same self-clash the agent excludes on its own side.

The refusal is matched on the code, and typed on the class that carries one. placedElsewhere tests instanceof RelayTaskError and code === "persona/facet/put:faceAlreadyPlaced" — R3.7, never a message. A refusal whose details are missing or half-typed returns null rather than an empty list: "0 faces already belong elsewhere" is a claim, and half an answer about where the holder's faces are is worse than none because the missing half is invisible.

The editor opens with the membership it will replace. A put replaces both lists, so an editor seeded empty would silently empty a world on an edit that meant to rename it. It carries the expectedVersion it read.

The palette

Eight --m-world-* tokens in both themes. A world colour is the third categorical set and the first chosen by a person, which is what makes it worth asserting: given a free-form hex, nothing could keep a decorative choice out of the semantic channel — someone names a world "Work", picks red, and every card in it reads as an alarm with no way to discover why.

manager-world-colour.test.mts computes the distance from the values the stylesheet actually ships, per theme. Closest legitimate pair is 69.6 (sand vs --w-warn, dark) against a threshold of 60. Verified non-vacuous: moving --m-world-clay to #b03d2d fails with "is 9.0 from --w-danger … a holder picking it would have every card in that world read as danger".

Guards

  • Both CI lists grow ten URIs → thirteen: the exclusion keeping this half out of every wallet surface, and the presence assertion keeping the console from losing it to a tree-shake. Verified on a real build — all three URIs are in manager.js and in no other bundle.
  • package.no-restated-types.mjs caught FacetColour derived by indexed access; it is now re-exported from the generated bindings.
  • task-surface.json synced from vta-sdk 0.34.1 — the version #1338 published, so the snapshot records a surface that exists. Recorded coverage 198 → 201 in the same commit, which is what that guard asks for.

Verification

core 596/596, extension 479/479 (24 new), tsc -b clean, npm run build clean.

Not in this PR

Attribute membership in a world — the wire carries attributeIds and the client sends it, but the pane only edits faces so far. And the payoff that motivated putting facets on the wire at all: persona/correlation/analyze distinguishing sharing within a world (intended) from sharing across worlds (the finding worth raising). That is a change to a different spec.

Three calls against the family specified in dtgwg-trust-tasks-tf#405 and
published in @openvtc/trust-tasks 0.17.9 (the dependency moves from ^0.17.8,
which cannot name these types).

On screen these are **worlds** — `design-docs/persona-vocabulary.md`. The
wire keeps the specification's word, exactly as `profile`/face does.

**Membership is sent only where the caller supplied it.** Both lists are
REPLACED by a put, so sending `[]` for a caller that passed nothing would
empty a facet's membership on an edit that meant to rename it — the same
replace hazard the attribute editor guards one record down. The paired
assertion matters as much: an explicitly empty list IS carried, because a
suite that only checks omissions passes for a client that never sends
membership at all.

**The listing follows the cursor.** `limit` is the page size to ask for and
never a cap, the fourth listing in this module to say so and for the same
reason: a short page is indistinguishable from a complete one, so only an
absent `nextCursor` means the end.

**Delete sends the facet and nothing else.** There is no cascade member
anywhere on this wire — a facet is an arrangement, not a container — and
`releasedFaces` is what a surface needs to describe the result honestly
rather than saying only "deleted".

`FacetColour` is re-exported from the generated bindings rather than derived
by indexed access: `package.no-restated-types.mjs` catches the second, and
it is right to — a local restatement of an enum drifts silently the moment a
ninth colour is published.

Both CI guard lists grow from ten URIs to thirteen: the exclusion that keeps
this half out of every wallet surface, and the presence assertion that keeps
the console from losing it to a dropped import or a tree-shake.

core 595/596. The one failure is `task-surface.mjs`: the snapshot is synced
from a vta-sdk checkout and records the crate VERSION, so syncing it from
the unmerged branch carrying these constants
(OpenVTC/verifiable-trust-infrastructure#1338) would record that a published
vta-sdk contains tasks it does not. It is synced from main, as its own
reviewed commit, once that merges.

Signed-off-by: Glenn Gore <glenn.g@affinidi.com>
`manager-theme.css` sets the rule that `--w-ok` / `--w-warn` / `--w-danger`
are the only colours that mean anything. A world colour is the third
categorical set, and the first one in this console chosen by a **person** —
which is exactly what makes it worth asserting rather than eyeballing.

Given a free-form hex from a holder, nothing could keep a decorative choice
out of the semantic channel: someone names a world "Work", picks red, and
every card in it reads as an alarm with no way to discover why. A closed set
of eight makes that unrepresentable rather than merely discouraged.

`manager-world-colour.test.mts` computes the distance from the values the
stylesheet actually ships — not from a copy kept in the test, which would
pass forever after someone edited the CSS — per theme, because a light world
colour sitting far from the light danger red says nothing about the dark
pair and both ship. Closest legitimate pair is 69.6 (sand vs warn, dark)
against a threshold of 60.

**Verified non-vacuous**: moving `--m-world-clay` to #b03d2d fails the test
with "is 9.0 from --w-danger … a holder picking it would have every card in
that world read as danger".

`WORLD_COLOURS` is typed against the generated `FacetColour` union, so a
ninth colour published in the specification is a compile error here — a
picker missing an option the agent will happily store — rather than a name
this console silently cannot draw. `worldHue` returns a token reference and
never a value, because a component reaching for a hex is correct in exactly
one theme.

Signed-off-by: Glenn Gore <glenn.g@affinidi.com>
The console's half of `persona/facet/*`, behind a third view on the persona
pane. A holder who uses this model for a while does not end up with three
faces; they end up with twenty, and a flat list of twenty is a list nobody
reads.

**The delete confirm is the whole screen.** A grouping that looks like a
folder is assumed to behave like one, so the confirm names what *survives*
before what goes: "All 2 faces in it — Acme, LinkedIn — stay exactly as they
are." A holder who believes Delete takes the faces with it will not press
it; one who believes it and is wrong has lost nothing and will never trust
the screen again. There is no cascade on the wire and none is implied —
`worlds-pane.render.test.mts` asserts both the survival sentence and the
absence of any "also delete" copy.

**A face already elsewhere is greyed before the save, not refused after.** A
checkbox that looked available and then failed is a refusal the holder had
no way to anticipate. `worldOfFace` excludes the world being edited, or a
world becomes uneditable the moment it holds anything.

**The refusal is matched on the code, and typed on the class that carries
one.** `placedElsewhere` tests `instanceof RelayTaskError` and `code ===
"persona/facet/put:faceAlreadyPlaced"` — R3.7, never a message. A refusal
whose details are missing or half-typed returns null rather than an empty
list: "0 faces already belong elsewhere" is a claim, and half an answer
about where the holder's faces are is worse than none because the missing
half is invisible.

**The editor opens with the membership it will replace.** A put replaces
both lists, so an editor seeded empty would silently empty a world on an
edit that meant to rename it — and it carries the version it read.

Faces belonging to no world are reported rather than hidden. Most are, before
anyone arranges anything, and a screen listing only arranged faces
under-reports what the holder has.

`task-surface.json` is synced from vta-sdk 0.34.1 — the version published by
VTI#1338, so the snapshot records a surface that exists — and the recorded
coverage moves 198 → 201 in the same commit, which is what that guard asks
for.

Verified on a real build: all three URIs appear in `manager.js` and in no
other bundle, which is both halves of the CI guard.

core 596/596, extension 479/479, `tsc -b` clean.

Signed-off-by: Glenn Gore <glenn.g@affinidi.com>
@stormer78
stormer78 merged commit 7e6acd5 into main Sep 9, 2026
3 checks passed
@stormer78
stormer78 deleted the feat/persona-worlds branch September 9, 2026 07:10
@affinidi-appsecurity-bot

Copy link
Copy Markdown

🛡️ AI Agentic Security Code Review

🔎 A manual security review is recommended before merging. Please contact the Security team for specifics and remediation guidance.

ℹ️ Detailed findings are not published on public repositories; the Security team holds the complete report.

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.

2 participants