Skip to content

fix(console): a store's write counter is not a record's revision - #164

Merged
stormer78 merged 1 commit into
mainfrom
fix/persona-version-display
Sep 7, 2026
Merged

fix(console): a store's write counter is not a record's revision#164
stormer78 merged 1 commit into
mainfrom
fix/persona-version-display

Conversation

@stormer78

Copy link
Copy Markdown
Contributor

Reported straight after #163 went live: adding attributes made the version number climb.

phone.mobile v1 · 07/09/2026, 10:48:29
name v2 · 07/09/2026, 10:48:44

Two different attributes, each written once, in a column headed Updated. It reads as "revision 2".

The agent is right; the console was wrong

version is a value of the store's monotonic write counter, not the record's own. The schema is explicit — "a value of the store's monotonic write counter. Server-assigned; a producer never chooses one" — and vta-persona/src/store.rs explains why it has to be store-wide:

One monotonic counter for the store. A record's version is the counter value its most recent write took, which makes the same number serve as both the optimistic-concurrency token and the change-feed watermark. Per-record counters could do the first but not the second, because two records' counters are not comparable to each other.

So v2 on a freshly created record is correct: it is the second write to the pool. The console was reporting the pool's write history as the record's own — the same failure format.ts was written for (a value the console did not have, rendered as a confident wrong one), and worse here, because the number is plausible and nothing on screen tells it apart from the revision count it resembles.

The fix

The counter is no longer displayed, in either the Attributes or the Profiles table. It is still read and still matters: every edit sends it back as expectedVersion, which is exactly what an opaque concurrency token is for. A value to carry, not a value to show.

Why a rule and not a one-line fix

vta/app-state's version is the same shape — a namespace-wide counter — so this is a trap the next pane can fall into. manager-version-display.test.mts sweeps src/manager/** for a version given a v prefix and set beside a timestamp, which is the specific form that reads as a revision. It deliberately permits the two legitimate uses: carrying it as expectedVersion, and showing it under its own name next to an explanation, the way the app-state and policy editors show what they are compare-and-swapping against.

Mutation-checked: restoring the v{a.version} render fails it.

One note worth keeping. The guard builds its regex per use rather than sharing one. A global regex carries lastIndex, and both assert.match and .test() advance it, so a shared instance resumes mid-string on the next call and silently skips matches — in a guard, indistinguishable from finding none. That caught the test's own self-check before it caught anything real.

Not covered: app-state.tsx renders the same kind of counter, but under a column headed "Version" with no prefix and no adjacent timestamp — the schema's own name for the field, standing alone. It reads as less of a claim, and the guard permits it deliberately rather than by omission. Worth a look separately if it misleads anyone.

Verification

npm run lint, npm run build, npm test — 787 across four workspaces. Every ci.yml assert step run locally against a real build.

Adding a second attribute to an empty pool produced a row labelled `v2`
that had never been edited, and the pane put it in a column headed
"Updated": "v2 · 07/09/2026, 10:48:44" reads as revision 2.

The agent is right and the console was wrong. `version` is a value of
the *store's* monotonic write counter — the schema says exactly that,
and `vta-persona`'s store header explains why it has to be store-wide:
the same number serves as the optimistic-concurrency token and as the
change-feed watermark, which per-record counters could not do, because
two records' counters are not comparable to each other. It is not an
edit count, and nothing on screen distinguished it from one.

So it is no longer shown. It is still read, and still matters — every
edit sends it back as `expectedVersion`, which is what an opaque token
is for. A value to carry, not a value to show.

`manager-version-display.test.mts` makes it a rule rather than a
one-line fix, because `vta/app-state` has the same shape (a
namespace-wide counter). A version may be carried, and may be shown
under its own name beside an explanation the way the app-state and
policy editors show what they are compare-and-swapping against; it may
not take a `v` prefix and sit next to a timestamp. Mutation-checked.

The guard builds its regex per use. A shared global one carries
`lastIndex` — `assert.match` advances it — so it would have resumed
mid-string on the second file and skipped matches, which in a guard is
indistinguishable from finding none. That bit the test's own
self-check first.

Signed-off-by: Glenn Gore <glenn.g@affinidi.com>
@stormer78
stormer78 merged commit 21939ad into main Sep 7, 2026
3 checks passed
@stormer78
stormer78 deleted the fix/persona-version-display branch September 7, 2026 08:54
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