Skip to content

refactor(persona): speak the vocabulary a person reads - #280

Merged
stormer78 merged 4 commits into
mainfrom
feat/persona-vocabulary
Sep 7, 2026
Merged

refactor(persona): speak the vocabulary a person reads#280
stormer78 merged 4 commits into
mainfrom
feat/persona-vocabulary

Conversation

@stormer78

Copy link
Copy Markdown
Contributor

Aligns the identity pane with design-docs/persona-vocabulary.md. Supersedes
the naming half of #278, which guessed at the vocabulary and got the direction
of one word backwards — I had used "face" for the persona DID; the table uses
it for a profile.

Two commits

1. persona, not face, for a persona DID. That much of #278 survives: the
spec, the VTA, the SDK, pnm, the console pane and this repo's own
PersonaRecord all say persona. Also renames ContentPanelState::personas
identity, so the list can be identity.personas rather than
personas.personas.

2. The vocabulary itself.

code / wire screen
attribute facta fact about you, held once
attribute pool your facts
profile facethe set of facts you show together
binding wearingChange face, Take it off
disclosure history what has left, and to whom
correlation linked
selfAsserted / credentialBacked / generated you said so / credential / made per verifier
proof rungs whole · partly · derived · yes/no only

Tabs read Personas · Your facts · Faces · Communities · What has left.

The spec's words stay in the types, on the wire and in the audit log — they are
kept off the screen. PersonaTab::Profiles still addresses
persona/profile/*; its label says Faces. That split is why this is a
display-layer change and not a rename: no core type was renamed.

Two things the table surfaced

  • stale now carries its reason (stale · revoked). The table asks for it,
    and "stale" alone tells someone something is wrong without telling them what.
    The agent was already sending staleReason and nothing was reading it.
  • The credential row carries the half people miss. Provenance shows
    credential and same signature everywhere — links you. Severity inverts
    intuition here: a credential shown whole links more than a fact you said, and
    the words must not hide it.

The guard

the_pane_speaks_the_agreed_vocabulary renders every tab — empty and populated
— plus all three editors, and fails if any word from the table's avoid-list
reaches the screen.

Every one of those words is a word this pane's own code and wire format use, so
they are one careless format! away at all times, and the drift is invisible in
review because each line looks correct to whoever wrote it. Verified by flipping
a label back to "Profiles": the test fails, rather than passing vacuously.

Two words are deliberately absent from the list: credential is on-screen
vocabulary, and per verifier is the agreed phrase for a generated value —
the table bans "verifier" in prose, not that phrase.

Not done here

openvtc health, the join flow and the communities panel still use the spec's
words on screen (persona, which is fine; and the communities panel's own
presents: line, which this replaces with wears: via
BindingSummary::describe). A wider sweep of the setup and join copy is worth
doing when those are next touched — flagged rather than folded in, since it is
outside this pane.

Pre-merge checklist (vti-stack-development-guide §9)

- [x] No new clients/timeouts (R1.2) — display-layer only
- [x] No lock held across a network await (R1.3)
- [x] No local state before its remote effect (R2.1)
- [x] Retries bounded (R1.4) — none added
- [x] Loops survive transient errors (R1.5) — n/a
- [x] Acks after durable handoff (R1.6) — n/a
- [x] Wire types (R3.*) — none changed; `staleReason` is newly *read*, and is
      already sent by the agent
- [x] Config absence = most restrictive (R5.*) — unchanged
- [x] Logs/status claim only what was verified (R6.*) — "wears: unknown" stays
      distinct from "wears: nothing"
- [x] "Process dies on the next line" (R2.1) — n/a
- [x] Deviations — none

cargo test --workspace green (420 in the binary, 500 in core), clippy clean.

The identity pane called a persona DID a "face". Nothing else does: not the
Trust Tasks spec, not the VTA, not the SDK, not `pnm persona binding set
--persona-did`, not the console pane shipped this week, and not this repo's own
`PersonaRecord` / `persona_ref` / `persona_did`. A holder moving between two of
those surfaces had to translate, and translation is where a boundary gets
misread.

"Profile" was the other candidate and it is the one that would have hurt: it
already names the thing a persona *presents*. Calling the DID a profile would
have collided head-on with `persona/profile/*` — you bind a profile to a
persona; the reverse is not a sentence.

The word "persona" is genuinely overloaded — the task family spans the DID and
everything above it — and "face" was an attempt to fix that by inventing a third
noun. The overload is a prose problem with a prose fix: say "persona DID" where
a sentence has to be unambiguous about which layer it means, exactly as `pnm`'s
own help does. The pane's module header now states both words and the direction
between them.

`ContentPanelState::personas` becomes `identity`, so the list can take the name
it should have had: `identity.personas` rather than `personas.personas`. That
matches the menu entry, which has read "My Identity" since the pane landed —
the pane is not only personas, and now the field says so.

Signed-off-by: Glenn Gore <glenn.g@affinidi.com>
`design-docs/persona-vocabulary.md` fixes one set of words across the console,
`pnm`, the mobile agent and this TUI, so a person meets the same sentence
wherever they meet the model. This brings the identity pane to it.

The substitutions that matter:

- an **attribute** is a **fact** — "a fact about you, held once";
- a **profile** is a **face** — "the set of facts you show together". Not
  "profile": that already means three things in this product and "my LinkedIn
  page" to everyone else;
- a **binding** is **wearing** — a persona wears a face in a community.
  `Change face`, `Take it off`;
- **disclosures** are **what has left, and to whom**; **correlation** is
  **linked**; provenance reads *you said so* / *credential* / *made per
  verifier*, and the credential row carries the half people miss — the same
  signature everywhere, so it links you.

Tabs: Personas · Your facts · Faces · Communities · What has left.

The spec's words are exact and stay in the types, on the wire and in the audit
log. They are kept off the *screen*: `PersonaTab::Profiles` still addresses
`persona/profile/*`; its label says Faces. That split is the whole design of the
table, and it is why this is a display-layer change rather than a rename.

`stale` now carries its reason (`stale · revoked`) — the table asks for it, and
"stale" alone tells someone something is wrong without telling them what. The
agent was already sending `staleReason`; nothing was reading it.

## The guard

A test renders every tab — empty and populated — plus all three editors, and
fails if any word from the table's avoid-list reaches the screen. Every one of
them is a word this pane's own code and wire format use, so they are one
careless `format!` away at all times, and the drift is invisible in review
because each line looks correct to whoever wrote it. Verified by flipping a
label back: the test fails, rather than passing vacuously.

Signed-off-by: Glenn Gore <glenn.g@affinidi.com>
A scripted replacement matched the same `use` line in two tests and added the
import to both. Clippy runs with `-D warnings` in CI, so an unused import there
is a failed build.

Ran `cargo clippy --workspace --all-targets -- -D warnings` this time rather
than plain clippy — the plain form said nothing, which is how it reached CI.

Signed-off-by: Glenn Gore <glenn.g@affinidi.com>
The rebase onto #279 put the grant hint inside the pane the vocabulary work had
just rewritten, and the widened guard immediately found three words on screen
that the table keeps off it — "pool" and "profiles" in the hint's own prose, and
"holder" in the agent's refusal, which the pane was echoing verbatim.

The hint's prose is ours, so it now says facts and faces.

The agent's sentence is not ours, and echoing it was the wrong call rather than
a wording slip. It is accurate and abstract — "the holder's attribute pool,
which sits above every trust context" — and it lands on someone who has just
been refused, which is the worst moment to meet three unfamiliar nouns. This is
the one failure the pane recognises exactly, so it now answers it in the agreed
words and gives the command. Every other failure is still echoed verbatim: that
text is *data*, naming a host or a contract mismatch, and translating what we do
not recognise would be inventing a cause (R6.4).

The guard grew the state that would have caught this: every tab now renders
refused as well as empty and populated. The words written for a failure are the
ones written under pressure, and nothing else on screen exercised them.

`persona-holder` inside the command is exempted — a capability name an operator
types verbatim, like a task URI. The exemption is the exact token, so a sentence
cannot hide behind the hyphen.

Signed-off-by: Glenn Gore <glenn.g@affinidi.com>
@stormer78
stormer78 force-pushed the feat/persona-vocabulary branch from b61f81c to c25cd34 Compare September 7, 2026 12:19
@stormer78
stormer78 merged commit ea09857 into main Sep 7, 2026
14 checks passed
@stormer78
stormer78 deleted the feat/persona-vocabulary branch September 7, 2026 12:29
stormer78 added a commit that referenced this pull request Sep 7, 2026
#281)

#280 brought the identity pane to `design-docs/persona-vocabulary.md` and left
the persona copy elsewhere for when it was next touched. This is that copy: the
join flow's identity choice, its reuse warning, the invitation list, and the
communities panel's empty state.

The reuse warning is the one that matters. It is the last thing a person reads
before linking themselves across two communities, and it now carries the
sentence the table asks every surface to share verbatim — "the same person to
anyone who sees both" — rather than a paraphrase of it. The point of a fixed
phrase is that meeting it twice teaches it; a paraphrase in one surface spends
the recognition and gives nothing back.

## What was deliberately left alone

`profile` in setup, backup and `--profile`. That is the **config profile** — a
local installation profile that predates the persona family, has its own
meaning to operators, and appears in flags they type. It is exactly the collision
the vocabulary doc cites when it declines to use "profile" for a face, and
renaming it here would create the confusion the table exists to prevent.

Signed-off-by: Glenn Gore <glenn.g@affinidi.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