Skip to content

Commit 06f9848

Browse files
test(showcase): land the seed fixtures the platform checklist could not run without (#9308) (#9789)
* test(showcase): land the seed fixtures the platform checklist could not 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 * i18n(showcase): translate the client-brief fixture into zh-CN (#9308) `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 --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 1258dca commit 06f9848

20 files changed

Lines changed: 1029 additions & 57 deletions
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
---
2+
"@objectstack/example-showcase": patch
3+
---
4+
5+
Land the showcase seed fixtures the platform checklist could not run without (#9308)
6+
7+
Three capabilities the platform ships had no fixture anywhere in the reference app, so the
8+
checklist items covering them were not failing — they were unrunnable. Each is closed here
9+
with the smallest stock addition that makes it observable, and with the negative control
10+
left intact.
11+
12+
**A second, actually loginable member.** The demo personas (Mei Phone the submitter, Ada
13+
Auditor the sole `auditor`) have existed as `sys_user` rows since #3409/#3411, and neither
14+
could sign in — so every item needing two acting identities was stuck: per-group 会签 needs
15+
the two groups decided by two different people, submitter-side viewer gating needs the
16+
submitter looking at their own request, and an out-of-office delegation is only falsifiable
17+
when the delegate holds a separate token. The non-obvious half is why a password hash was
18+
never enough: better-auth 1.7 keys accounts on `(issuer, providerAccountId)`, so a
19+
credential row carrying any other issuer is invisible to sign-in, which then fails
20+
`INVALID_EMAIL_OR_PASSWORD` behind a "User not found" warn pointing at the user row rather
21+
than at the account. `seed-approval-demo.ts` now provisions the credential account through
22+
better-auth's own `$context` — its hasher, its `internalAdapter.createAccount` — and READS
23+
the issuer off the dev admin's own credential row instead of re-spelling a constant
24+
`plugin-auth` owns, so the two cannot drift. Dev-only by construction: the bootstrap runs
25+
only where the dev admin exists, and that admin is hard-gated on `NODE_ENV=development`.
26+
27+
**An object that opts into `publicSharing`.** No stock object declared it, so
28+
`POST /share-links` answered 422 `SHARING_NOT_ENABLED` for every showcase object and the
29+
whole downstream half of link sharing — resolve, redaction, the audience and password
30+
gates, fail-closed revoke — was unreachable. `showcase_client_brief` opts in with
31+
`redactFields`, an expiry cap and an `eligibility` predicate, and the seed carries both a
32+
`published` brief (mint-eligible) and a `draft` one (refused `RECORD_NOT_ELIGIBLE`) so the
33+
predicate is falsifiable and not merely satisfied. Every other object still declines the
34+
opt-in, which is what keeps the per-object 422 a real control.
35+
36+
**A `readable: false` FLS grant.** The app governed the three `showcase_project` budget
37+
figures with `readable: true, editable: false` — the WRITE half of field-level security —
38+
and authored no read-withheld grant at all, leaving `plugin-security`'s field masker with
39+
no stock fixture. `showcase_client_liaison` is that grant, on the same three fields, so the
40+
two sets read side by side as the two halves of one mechanism. All three figures move
41+
together because `budget_remaining` is a formula over `budget - spent` and masking one
42+
leaks it back through arithmetic.
43+
44+
Downstream reconciliations, each deliberate: `access-matrix.json` gains two rows and moves
45+
none; the persona × CRUD sweep's census follows the matrix (50/50 → 54/54, arithmetic
46+
recorded at the assertion) and its fixture maps learn the new object; the position count
47+
pin follows the new position. The five checklist items whose `knownGaps` this closes are
48+
revised in the same change — gap text kept, marked closed-by-fixture, `revision` bumped,
49+
`history` appended.

‎docs/qa/platform-checklist/FOLLOW-UPS.md‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,3 +136,33 @@ Ledger 182 → 190 items; `coverage.json` 28 mapped / 2 waived → **30 mapped /
136136
`integration-system.datasource-credential-refusal-matrix`) assert guards that are
137137
already shipped and already public in their ADRs/issues; nothing here discloses an
138138
unfixed hole.
139+
140+
## 6. Resolution 2026-08-18 — §3's fixture list, three of seven paid (#9308)
141+
142+
§3 above is left exactly as written; this section is its resolution row, per this file's
143+
own append-never-rewrite rule. Three of the seven fixtures §3 asked for landed in
144+
`examples/app-showcase/**`, and the checklist items they unblock were revised in the same
145+
change (gap text kept and marked closed-by-fixture, `revision` bumped, `history`
146+
appended — the #7670 pattern).
147+
148+
| §3 line | landed as | items unblocked |
149+
|---|---|---|
150+
| "a `publicSharing.enabled` object" | `showcase_client_brief` — `redactFields`, `maxExpiryDays`, and an `eligibility` predicate, with a `published` AND a `draft` brief seeded so the predicate is falsifiable | `access-security.share-link-capability-tokens` (was `blocked(fixture)`, now runnable) |
151+
| "a second signed-up (non-admin) user in seeds" | both demo personas get a better-auth credential account at boot (`seed-approval-demo.ts` → `ensureCredentialAccount`), issuer derived from the dev admin's own row | `approvals.per-group-signoff`, `approvals.viewer-gating-submitter-side` (was `blocked(fixture)`), `approvals.ooo-delegation-reroute` |
152+
| — (not in §3; found by #9308) | `showcase_client_liaison` — the app's first `readable: false` FLS grant | `access-security.fls-mask-and-strip` clause 5, whose UI half had no reachable fixture |
153+
154+
**Still open from §3, unchanged and still correctly listed there:** the OIDC/social IdP,
155+
the gantt fixture variants, the not-auto-bound audience suggestion, the
156+
`IMPORT_CONSOLE_LIVE` import harness, and the escalation clock-control harness.
157+
158+
**One fixture #9308 scoped but did NOT land: the writable-package summary field** for
159+
`automation.rollup-summary-filter`'s editor half. It is not seed data and could not be
160+
made into seed data. A writable package is a DB-backed `sys_packages` row plus authored
161+
`sys_metadata` items (`isWritablePackage`: a booted CODE package is read-only by
162+
definition, and the showcase is one), and a roll-up needs a parent AND a child object, so
163+
the fixture is a boot-time metadata-authoring bootstrap that mints two tables on every
164+
fresh boot of the reference app. Whether the showcase should ship a permanent writable
165+
base is a showcase design call with consequences beyond this item — it is also the
166+
contrast side `access-security.readonly-package-locks-studio` needs — so it is filed
167+
separately rather than guessed at. The item keeps its `blocked(fixture)` and its
168+
`knownGap` untouched.

‎docs/qa/platform-checklist/areas/access-security.json‎

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -621,7 +621,7 @@
621621
"title": "Field-level security: editable:false strips/denies writes API-side and renders read-only in the UI; masked-read half needs an authored readable:false grant",
622622
"since": "v15",
623623
"status": "active",
624-
"revision": 2,
624+
"revision": 3,
625625
"priority": "P1",
626626
"surface": "mixed",
627627
"personas": [
@@ -632,10 +632,12 @@
632632
"app": "showcase",
633633
"requires": [
634634
"showcase_contributor.fields FLS on showcase_project budget figures (permission-sets.ts — keys are <object>.<field> qualified; bare keys silently enforce nothing, the compile-time lint security-fls-unqualified-key guards that)",
635-
"a seeded showcase_project row with a non-null budget"
635+
"a seeded showcase_project row with a non-null budget",
636+
"showcase_client_liaison — the STOCK readable:false grant since #9308 fixture 4 (permission-sets.ts): showcase_project { allowRead: true } plus fields { 'showcase_project.budget' | '.spent' | '.budget_remaining': { readable: false, editable: false } }, bound to the client_liaison position. Grant it to a FRESH member and to nobody who also holds showcase_contributor: field permissions merge most-permissive across the sets a caller holds, so contributor's readable:true would legitimately un-mask all three"
636637
],
637638
"knownGaps": [
638-
"[API half CLOSED by #9481; the seed gap itself stays open] stock showcase authors NO readable:false FLS grant, so the read-MASKING half (field absent/nulled on GET, plugin-security/src/field-masker.ts) has no stock fixture; to run it, author a scratch permission set carrying readable:false on a showcase_project field and grant it to a fresh member — if the run cannot author one through a supported surface, record that half blocked(fixture) rather than ticking on the write half alone. What changed: showcase-fls-read-mask-strip.dogfood.test.ts does exactly that at runtime, so the SERVER half no longer waits on the seed. The UI half still does — a console render cannot be driven off a permission set that exists only inside a test's stack — and #9308 fixture 4 is the card that would land the stock grant.",
639+
"[CLOSED — API half by #9481, the seed gap itself by #9308 fixture 4; the gap text stays because it carries the reason] stock showcase authored NO readable:false FLS grant, so the read-MASKING half (plugin-security/src/field-masker.ts) had no stock fixture and a run had to author a scratch permission set at runtime — which the SERVER half could do (showcase-fls-read-mask-strip.dogfood.test.ts does exactly that) but the UI half could not, because a console render cannot be driven off a permission set that exists only inside a test's stack. showcase_client_liaison is now that grant on stock seeds, so clause 5 is runnable end to end: grant the set to a fresh member, sign in as them, and compare their API body and their console render against the admin's. packages/qa/dogfood/test/showcase-client-liaison-fixtures.dogfood.test.ts pins the server half of the stock grant (by id, in lists, through an explicit $select) with the admin as the entitled contrast.",
640+
"all three budget figures are withheld together, not just `budget` — `budget_remaining` is a formula over `budget - spent`, so masking one of the three leaks it back through arithmetic. A run that ticks clause 5 having observed only `budget` absent has not checked the mask that matters.",
639641
"MASKED and STRIPPED are two different mechanisms with two different wire shapes, and this item's clause 4 (\"absent or nulled\") predates the measurement. Measured answer: a permission-set readable:false DELETES the key (strip); the #8993 `maskingRule` path REPLACES the value and leaves the key present (mask). A test that only checks \"I did not get the real value\" passes for both and pins neither, so the pin asserts key-absence. The masked half has no fixture at all — do not tick it off the strip pin."
640642
]
641643
},
@@ -709,6 +711,12 @@
709711
"date": "2026-08-18",
710712
"change": "READ half pinned (QA run #9401 scored this item partial on a write-only pin). automated.ref now names the read pin and states what it does NOT reach: the UI render, and the #8993 maskingRule path, which has no fixture in this repo. knownGaps records the measured mask-vs-strip distinction — readable:false DELETES the key, it does not null it — so the next runner does not tick the masked half off the strip pin",
711713
"ref": "#9481"
714+
},
715+
{
716+
"revision": 3,
717+
"date": "2026-08-18",
718+
"change": "the SEED gap is closed too: showcase_client_liaison is a stock permission set carrying readable:false on all three showcase_project budget figures, so clause 5's UI half is reachable for the first time — a real member, a real console render, no test-local stack. knownGaps rewritten as closed-by-fixture rather than deleted, with the merge caveat (do not also grant showcase_contributor) and the reason all three figures move together. The #8993 maskingRule gap is UNTOUCHED and still has no fixture anywhere in this repo",
719+
"ref": "#9308"
712720
}
713721
]
714722
},
@@ -1413,7 +1421,7 @@
14131421
"title": "Share-link capability tokens: anon resolve renders the record minus redactFields, password/audience gates hold, revoke/expire refuse without leaking",
14141422
"since": "v16",
14151423
"status": "active",
1416-
"revision": 1,
1424+
"revision": 2,
14171425
"priority": "P2",
14181426
"surface": "api",
14191427
"personas": [
@@ -1424,17 +1432,15 @@
14241432
"fixtures": {
14251433
"app": "showcase",
14261434
"requires": [
1427-
"an object declaring publicSharing { enabled:true, allowedAudiences, allowedPermissions, redactFields } with at least one visible record — REQUIRED to mint any link"
1435+
"an object declaring publicSharing { enabled:true, allowedAudiences, allowedPermissions, redactFields } with at least one visible record — REQUIRED to mint any link",
1436+
"showcase_client_brief is that object since #9308 fixture 2: publicSharing { enabled:true, allowedAudiences ['link_only','signed_in','email'], allowedPermissions ['view'], maxExpiryDays 30, redactFields ['internal_notes','deal_value'], eligibility \"record.status == 'published'\" }. The minter persona is a member holding showcase_client_liaison (bound to the client_liaison position); the seed carries a PUBLISHED brief ('Northwind — Website Relaunch brief', mint-eligible) and a DRAFT one ('Fabrikam — Compliance Audit brief', which the eligibility predicate refuses 422 RECORD_NOT_ELIGIBLE)"
14281437
],
14291438
"knownGaps": [
1430-
"NO stock showcase object sets publicSharing.enabled (verified across examples/app-showcase/src) — createLink answers 422 SHARING_NOT_ENABLED for every showcase object, so the resolve/redact/password/revoke clauses are BLOCKED until a publicSharing-enabled fixture object (e.g. qa_public_note with redactFields) is added; only the SHARING_NOT_ENABLED clause is runnable on stock today",
1431-
"the GET /share-links/:token/messages half is ai_conversations-only (Cloud/EE service-ai in the `cloud` repo) — split out as a separate knownGap; do not exercise messages against open-framework showcase"
1439+
"[CLOSED by #9308 fixture 2 — the gap text stays because it carries the reason] NO stock showcase object set publicSharing.enabled (verified across examples/app-showcase/src), so createLink answered 422 SHARING_NOT_ENABLED for every showcase object and only the SHARING_NOT_ENABLED clause was runnable; the resolve/redact/password/revoke clauses were BLOCKED. showcase_client_brief now opts in (see requires). NOTE the negative control did NOT move: every OTHER showcase object still declines the opt-in, so clause 1's 422 is still runnable — probe it against showcase_private_note, not against the new object",
1440+
"the GET /share-links/:token/messages half is ai_conversations-only (Cloud/EE service-ai in the `cloud` repo) — split out as a separate knownGap; do not exercise messages against open-framework showcase",
1441+
"audience `public` is deliberately NOT in the fixture object's allowedAudiences (nothing in the showcase wants an un-tokened, indexable surface), so a `public`-audience mint is refused PERMISSION/AUDIENCE-not-allowed rather than being a resolve variant. Record that as the declared policy, not as a defect"
14321442
]
14331443
},
1434-
"blocked": {
1435-
"by": "fixture",
1436-
"ref": "no showcase object sets publicSharing.enabled — needs a publicSharing-enabled fixture object (PENDING-GAPS.md §C share-link-capability-tokens)"
1437-
},
14381444
"steps": [
14391445
"STOCK-RUNNABLE probe: as a member, POST /api/v1/share-links {\"object\": \"showcase_private_note\", \"recordId\": \"<a visible note id>\"} — capture the 422 SHARING_NOT_ENABLED that proves the block (no showcase object opts in)",
14401446
"with a publicSharing-enabled fixture object F (redactFields declared): as a member who can see a record r of F, POST /api/v1/share-links {object:F, recordId:r, redactFields?, audience?, password?, expiresAt?} — capture the 201 {token}",
@@ -1513,6 +1519,12 @@
15131519
"date": "2026-08-08",
15141520
"change": "new — share-link capability tokens (ADR-0047): resolve-minus-redactFields, password/audience gates, fail-closed revoke/expire/record-gone, caller-scoped list; blocked(fixture) because no showcase object opts into publicSharing, messages half split as a Cloud/EE knownGap",
15151521
"ref": "claude/platform-test-checklist-ocwugl"
1522+
},
1523+
{
1524+
"revision": 2,
1525+
"date": "2026-08-18",
1526+
"change": "UNBLOCKED. showcase_client_brief is the stock publicSharing fixture and the seed carries both an eligible and an ineligible record, so mint / resolve / redaction are runnable on stock and the declared eligibility predicate is falsifiable too. `blocked` removed; the fixture is named in requires; the knownGap is rewritten as closed-by-fixture rather than deleted, with the warning that clause 1's negative control must stay pointed at an object that did NOT opt in. packages/qa/dogfood/test/showcase-client-liaison-fixtures.dogfood.test.ts pins the mint, the eligibility refusal and the redaction",
1527+
"ref": "#9308"
15161528
}
15171529
]
15181530
},

0 commit comments

Comments
 (0)