Skip to content

fix(persona): read a listing to the end, rather than to the first page - #199

Merged
stormer78 merged 1 commit into
mainfrom
fix/persona-list-paging
Sep 8, 2026
Merged

fix(persona): read a listing to the end, rather than to the first page#199
stormer78 merged 1 commit into
mainfrom
fix/persona-list-paging

Conversation

@stormer78

Copy link
Copy Markdown
Contributor

The loose end from the persona-pane run, plus the last of #191's vocabulary.

A listing was read to the first page

personaAttributeList, personaProfileList and listBindings asked for a cursor-paginated listing and returned page one, dropping nextCursor. The specification names that inference directly:

a producer MUST NOT infer exhaustion from a short page — only an absent nextCursor means the end

and each client's own docstring promised the opposite of what it did: enumerate the pool, every persona bound in this context.

Nothing downstream could notice, which is what makes it worth fixing while it is still latent. A short array is indistinguishable from a complete one, so past the agent's page size (100 by default) the identity map drew a face pointing at attributes that were not in its own list — the chip falls back to printing a raw id where the type should be — attributeReach under-reported which contexts a value reaches, and every count on screen agreed with every other because they were all counting the same truncated array. A picture that reads as complete while being partial is the one wrong answer that pane must never give.

How

  • collectPages (core/src/util/pages.ts) follows the cursor, and is where the reasoning lives. util is the bottom of the layering, so admin/persona.ts and persona/bindings.ts can share it without a sideways import.
  • All three clients page through it. limit is documented as the page size to ask for, never a cap on the result.
  • The rest of the request is preserved across pages. A second page of a typePrefix query that forgot the prefix would answer a question about one attribute by reading every value the holder has — reveal-value.ts matches by id, so it would not even look wrong.
  • listBindings returns a document with no nextCursor, because there is nothing left to fetch. The two console surfaces that ignored that member are correct by construction now rather than by luck.
  • The bound throws. Returning what had been collected would reintroduce this defect one layer down and with a longer array, and a caller cannot tell a truncated answer from a complete one. A repeated cursor is reported as its own fault, separately from the page bound, so the message says which happened. MAX_PAGES is 50 — 25,000 records at the specification's maximum page size, high enough that reaching it means a fault rather than a large pool.

The last two fact symbols

FactValueAttributeValue, maskedFactmaskedValue: the last symbols in the persona surface carrying the word #191 took off the screen. English uses of "fact" — a fact about the clock, a fact about how the agent is deployed — are left alone; there the word is doing its own job.

The other two tidy-ups from that list are already gone: #196's rewrite of claim-sensitivity.ts took the stale "there is no prefix walk" comment and the unused type-only isSensitive with it.

Checks

  • 12 new tests. Core 573 pass, extension 359 pass.
  • tsc -b clean; npm run build clean, manager.js a single chunk.
  • Pinned in util.pages.mjs: pages are concatenated in order, a short page is not the end, an empty page carrying a cursor is followed, a repeated cursor is named as a loop, and an endless listing throws instead of returning a short answer. Pinned in admin.persona.mjs: each of the three clients reads to the end, the cursor goes back on the next request, and a narrowed listing stays narrowed.

`personaAttributeList`, `personaProfileList` and `listBindings` asked for a
cursor-paginated listing and returned the first page, dropping `nextCursor`.
The specification names that inference directly — "a producer MUST NOT infer
exhaustion from a short page — only an absent `nextCursor` means the end" — and
the clients' own docstrings promised the opposite of what they did: *enumerate
the pool*, *every persona bound in this context*.

Nothing downstream could notice. A short array is indistinguishable from a
complete one, so past the agent's page size (100 by default) the identity map
drew a face pointing at attributes that were not in its own list, `attributeReach`
under-reported which contexts a value reaches, and every count on the screen
agreed with every other because they were all counting the same truncated array.
A picture that reads as complete while being partial is the one wrong answer
that pane must never give.

- `collectPages` (`core/src/util/pages.ts`) follows the cursor and is where the
  reasoning lives. `util` is the bottom of the layering, which is why it can be
  shared by `admin/persona.ts` and `persona/bindings.ts` without a sideways
  import.
- All three clients page through it. `limit` is documented as the page size to
  ask for, never a cap on the result, and the rest of the request is preserved
  across pages — a second page of a `typePrefix` query that forgot the prefix
  would answer a question about one attribute by reading every value the holder
  has.
- `listBindings` returns a document with no `nextCursor`, because there is
  nothing left to fetch. The two console surfaces that ignored that member are
  now correct by construction rather than by luck.
- The bound **throws**. Returning what had been collected would reintroduce this
  defect one layer down with a longer array, and a caller cannot tell a
  truncated answer from a complete one. A repeated cursor is reported as its own
  fault, separately from the page bound, so the message says which happened.

Also finishes #191's vocabulary work: `FactValue` → `AttributeValue` and
`maskedFact` → `maskedValue`, the last two symbols in the persona surface
carrying the word that PR took off the screen. English uses of "fact" — a fact
about the clock, a fact about how the agent is deployed — are left alone; they
are the word doing its own job.

12 new tests (573 core, 359 extension), `tsc -b` clean, `npm run build` clean.

Signed-off-by: Glenn Gore <glenn.g@affinidi.com>
@stormer78
stormer78 merged commit 0dd7e6a into main Sep 8, 2026
3 checks passed
@stormer78
stormer78 deleted the fix/persona-list-paging branch September 8, 2026 11:42
@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