Skip to content

test(dogfood): the persona matrix hangs its invoice lines off an unpaid invoice - #24832

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-24780-persona-matrix-unpaid-invoice
Oct 20, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-24780-persona-matrix-unpaid-invoice

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Refs #24780
Clause-②: no

This is row 3 of #24780 (triage pointer 6286191020): the stale fixture in showcase-crud-persona-matrix.dogfood.test.ts. Row 1 is PR #24805, which landed as c56115bf9. Row 2 belongs to #24749. #24780 remains open: the PM closes it once rows 1 and 3 have both landed.

What changes

One fixture line in packages/qa/dogfood/test/showcase-crud-persona-matrix.dogfood.test.ts, in beforeAll:

  • Before: seed.invoiceId = await firstId('showcase_invoice'), which takes whichever invoice a limit: 1 read returns first.
  • After: the first invoice whose status is not paid. That is the state the showcase's paid-invoice line lock reads (the showcase_guard_paid_invoice_line_* hooks test header.status === 'paid'). It follows the pattern of the [#18682] account choice directly above it, and the new comment explains why.

The lock from PR #24731 is unchanged. This PR changes no case assertion, no other dogfood file, and nothing under examples/app-showcase/** or packages/spec/**.

Why

  • The admin payload control creates a showcase_invoice_line under seed.invoiceId and asserts a status below 300. Since PR fix(showcase): a paid invoice's lines can no longer be added, removed or moved, so its total stays what was paid #24731, the showcase refuses a line insert under a paid invoice with a 400: "This invoice is paid, so its lines are locked: a line cannot be added to it."
  • firstId does not pick the same invoice each time. On ab9890169 I measured three boots. The limit: 1 read returned INV-1012 (sent), then INV-1008 (sent), then INV-1005 (paid). The ids are random and differ on every boot. The unlimited read returned insertion order every time, with INV-1001 first. The seed holds 12 invoices, and 5 of them are paid. So the control fails on some boots and passes on others.
  • CI shows the same pattern. Shard 3/3 on 74f78159af was red in push run 39376439272 and green in schedule run 39378039597. On a43405d596 it was red in schedule run 39384364728. I also read four main runs from before 576d0f3733, and shard 3/3 was green in all of them.

Evidence

Every run used pnpm --filter @objectstack/dogfood exec vitest run --maxWorkers=2 FILE under os-verify-lock, after building the closure with turbo run build --filter='@objectstack/dogfood^...'. This file belongs only to the isolated project. The gate runs turbo run test --filter=@objectstack/dogfood with OS_TEST_SHARD=k/3.

On an unmodified file, red or green comes down to which invoice the boot happens to draw. So the red/green comparison uses an uncommitted scratch copy whose only change forces the parent to a paid invoice (where: { status: 'paid' }):

tree parent result
4e0f5f50d (576d0f3733^) forced paid Tests 46 passed (46), VERDICT command-exit 0
576d0f373 (PR #24731) forced paid FAIL: "admin control: the showcase_invoice_line payload is VALID (...) got {"error":"This invoice is paid, so its lines are locked: a line cannot be added to it.","object":"showcase_invoice_line"}: expected 400 to be less than 300", VERDICT command-exit 1
ab9890169 (main, the base) forced paid the same FAIL at the assertion CI cites (:369), VERDICT command-exit 1
8c65e505a (the fix) the fix Tests 46 passed (46), VERDICT command-exit 0
0503d47c6 (final head, main merged) the fix Tests 46 passed (46) on each of two runs

Only examples/app-showcase, a changeset and one docs file differ between the two bisect trees, and the dogfood suite loads the showcase from source. So a single built closure served both legs, and the matrix file is byte-identical across the pair.

Which lines hang off a seeded invoice. I ran a probe on 8c65e505a: a scratch copy with one extra reporting case, not committed. Only showcase_contributor creates a line. That persona's showcase_invoice row runs first, and its line hangs off its own invoice, whose status is draft. The only line under a seeded invoice is the admin control's, so fixing this one parent fixes every place in the file with this cause.

No ablation: product behaviour does not change.

Gates (final head 0503d47c6)

  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 56 commands from 1 changed path. All 56 ran, each with its exit code captured before any pipe. --ran: "56 derived famil(ies) accounted for — 56 run, 0 NOT-MEASURED (a DERIVED zero — all 56 recorded an exit code and none of them is 3)."
  • check:dual-build-cjs-loads first exited 3 (PREREQUISITE NOT MET: 8 packages had no dist). I built those 8 and re-ran it: exit 0, "112 published require entry point(s) across 69 package(s) load".
  • pnpm lint (eslint . --no-inline-config): exit 0, no findings.
  • pnpm --filter @objectstack/dogfood run typecheck: exit 0. tsc --listFilesOnly lists the edited file.
  • check-nul-bytes: OK (scanned 12537 text file(s) ...; no raw ASCII control bytes).

Changeset

skip-changeset: the diff is one test file in @objectstack/dogfood, which is private: true and publishes nothing.

Acceptance notes

  • The file's other seeded parents (showcase_product, showcase_project, showcase_expense_report, showcase_team) still use firstId, so they are also drawn at random on each boot. Today no rule makes any of those choices refuse a payload, and every pre-lock run I read was green. If a state rule is added to one of those objects, its parent should be chosen the same way.

Generated by Claude Code

…id invoice

The admin payload control created a showcase_invoice_line under
firstId('showcase_invoice'), whichever invoice a limit-1 read returned.
With random ids that is a different invoice on every boot, and several
seeded invoices are paid. Since the showcase locks a paid invoice's lines
(its showcase_guard_paid_invoice_line_* hooks), the control answered 400
"This invoice is paid, so its lines are locked" on some boots and not
others.

The seeded parent is now chosen by the state the lock reads (status not
paid), the way the account above it is chosen by its rule. Every line the
file creates under a seeded invoice hangs off this one parent. The lock is
unchanged.

Claude-Session: https://claude.ai/code/session_01LmjU2x1faLZ39DVTFyVca6
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 20, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs.

What this run could not see

Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 87c09ebb7ac768b43d137996296d042a3cee1c97 → packageMentionDocs.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 · ACCEPT

domain:cli seat 2 (#22648) · os-justin · session session_01LmjU2x1faLZ39DVTFyVca6 · 2026-10-20T08:44Z · head 0503d47c627fb46e1775dc48358e96f925abb09a · claim 6286414566 (row 3) → report 6287051424. No contract face (Clause-②: no, a test file only), so no review of record is owed.

Check Reading
Shape Draft, base main. First line Refs #24780, which is correct: row 2 is #24749's, and this seat closes the card after this lands. Clause-②: no line-initial. Assignee os-justin = the card's. skip-changeset is applied and correct: one test file in a private package.
Surface 1 file, showcase-crud-persona-matrix.dogfood.test.ts (+13 / −1), inside the claim. One fixture line in beforeAll changes, and no case assertion moves. The paid-invoice lock (PR #24731) and examples/app-showcase/** are untouched. check-governed-merges.mjs --pr 24832: NOT governed, 14 changed lines.
Mechanism seed.invoiceId is now the first invoice with status not paid. That is the one predicate the lock reads, header.status === 'paid' in examples/app-showcase/src/data/hooks/index.ts, read at source. No list of admitted statuses is copied. If no such invoice existed, the file's own seed guard (:342–:350) would fail loud on an empty id.
Evidence The failure is intermittent. A limit: 1 read picks a random-id invoice per boot, and 5 of the 12 seeded invoices are paid. So the dev forced a paid parent on a scratch copy to get a deterministic pair: red on ab9890169 with the card's message at :369; green on 576d0f3733^ and red on 576d0f3733, with the matrix file identical across the pair. The head is green (46/46, run twice). CI agrees: 74f78159a went red on push run 39376439272 and green on schedule run 39378039597.
Gates Local: 56/56 derived families exit 0, 0 UNRUN; pnpm lint exit 0. Check-runs on this head (39, latest per name), read at this act: 21 success, 9 skipped (the dogfood shards among them, which run on main's battery), 9 in_progress (Lint, Type Check, Test Core 1–3). Landing waits on all of them.

Accepted deviations:

  • Zone 2's single-run bisect was replaced by the forced-paid deterministic pair, because the failure is random per boot.
  • CI's job log was not readable from the dev's container, so the reproduction is local, with the identical message.
  • main (c56115bf9) was merged before the PR.
  • The full dogfood run was not done locally, a declared narrowing. The shards do not run on pull_request or merge_group.

Out-of-scope dispositions (one line each):

  • The file's other seeded parents (product, project, expense report, team) are also drawn by firstId at random, and no rule refuses a payload on any of them today → Acceptance notes. It is a latent-drift observation, not a defect.

Landing: this is the main-red card's last row in this lane. It goes through the merge queue by this seat once every check on this head reads success (or an expected skip). After it merges, this seat closes #24780 (rows 1 and 3 landed; row 2 is #24749's) and lifts the lane's main-red hold.


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants