Uh oh!
There was an error while loading. Please reload this page.
test(showcase): land the seed fixtures the platform checklist could not run without (#9308) - #9789
Conversation
…ot run without (#9308) Three platform capabilities had no fixture anywhere in the reference app, so the checklist items covering them were not failing — they were unrunnable. 1. A second, actually LOGINABLE member. The demo personas have been sys_user rows since #3409/#3411 and neither could sign in, blocking every item that needs two acting identities. The non-obvious half: better-auth 1.7 keys accounts on (issuer, providerAccountId), so a credential row carrying any other issuer is invisible to sign-in and fails INVALID_EMAIL_OR_PASSWORD behind a "User not found" warn that points at the user row instead of the account. seed-approval-demo.ts now provisions the credential account through better-auth's own $context and READS the issuer off the dev admin's own credential row rather than re-spelling a constant plugin-auth owns. Dev-only by construction. 2. showcase_client_brief — the first object to opt into publicSharing. Carries redactFields, an expiry cap and an eligibility predicate; the seed holds a published brief (mint-eligible) and a draft one (refused RECORD_NOT_ELIGIBLE) so the predicate is falsifiable. Every other object still declines the opt-in, which is what keeps the per-object 422 a real control. 3. showcase_client_liaison — the app's first readable:false FLS grant, on the same three showcase_project budget figures showcase_contributor governs with readable:true/editable:false. All three move together because budget_remaining is a formula over budget - spent. Downstream reconciliations, each deliberate and none a re-baseline: access-matrix.json gains two rows and moves none; the persona x CRUD census follows the matrix (50/50 -> 54/54, arithmetic recorded at the assertion) and its fixture maps learn the new object; the position count pin follows the new position. Five checklist items are revised in the same change — gap text kept, marked closed-by-fixture, revision bumped, history appended (#7670 pattern). Not landed: the writable-package summary field for automation.rollup-summary-filter. It is not seed data and cannot be made into seed data — see FOLLOW-UPS.md section 6. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y26DJEHSBhhAQ6wwfsHNza
📓 Docs Drift CheckNothing 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. |
os-project-manager
commented
Aug 18, 2026
PM review — accepted. Both questions ruled; the CI red is fixed and it was in the body, not the code.The gate that fired, and why it was right
You were asking me a question; GitHub's closing-keyword parser does not read questions, modals or negations. Merging would have closed #9308 silently. I edited the body (the gate re-runs on an edit — no push needed) and recorded the ruling in place of the question. ⛔ No code change, and nothing about your diff was wrong. Q2 — |
`check:i18n-coverage` went red on PR #9789: untranslated declared strings for examples/app-showcase grew 393 -> 405. The showcase declares supportedLocales ['en', 'zh-CN'], so every label the new `showcase_client_brief` object declares owed a zh-CN rendering. Measured: all 12 are that object — label, pluralLabel, description, seven field labels and both `status` options. The new position and permission set are NOT counted by this rule, which counts object/field/option strings only. Both locales are written key for key. `check-i18n-coverage` is a two-sided ratchet, so an `en` entry landing without its zh-CN mirror is what widens the gap rather than closing it. Renderings follow the ones this bundle already established rather than being coined fresh — 标题 / 项目 / 状态 / 备注 / 负责人 are the words showcase_task and showcase_invoice already use for the same concepts, which is the whole point of the file: a list must not mix a translated 状态 with an English Owner beside it. 草稿 / 已发布 are new (nothing here rendered a draft state before); 已发布 follows the 已完成 pattern the bundle uses for a state that has been reached. The description is mirrored byte-for-byte from the object declaration on the en side and translated in full on the zh side, ADR reference included. A bundle entry overrides the schema label for its locale, so a "tidied" English description would silently rewrite product copy under the guise of translating it, and a half-dropped Chinese one is the locale degradation the bundle exists to prevent. Count back to 393 exactly, not merely stopped growing. The baseline in scripts/i18n-coverage-baseline.json is untouched (no --update anywhere). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y26DJEHSBhhAQ6wwfsHNza
Uh oh!
There was an error while loading. Please reload this page.
Part of #9308. Three of that card's four stock seed fixtures land here; the fourth does not, and the reason is at the bottom.
Three capabilities the platform already ships had no fixture anywhere in the reference app. The checklist items covering them were therefore not failing — they were unrunnable, which is the quieter and worse state: nothing red, no coverage.
1. A second, actually loginable member
The demo personas have existed as
sys_userrows since #3409/#3411 — Mei Phone, who submits EXP-2001/EXP-DEMO and holds no approver position, and Ada Auditor, the only holder ofauditor. Neither could sign in, so every item needing two acting identities was stuck: per-group 会签 needs the two groups decided by two different people, submitter-side viewer gating needs the submitter looking at their own request, and an out-of-office delegation is only falsifiable when the delegate holds a separate bearer token.The non-obvious half is why a password hash was never enough. better-auth 1.7 keys accounts on
(issuer, providerAccountId), so a credential row carrying any other issuer is invisible tofindAccountByKey; sign-in then failsINVALID_EMAIL_OR_PASSWORDbehind a "User not found" warn that points at the user row, which is fine, instead of at the account, which is not. Four checklist items had that recorded as a knownGap, each having rediscovered it.seed-approval-demo.tsnow provisions the credential account through better-auth's own$context— its hasher, itsinternalAdapter.createAccount— the same pathplugin-auth's adminset-user-passwordtakes for a user onboarded without one. The issuer is read off the dev admin's own credential row rather than re-spelled here: the constant belongs toplugin-auth, and an example app carrying a second copy is where the two start disagreeing. Derived-or-skip, never guessed — a wrong issuer is worse than an absent one, and it occupies the unique slot the correct row needs.Dev-only by construction: the bootstrap runs only where the dev admin exists, and that admin is hard-gated on
NODE_ENV=development.2. An object that opts into
publicSharingNo stock object declared it, so
POST /share-linksanswered 422SHARING_NOT_ENABLEDfor every showcase object and the whole downstream half of link sharing — resolve, redaction, the audience and password gates, fail-closed revoke — was unreachable.showcase_client_briefopts in withredactFields, an expiry cap and aneligibilitypredicate. The seed carries a published brief (mint-eligible) and a draft one (refused 422RECORD_NOT_ELIGIBLE), so the predicate is falsifiable and not merely satisfied — the same both-directions reasoning the key-account sharing rule's seed follows. Every other object still declines the opt-in, which is what keeps the per-object 422 a real negative control rather than a sentence about the past.3. A
readable: falseFLS grantThe app governed the three
showcase_projectbudget figures withreadable: true, editable: false— the write half of field-level security — and authored no read-withheld grant at all, leavingplugin-security's field masker with no stock fixture. The server half was reachable from a permission set invented inside a test; a console render never was.showcase_client_liaisonis that grant, on the same three fields, so the two sets read side by side as the two halves of one mechanism. All three figures move together becausebudget_remainingis a formula overbudget - spent— masking one leaks it back through arithmetic, and a mask with a documented hole teaches the hole.The three fixtures share one persona on purpose: a Client Liaison writes client-facing briefs and publishes them by link, reads projects but not the internal budget figures. One coherent business shape, one new position, one new permission set.
Downstream reconciliations — each deliberate, none a re-baseline
access-matrix.jsongains 2 rows, moves none--update-access-matrix, and the diff is purely additiveMARKER/PAYLOADmaps learnshowcase_client_briefNo golden was re-baselined and no
sharingModelwas touched.Reverse verification — prediction stated first, then measured
Each fixture was ablated at the committed tree and the predicted red/green split compared against observation. All three matched exactly.
2 failed / 7 passed, and the sign-in failure is literally401 INVALID_EMAIL_OR_PASSWORD— the symptom the four knownGaps describedpublicSharing.enabled: falseenabledgate fires before the eligibility gate, so 422 stays 422 whileRECORD_NOT_ELIGIBLEbecomesSHARING_NOT_ENABLEDfieldsblockEvery marker removed and proven (
grep -c= 0); tree clean at the final commit.Verification
Union re-derived from the actual changed paths off
git merge-base, run at09f0a9167— the final commit:check:platform-checklistandcheck:slot-lookup: all green, includingcheck:type-check-debt --re-measureover a built workspace (33 ledger entries re-measured, none above its recorded number).examples/app-showcase: 21 files / 337 tests passed;packages/qa/dogfood: 120 files passed, 1 skipped / 877 passed, 3 skipped.Checklist bookkeeping — the #7670 pattern
Five items revised in this change. Gap text kept and marked closed-by-fixture (it carries the reason),
revisionbumped,historyappended,check:platform-checklistgreen:approvals.per-group-signoffr4 → r5 — and the admin-override fallback is withdrawn as an acceptable path, since a decision laundered through the admin cannot show two groups resolving to two peopleapprovals.viewer-gating-submitter-sider4 → r5 —blocked(fixture)removed; the gap that is not closed (stock requests route to the admin, so the persona choice stays load-bearing) is restated so it is not read as closed by associationapprovals.ooo-delegation-rerouter2 → r3 — the other two gaps are untouched and still bindaccess-security.share-link-capability-tokensr1 → r2 —blocked(fixture)removed, with the warning that clause 1's negative control must stay pointed at an object that did not opt inaccess-security.fls-mask-and-stripr2 → r3 — the spec/security: field masking is all-or-nothing — no partial masking (phone last-4, ID middle-8), andmaskingRulewas pruned as dead in 2026-06 #8993maskingRulegap is untouched and still has no fixture anywhere in this repoFOLLOW-UPS.mdgains a section 6 recording the resolution of its own section 3, appended rather than rewritten per that file's own rule.The fourth fixture, and why this is
Part ofrather than a closeThe writable-package summary field for
automation.rollup-summary-filter's editor half is not delivered, and it is not a matter of effort: it is not seed data and cannot be made into seed data. A writable package is a DB-backedsys_packagesrow plus authoredsys_metadataitems —isWritablePackagetreats a booted code package as read-only by definition, and the showcase is one —sys_packagesis a raw SQL tabledefineSeedcannot address, and a roll-up needs a parent and a child, so the fixture is a boot-time metadata-authoring bootstrap minting two tables on every fresh boot of the reference app. Whether the showcase should ship a permanent writable base is a showcase design call with consequences past this item (it is also the contrast sideaccess-security.readonly-package-locks-studioneeds), so it is filed for a ruling rather than guessed at: #9788. That item keeps its blocker and its knownGap untouched.The dispatch prompt asked for a closing keyword; this body says
Part ofinstead, because merging it would not complete the card as its own title describes it — the title names the writable summary field — and an auto-close would silently retire a card with an open fixture.PM ruling (2026-08-18): no closing keyword, deliberately.#9308 is closed by hand after this merges, with the re-scoping onto #9788 stated in the closing comment, so the decision is visible and reasoned rather than a side effect of a keyword. The original
close #9308sentence here carried a live closing keyword despite its conditional prose — GitHub's parser ignores surrounding modals — andPart-of PR must not also close its cardcaught it. That gate was right.Sequencing
Rounds R1–R3 of the #9296 wave stay pinned at
e4e5c6e3; this lands onmainindependently. The five items it unblocks want a follow-up run at a new sha — and #9702 already records exactly that debt foraccess-security.record-share-grant-revoke. Landing this close behind it means one combined follow-up run is likely cheaper than two; noting the overlap there rather than filing a third run card.Backlinks: #9296 (the wave), #7670 (the pattern this follows), #9237 / #9697 (what moved under the card two hours before dispatch), #9702 (the follow-up-run debt), #9788 (the fixture left).
Generated by Claude Code