fix(console): a store's write counter is not a record's revision - #164
Merged
Conversation
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>
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.
Reported straight after #163 went live: adding attributes made the version number climb.
phone.mobilev1 · 07/09/2026, 10:48:29namev2 · 07/09/2026, 10:48:44Two different attributes, each written once, in a column headed Updated. It reads as "revision 2".
The agent is right; the console was wrong
versionis 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" — andvta-persona/src/store.rsexplains why it has to be store-wide:So
v2on 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 failureformat.tswas 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.mtssweepssrc/manager/**for a version given avprefix and set beside a timestamp, which is the specific form that reads as a revision. It deliberately permits the two legitimate uses: carrying it asexpectedVersion, 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 bothassert.matchand.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.tsxrenders 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. Everyci.ymlassert step run locally against a real build.