You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Round R1 of the platform-checklist wave tracked at #9296 — the release-gating smoke set, run locally in the R0-provisioned tree to answer "is anything release-blocking broken?" ahead of the three cloud sessions.
Two P0 failures found. Both were surfaced on #9296 the moment they survived second derivation, rather than held to round end:
e4e5c6e3c608b1b807c83a0d5b734f213eb1a1dd — not moved
.objectui-sha (pin)
665661ab093263f39f2e660a295ea615dbcee35a
console dist stamp
665661ab093263f39f2e660a295ea615dbcee35a — stamp == pin, /_console/ served 200 text/html
spec source
PR #9309 head fbd1c0491215406c379405cef262befad1de458d (verified with git rev-parse FETCH_HEAD; in the merge queue at run time, not yet on main). docs/qa/platform-checklist/ is byte-identical to the earlier d23a746c7 — the newer commit only merges main — so the ledger ran is exactly the reviewed one
Environment correction worth carrying forward: Playwright 1.62.1 expects chromium build 1234; this box has only chromium-1194. The first e2e attempt reported 31 failed — pure environment, zero product signal. Passing launchOptions.executablePath=/opt/pw-browsers/chromium-1194/chrome-linux/chrome turns it into 31 passed. Any later round that runs Playwright here must set this or it will file a spectacular false P0.
⚠️objectstack verify --rls is a separate invocation. R0's reading (and my first pass) ran bare objectstack verify, which emits no RLS section at all — runRlsProofs only runs behind the --rls flag. Any round citing plain verify as the oracle for an RLS clause has not actually consulted it.
A list total=2 ["A note 1","A note 2"]; B list total=1 ["B note 1"] — presence and absence, both directions
1 foreign by-id read denied
pass
A → B's note: 404 RECORD_NOT_FOUND (not-found-shaped denial, permitted)
2 foreign by-id write denied, row unchanged
pass
A PATCH → 403 PERMISSION_DENIED; admin re-read body="b1", updated_at unchanged from creation
3 admin reads full set
pass
admin list total=3 — all three run-created ids
4 runRlsProofs no holes
pass
20 PROVEN (20 consistent, 0 HOLES); all 9 position personas 0 HOLES
5 skips explainable + D11 probe
blocked(dependency)
see below
Clause 5: the 3 skips are each benign — showcase_project_membership "no plain-text probe field", showcase_ext_customer/showcase_ext_order external read-only. Hand spot-check of the skipped object: member A → 403 PERMISSION_DENIED, admin sees rows, i.e. genuinely protected. But the clause requires judging against a write-up held privately with the maintainer and deliberately absent from this repo — this runner does not hold it, and the clause says never to tick it green over that. Recorded blocked(dependency), not pass. Nothing about it is restated here.
The dogfood pin covers /data, /meta, /actions, /automation, /packages (25 tests). It does not cover /batch or /security/explain, both named in the item's variants — driven by hand so the item is not left partial:
variant (anonymous)
status
envelope family
GET /api/v1/data/showcase_private_note
401 UNAUTHENTICATED
rest-flat
GET /api/v1/meta
401 UNAUTHENTICATED
rest-flat
POST /api/v1/actions/showcase_task/showcase_mark_done/anon-probe-id
401, not 404
dispatcher-wrapper
GET /api/v1/automation
401 UNAUTHENTICATED
dispatcher-wrapper
POST /api/v1/batch
401 UNAUTHENTICATED
rest-flat
GET /api/v1/security/explain
401 UNAUTHENTICATED
rest-flat
Exactly the two declared families, mapped as the item specifies — no third dialect. Member contrast: data + meta both 200. Declared-public route: anonymous GET /api/v1/forms/<slug> → 404 FORM_NOT_FOUND, not 401 — the gate keys on declared posture, not blanket. Destructive negative: anonymous DELETE /api/v1/automation/showcase_reassign_wizard → 401, and an authed listing still shows the flow among 29.
12 · platform-core.boot-health (rev 2) → pass
health 200 · /ready 200 {"status":"ready","state":"running"} · Flows banner 29 flow(s) 22 bound … · 7 draft with no ⚠ and 0 ERROR lines · /_console/ 200 text/html 6467 bytes · nav diff empty — served grp_data children equal the 12 authored in src/ui/apps/index.ts, nothing added or dropped.
3 WARN lines exist, none of them a SeedLoader rejection: one is the #8686 autonumber tenancy note (which fires at 13:37:39, 3 s before the default organization row is created at 13:37:42 — the "0 rows in sys_organization" it reports is a boot-ordering artifact, not the steady state), and two are SHARING_NOT_ENABLED rule-backfill failures for share_open_tasks_with_manager / share_red_projects_with_execs. Those two mean the showcase ships two authored sharing rules that can never be consulted — noted as a fixture observation, not scored here.
13 · platform-core.seed-integrity (rev 1) → pass
Counts re-derived from src/data/seed/index.ts rather than trusted from the item's baseline. All match, and are identical after a restart against the same DB (idempotent upsert, 130 rows both boots, no rejection lines): account 14 · contact 32 · project 5 · task 10 · invoice 12 · invoice_line 5 · expense_report 4 · expense_line 13 · field_zoo 2 · announcement 2 · product 4 · team 2 · category 4 · preference 1.
Values verbatim: f_multiselect ["red","green"], f_json {"nested":{"k":"v"},"list":[1,2,3]} structurally intact, f_lookup resolves to the real Northwind account id. Expected-absent fields (f_user, f_users, f_secret) null as documented.
201, new id ≠ source, sourceId echoed, values copied, created_at re-derived
7 clone RLS-gated
blocked(fixture)
member A can read showcase_account (public_read_write), so no non-reading persona is bound on this boot — the item explicitly allows recording this as blocked. A's clone returned 403 PERMISSION_DENIED (no create grant), which does not exercise the 404 path the clause is about
Reproduction rule for clause 2 — full write-up and causal chain in the #9296 comment:
DELETE /api/v1/data/showcase_account/<any id>
expected: 200, row gone.
actual: 400 {"code":"INVALID_FILTER","object":"showcase_account","error":"The bare equality spelling { \"f_lookups\": value } WAS NOT APPLIED: … Use \"$contains\" …"}, and a follow-up GET on the same id returns 200 — the row survives.
Cause: packages/objectql/src/engine.ts:10112 probes dependents with find(childName, { where: { [fieldName]: id } }) — bare equality — for every referencing field. showcase_field_zoo.f_lookups is Field.lookup('showcase_account', { multiple: true }), a JSON-valued column the engine's own #5869 filter guard refuses. The catch below rethrows anything that is not a missing-table error, so the probe failure aborts the delete.
Marked regression: before the #8895 hardening that catch swallowed probe failures, so this delete previously succeeded (while silently skipping the integrity check). The #8895 change is correct in itself — it converted a silent fail-open into a loud refusal, which is what made the pre-existing bad filter shape visible. The remedy is the one the error text itself gives: $contains for multi-value fields.
Scope measured, not assumed: showcase_team / showcase_category / showcase_announcement / showcase_private_note / showcase_project delete 200; showcase_product → 409 DELETE_RESTRICTED (correct guard); sys_user → 405 OBJECT_API_METHOD_NOT_ALLOWED, which is the only reason the multi-value Field.user columns (f_users, team_members) don't reach the same probe. On stock showcase the blast radius is exactly showcase_account; the framework rule is broader.
the 6-door publish matrix was not driven this round
1 credentialsRef alternative works
not-run
not driven
2 both read doors redact
pass
no cleartext in either door; /datasources/showcase_external → hasSecret:false, redactedConfigKeys:[]; metadata door shows config.filename only. The two doors agree
3 redaction presence-preserving
partial
only an unset-credential datasource exists on stock showcase, so the set-vs-unset distinction could not be exercised
4 legacy alias spellings redacted
blocked(fixture)
requires planting a stored row directly; not done — the item mandates recording this rather than passing it
5 unknown-driver canonical keys redacted
not-run
not driven
6 echoed-mask write guard
not-run
not driven
7 non-admin/anonymous get no more than admin
FAIL
anonymous responses are byte-identical to the platform admin's on both read doors; anonymous create returns 201 and anonymous delete 204
Reproduction withheld from this issue per the wave's rule for real access-control holes — it is on #9296 for the maintainer. Summary only: the datasource-admin family is mounted directly on IHttpServer by a CLI-built plugin, bypassing the @objectstack/restenforceAuth seam, with no guard in the registrar (packages/services/service-datasource/src/admin-routes.ts:113). The family's own ledger already describes this posture as "mounted, working, and guarded by nothing". #7744 closed having added ledger entries without adding a guard; I find no open issue tracking the authentication hole itself. Long-standing, therefore not labelled a regression.
Brand-new ADR-0123 item, never run before. Its own fixture precondition is not met on stock showcase, so every clause is vacuous — the item instructs confirming the posture before scoring, and that is what this verdict records.
Posture evidence, three independent sources agreeing:
Boot banner: Tenancy: single.
Runtime log, authoritative: [security] tenancy posture 'single' — Layer 0 is inert; the platform's own tenant-scoped RLS policies are stripped (ADR-0105 D3).
Source: tenant-layer.ts:119 — if (!postureEnforcesWall(input.tenancyPosture)) return null — single contributes no wall.
The state itself is also not producible here: a fresh signup lands with activeOrganizationId: "org_msxa3kt9sxctxowj" (the seeded Default Organization), so the racy producer the item names is unavailable and the removed-member producer needs a fixture nobody ships.
Corroboration rather than a verdict: plugin-security/src/no-active-organization-write-refusal.test.ts runs 19/19 green at this sha and pins every clause of this item at unit level — including a control that is precisely "the single posture has no wall, so an org-less write is ordinary", i.e. the test itself agrees the showcase configuration makes this vacuous.
Studio is not installed in the showcase. The boot plugin list carries @objectstack/setup and @objectstack/account but no studio plugin; GET /api/v1/meta/app as platform admin returns ["showcase_app","setup","account"], and the post-login screenshot independently shows "Your apps 2" (Showcase, Setup). STUDIO_APP exists (packages/platform-objects/src/apps/studio.app.ts, name: 'studio', requiredPermissions: ['studio.access']) and packages/apps/studio ships, but the showcase config does not install it. Every step of this item begins in Studio, so nothing is scoreable. Clause 5 (server-side read-only-package gate) is independently reachable via API and was not driven this round.
Remaining partials — what was and was not proved
2 write-path-guards → pass via the 3-file automation pin (17 tests: owner forge/disown/transfer, bulk update+delete scoping, static readonly stripping, parent-scoped readonlyWhen, X-ObjectStack-Dropped-Fields). Clause mapping inherited from the pin, not independently re-driven.
4 owd-sharing-matrix → pass via its 4-file pin (16 tests: private OWD, public-read OWD, controlled-by-parent both fixtures). Clause 4 deliberately not double-counted against the private D11 defect — the item's own negative says it is recorded once, on rls-both-sides.
3 crud-permission-matrix → the verify half is green (CRUD 15/0/0, RLS 0 holes, 9 position personas). Clause 6 requires a verdict per cell across ~31 access-matrix rows × 4 verbs with a member provisioned per permission set; not driven. Honest partial, not pass.
7 ai.mcp-stdio-fail-closed → the 4 ADR-0101 unit cases are green (keyless refusal, unresolvable key, objectql unavailable, no key demanded when stdio is off). The api clauses (member-keyed MCP reads vs REST row/field sets, aggregate parity, FLS aggregate-input gate, revocation on next read) were not driven — they need a real stdio transport with a provisioned API key.
8 api-backend.query-contract-matrix → driven live: $top/$skip window 200; $select narrows to exactly name,status; $orderby on a bad field → 400 INVALID_SORT; scalar comparand on a collection operator → 400 INVALID_FILTER; unknown $-param → 400 UNSUPPORTED_QUERY_PARAM; unknown field → 400 INVALID_FIELD; empty-list boundary {status:{$in:[]}} → 200/0 rows and {status:{$nin:[]}} → 200/18 rows (full visible set), both spellings. Clause 4 (expand RLS/FLS) rests on the engine pin. Clauses 0 and 8 (full operator matrix set-equality, verdict per variant) not driven → partial.
9 approvals.account-app-entry → clause 0 pass on the build oracle: grep -o 'approvals:inbox' packages/console/dist/assets/index-*.js hits in index-B1QKp1HQ.js, so the registration survives bundling — exactly what the item says the unit pin cannot see. Clauses 1–6 need the non-admin persona plus a drawer walk; not driven.
10 cli.dev-boot-contract → clause 0 pass (health/ready//_console); clause 4 pass — instance B on a busy port printed ↪ server bound to port 3111 (requested 3110) and the banner advertised 3111, no silent divergence; clause 6 pass — no config, no artifact exits 1 with the exact remedy Run in a directory with objectstack.config.ts, pass --artifact <path|url>, or run from the monorepo root. Clauses 1, 2, 3, 5 (seed idempotence under a changed --admin-password, the DB-resolution matrix, --fresh tempdir, stale-artifact warning) not driven.
16 builtin-apps-nav-render → clause 3 pass, server-side: member GET /api/v1/meta/app?id=setup → {"items":[]} while admin gets items:1; the member's app list is ["showcase_app","account"] vs the admin's ["showcase_app","setup","account"] — the server prunes, the launcher does not merely hide. Clause 4 pass (all Setup entries absent for the member; nav_jwks absent for admin too, per Setup › Advanced › Signing Keys (JWKS) is a dead nav entry — sys_jwks is API-disabled and a requiredPermissions gate cannot prune it #7544, so it proves nothing either way). Clause 1 partial — Setup's 9 group anchors and Account's Profile/Inbox/Security/Developer all present; Studio absent per item 18. Clauses 0/2/6 (per-destination render walk) not driven.
Checklist accuracy findings — for the ledger owner, not product defects
Each of these made a correct product behaviour look wrong on first read. Filed here rather than edited into the tree (this round commits nothing).
platform-core.seed-integrity step 1 states showcase_contact=33 (9 named + 24 bulk prospects). The seed authors 8 named + 24 = 32, and the server reports 32. The baseline is stale; the item's own instruction to re-derive from the seed module is what saved the clause from a false FAIL.
platform-core.boot-health step 6 / clause 3 and builtin-apps-nav-render steps 0–1 use GET /api/v1/meta/app?id=com.example.showcase (and ?id=com.objectstack.setup / .studio / .account). That parameter keys on the app name, not the package id — the showcase app is showcase_app, the built-ins are setup / studio / account. The documented spellings return {"type":"app","items":[]}, which reads exactly like "the app metadata is gone". The dogfood skill §1 carries the same shape and would benefit from the same correction.
records-forms.crud-roundtrip negative expects a direct POST missing required status to answer 400 VALIDATION_FAILED with code:'required'. showcase_account.status is required: truebut its prospect option carries default: true, so the default satisfies it and the row is created with status:"prospect". Correct behaviour; the negative as written can never hold for this object.
access-security.no-active-org-session-semantics carries no automated.ref, yet plugin-security/src/no-active-organization-write-refusal.test.ts pins every one of its clauses at unit level. Per RUNNER rule 6's converse, that test is the natural automated value for a future revision — and it would tell any runner up front that the showcase posture makes the item vacuous.
access-security.rls-both-sides clause 4 and crud-permission-matrix both name runRlsProofs as the oracle without noting it requires objectstack verify --rls. Bare verify prints no RLS section, so the flag is load-bearing.
Fixture gaps
tenancy: single on stock showcase makes the whole ADR-0123 tenant-semantics surface unscoreable (item 6). A walled fixture — or a documented isolated/group boot recipe — is what would unblock it.
Studio is not installed in the showcase, so item 18 and the Studio third of item 16 have no surface.
No non-reading persona is bound on a stock boot, so the clone-RLS clause (17/7) cannot be exercised; showcase_account is public_read_write.
integration-system.… clauses 3 and 4 need a datasource with a bound credential and a directly-planted legacy-alias row; neither exists on stock showcase.
Two authored sharing rules (share_open_tasks_with_manager, share_red_projects_with_execs) fail backfill at every boot with SHARING_NOT_ENABLED — they are inert fixtures.
Anti-false-positive work this round
Three findings were disproven before they reached this issue, which is the protocol earning its cost:
"31 e2e surfaces failed" — Playwright/chromium build mismatch, not the product. 31/31 pass with the right executablePath.
"showcase_reassign_wizard was deleted / is missing" — my parser read f.name from data.flows, which is an array of strings. The flow is present among 29.
"$nin: [] is rejected 400" — I had spelled the operator "not in", which is not in the recognised set; $nin / nin both answer 200 with the full visible set.
Conversely, both real failures survived: reproduced on two independent boots each with fresh DBs and distinct ports, driven with plain curl (no browser, so no automation-input/hydration-race exposure), wire-level proof of no credentials on the auth-hole probes, every listed trap ruled out, and a second independent re-derivation from the captured evidence and source alone before either was written down (RUNNER rule 7, which applies to all 18 items here since every one is P0).
Round R1 of the platform-checklist wave tracked at #9296 — the release-gating smoke set, run locally in the R0-provisioned tree to answer "is anything release-blocking broken?" ahead of the three cloud sessions.
Two P0 failures found. Both were surfaced on #9296 the moment they survived second derivation, rather than held to round end:
multiple: truereference — a regression.Environment fingerprint
e4e5c6e3c608b1b807c83a0d5b734f213eb1a1dd— not moved.objectui-sha(pin)665661ab093263f39f2e660a295ea615dbcee35a665661ab093263f39f2e660a295ea615dbcee35a— stamp == pin,/_console/served 200text/htmlfbd1c0491215406c379405cef262befad1de458d(verified withgit rev-parse FETCH_HEAD; in the merge queue at run time, not yet onmain).docs/qa/platform-checklist/is byte-identical to the earlierd23a746c7— the newer commit only mergesmain— so the ledger ran is exactly the reviewed onecheck-platform-checklist: OK — 15 areas, 190 items (190 active); coverage: 30 kinds mapped, 0 waivednode scripts/checklist-select.mjs priority:P0 --json→ 18 runnable items (16 → 18 after the R4 sweep, as predicted)file:/tmp/qa-wave/r1-core.db· 3111r1-portshift.db· 3112r1-del.db(band 3110–3129, ≤3 concurrent)--seed-admin→admin@objectos.ai / admin123; membersrls-a@verify.test,rls-b@verify.testgit statusshows onlydocs/qa/**. No JSON, no screenshots, no PRBoot: health 200,
/api/v1/ready→{"status":"ready","state":"running"},Seeds: com.example.showcase 130 rows, 0 ERROR lines.Environment correction worth carrying forward: Playwright 1.62.1 expects chromium build 1234; this box has only
chromium-1194. The first e2e attempt reported 31 failed — pure environment, zero product signal. PassinglaunchOptions.executablePath=/opt/pw-browsers/chromium-1194/chrome-linux/chrometurns it into 31 passed. Any later round that runs Playwright here must set this or it will file a spectacular false P0.Scope — per-item revision and derived verdict
access-security.rls-both-sidesaccess-security.write-path-guardsaccess-security.crud-permission-matrixaccess-security.owd-sharing-matrixaccess-security.anonymous-deny-surfacesaccess-security.no-active-org-session-semanticsai.mcp-stdio-fail-closedapi-backend.query-contract-matrixapprovals.account-app-entrycli.dev-boot-contractintegration-system.datasource-credential-refusal-matrixplatform-core.boot-healthplatform-core.seed-integrityplatform-core.console-loginplatform-core.nav-surfaces-renderplatform-core.builtin-apps-nav-renderrecords-forms.crud-roundtripstudio-authoring.first-run-loopPer-clause verdicts (text oracle evidence)
Automated refs run and cited (RUNNER rule 6) — 8 items
packages/mcp/src/__tests__/plugin.test.tspackages/objectql/src/engine.test.tsexamples/app-showcase/e2e/showcase-smoke.spec.tsobjectstack verifyobjectstack verify --rlspackages/plugins/plugin-security/src/no-active-organization-write-refusal.test.tsobjectstack verify --rlsis a separate invocation. R0's reading (and my first pass) ran bareobjectstack verify, which emits no RLS section at all —runRlsProofsonly runs behind the--rlsflag. Any round citing plainverifyas the oracle for an RLS clause has not actually consulted it.1 ·
access-security.rls-both-sides(rev 3) → partialtotal=2 ["A note 1","A note 2"]; B listtotal=1 ["B note 1"]— presence and absence, both directions404 RECORD_NOT_FOUND(not-found-shaped denial, permitted)403 PERMISSION_DENIED; admin re-readbody="b1",updated_atunchanged from creationtotal=3— all three run-created idsrunRlsProofsno holes20 PROVEN (20 consistent, 0 HOLES); all 9 position personas 0 HOLESClause 5: the 3 skips are each benign —
showcase_project_membership"no plain-text probe field",showcase_ext_customer/showcase_ext_orderexternal read-only. Hand spot-check of the skipped object: member A →403 PERMISSION_DENIED, admin sees rows, i.e. genuinely protected. But the clause requires judging against a write-up held privately with the maintainer and deliberately absent from this repo — this runner does not hold it, and the clause says never to tick it green over that. Recordedblocked(dependency), notpass. Nothing about it is restated here.5 ·
access-security.anonymous-deny-surfaces(rev 1) → passThe dogfood pin covers
/data,/meta,/actions,/automation,/packages(25 tests). It does not cover/batchor/security/explain, both named in the item's variants — driven by hand so the item is not left partial:GET /api/v1/data/showcase_private_noteGET /api/v1/metaPOST /api/v1/actions/showcase_task/showcase_mark_done/anon-probe-idGET /api/v1/automationPOST /api/v1/batchGET /api/v1/security/explainExactly the two declared families, mapped as the item specifies — no third dialect. Member contrast: data + meta both 200. Declared-public route: anonymous
GET /api/v1/forms/<slug>→ 404FORM_NOT_FOUND, not 401 — the gate keys on declared posture, not blanket. Destructive negative: anonymousDELETE /api/v1/automation/showcase_reassign_wizard→ 401, and an authed listing still shows the flow among 29.12 ·
platform-core.boot-health(rev 2) → passhealth 200 ·
/ready200{"status":"ready","state":"running"}· Flows banner29 flow(s) 22 bound … · 7 draftwith no ⚠ and 0 ERROR lines ·/_console/200text/html6467 bytes · nav diff empty — servedgrp_datachildren equal the 12 authored insrc/ui/apps/index.ts, nothing added or dropped.3 WARN lines exist, none of them a SeedLoader rejection: one is the #8686 autonumber tenancy note (which fires at 13:37:39, 3 s before the default organization row is created at 13:37:42 — the "0 rows in sys_organization" it reports is a boot-ordering artifact, not the steady state), and two are
SHARING_NOT_ENABLEDrule-backfill failures forshare_open_tasks_with_manager/share_red_projects_with_execs. Those two mean the showcase ships two authored sharing rules that can never be consulted — noted as a fixture observation, not scored here.13 ·
platform-core.seed-integrity(rev 1) → passCounts re-derived from
src/data/seed/index.tsrather than trusted from the item's baseline. All match, and are identical after a restart against the same DB (idempotent upsert,130 rowsboth boots, no rejection lines): account 14 · contact 32 · project 5 · task 10 · invoice 12 · invoice_line 5 · expense_report 4 · expense_line 13 · field_zoo 2 · announcement 2 · product 4 · team 2 · category 4 · preference 1.Values verbatim:
f_multiselect ["red","green"],f_json {"nested":{"k":"v"},"list":[1,2,3]}structurally intact,f_lookupresolves to the real Northwind account id. Expected-absent fields (f_user,f_users,f_secret) null as documented.14 ·
platform-core.console-login(rev 2) → pass/_console/home200 GET /api/v1/runtime/config,200 /auth/get-session,200 /i18n/translations/en; no bounce to loginclearCookies(), in-pagefetch /api/v1/meta/app→ 401 UNAUTHENTICATED/_console/login?redirect=%2Fapps%2Fshowcase_app%2Fshowcase_project, login surface rendered — not a dead shell/_console/home, authed fetch → 200401 POST /api/v1/auth/sign-in/email, no cookies set, visible error "Invalid email or password"0 page errors across the whole drive.
17 ·
records-forms.crud-roundtrip(rev 3) → FAIL201,name/status/industryreturned verbatim["updated_at","annual_revenue"];null → 123456400 INVALID_FILTER; row survivesos-qa-r1-华宁read back byte-identical201, new id ≠ source,sourceIdechoed, values copied,created_atre-derivedshowcase_account(public_read_write), so no non-reading persona is bound on this boot — the item explicitly allows recording this as blocked. A's clone returned403 PERMISSION_DENIED(no create grant), which does not exercise the 404 path the clause is aboutstatustax_id400 VALIDATION_FAILED,fields:[{field:"tax_id",code:"invalid_format"}]Reproduction rule for clause 2 — full write-up and causal chain in the #9296 comment:
400 {"code":"INVALID_FILTER","object":"showcase_account","error":"The bare equality spelling { \"f_lookups\": value } WAS NOT APPLIED: … Use \"$contains\" …"}, and a follow-upGETon the same id returns 200 — the row survives.Cause:
packages/objectql/src/engine.ts:10112probes dependents withfind(childName, { where: { [fieldName]: id } })— bare equality — for every referencing field.showcase_field_zoo.f_lookupsisField.lookup('showcase_account', { multiple: true }), a JSON-valued column the engine's own #5869 filter guard refuses. Thecatchbelow rethrows anything that is not a missing-table error, so the probe failure aborts the delete.Marked
regression: before the #8895 hardening thatcatchswallowed probe failures, so this delete previously succeeded (while silently skipping the integrity check). The #8895 change is correct in itself — it converted a silent fail-open into a loud refusal, which is what made the pre-existing bad filter shape visible. The remedy is the one the error text itself gives:$containsfor multi-value fields.Scope measured, not assumed:
showcase_team/showcase_category/showcase_announcement/showcase_private_note/showcase_projectdelete 200;showcase_product→409 DELETE_RESTRICTED(correct guard);sys_user→405 OBJECT_API_METHOD_NOT_ALLOWED, which is the only reason the multi-valueField.usercolumns (f_users,team_members) don't reach the same probe. On stock showcase the blast radius is exactlyshowcase_account; the framework rule is broader.11 ·
integration-system.datasource-credential-refusal-matrix(rev 1) → FAILcredentialsRefalternative works/datasources/showcase_external→hasSecret:false,redactedConfigKeys:[]; metadata door showsconfig.filenameonly. The two doors agreeReproduction withheld from this issue per the wave's rule for real access-control holes — it is on #9296 for the maintainer. Summary only: the datasource-admin family is mounted directly on
IHttpServerby a CLI-built plugin, bypassing the@objectstack/restenforceAuthseam, with no guard in the registrar (packages/services/service-datasource/src/admin-routes.ts:113). The family's own ledger already describes this posture as "mounted, working, and guarded by nothing". #7744 closed having added ledger entries without adding a guard; I find no open issue tracking the authentication hole itself. Long-standing, therefore not labelled a regression.6 ·
access-security.no-active-org-session-semantics(rev 1) → blocked(fixture)Brand-new ADR-0123 item, never run before. Its own fixture precondition is not met on stock showcase, so every clause is vacuous — the item instructs confirming the posture before scoring, and that is what this verdict records.
Posture evidence, three independent sources agreeing:
Tenancy: single.[security] tenancy posture 'single' — Layer 0 is inert; the platform's own tenant-scoped RLS policies are stripped (ADR-0105 D3).tenant-layer.ts:119—if (!postureEnforcesWall(input.tenancyPosture)) return null—singlecontributes no wall.The state itself is also not producible here: a fresh signup lands with
activeOrganizationId: "org_msxa3kt9sxctxowj"(the seeded Default Organization), so the racy producer the item names is unavailable and the removed-member producer needs a fixture nobody ships.Corroboration rather than a verdict:
plugin-security/src/no-active-organization-write-refusal.test.tsruns 19/19 green at this sha and pins every clause of this item at unit level — including a control that is precisely "thesingleposture has no wall, so an org-less write is ordinary", i.e. the test itself agrees the showcase configuration makes this vacuous.18 ·
studio-authoring.first-run-loop(rev 1) → blocked(fixture)Studio is not installed in the showcase. The boot plugin list carries
@objectstack/setupand@objectstack/accountbut no studio plugin;GET /api/v1/meta/appas platform admin returns["showcase_app","setup","account"], and the post-login screenshot independently shows "Your apps 2" (Showcase, Setup).STUDIO_APPexists (packages/platform-objects/src/apps/studio.app.ts,name: 'studio',requiredPermissions: ['studio.access']) andpackages/apps/studioships, but the showcase config does not install it. Every step of this item begins in Studio, so nothing is scoreable. Clause 5 (server-side read-only-package gate) is independently reachable via API and was not driven this round.Remaining partials — what was and was not proved
write-path-guards→ pass via the 3-file automation pin (17 tests: owner forge/disown/transfer, bulk update+delete scoping, static readonly stripping, parent-scopedreadonlyWhen,X-ObjectStack-Dropped-Fields). Clause mapping inherited from the pin, not independently re-driven.owd-sharing-matrix→ pass via its 4-file pin (16 tests: private OWD, public-read OWD, controlled-by-parent both fixtures). Clause 4 deliberately not double-counted against the private D11 defect — the item's own negative says it is recorded once, onrls-both-sides.crud-permission-matrix→ theverifyhalf is green (CRUD 15/0/0, RLS 0 holes, 9 position personas). Clause 6 requires a verdict per cell across ~31 access-matrix rows × 4 verbs with a member provisioned per permission set; not driven. Honestpartial, notpass.ai.mcp-stdio-fail-closed→ the 4 ADR-0101 unit cases are green (keyless refusal, unresolvable key, objectql unavailable, no key demanded when stdio is off). Theapiclauses (member-keyed MCP reads vs REST row/field sets, aggregate parity, FLS aggregate-input gate, revocation on next read) were not driven — they need a real stdio transport with a provisioned API key.api-backend.query-contract-matrix→ driven live:$top/$skipwindow 200;$selectnarrows to exactlyname,status;$orderbyon a bad field →400 INVALID_SORT; scalar comparand on a collection operator →400 INVALID_FILTER; unknown$-param →400 UNSUPPORTED_QUERY_PARAM; unknown field →400 INVALID_FIELD; empty-list boundary{status:{$in:[]}}→ 200/0 rows and{status:{$nin:[]}}→ 200/18 rows (full visible set), both spellings. Clause 4 (expand RLS/FLS) rests on the engine pin. Clauses 0 and 8 (full operator matrix set-equality, verdict per variant) not driven →partial.approvals.account-app-entry→ clause 0 pass on the build oracle:grep -o 'approvals:inbox' packages/console/dist/assets/index-*.jshits inindex-B1QKp1HQ.js, so the registration survives bundling — exactly what the item says the unit pin cannot see. Clauses 1–6 need the non-admin persona plus a drawer walk; not driven.cli.dev-boot-contract→ clause 0 pass (health/ready//_console); clause 4 pass — instance B on a busy port printed↪ server bound to port 3111 (requested 3110)and the banner advertised 3111, no silent divergence; clause 6 pass — no config, no artifact exits 1 with the exact remedyRun in a directory with objectstack.config.ts, pass --artifact <path|url>, or run from the monorepo root.Clauses 1, 2, 3, 5 (seed idempotence under a changed--admin-password, the DB-resolution matrix,--freshtempdir, stale-artifact warning) not driven.builtin-apps-nav-render→ clause 3 pass, server-side: memberGET /api/v1/meta/app?id=setup→{"items":[]}while admin getsitems:1; the member's app list is["showcase_app","account"]vs the admin's["showcase_app","setup","account"]— the server prunes, the launcher does not merely hide. Clause 4 pass (all Setup entries absent for the member;nav_jwksabsent for admin too, per Setup › Advanced › Signing Keys (JWKS) is a dead nav entry —sys_jwksis API-disabled and arequiredPermissionsgate cannot prune it #7544, so it proves nothing either way). Clause 1 partial — Setup's 9 group anchors and Account's Profile/Inbox/Security/Developer all present; Studio absent per item 18. Clauses 0/2/6 (per-destination render walk) not driven.Checklist accuracy findings — for the ledger owner, not product defects
Each of these made a correct product behaviour look wrong on first read. Filed here rather than edited into the tree (this round commits nothing).
platform-core.seed-integritystep 1 statesshowcase_contact=33 (9 named + 24 bulk prospects). The seed authors 8 named + 24 = 32, and the server reports 32. The baseline is stale; the item's own instruction to re-derive from the seed module is what saved the clause from a false FAIL.platform-core.boot-healthstep 6 / clause 3 andbuiltin-apps-nav-rendersteps 0–1 useGET /api/v1/meta/app?id=com.example.showcase(and?id=com.objectstack.setup/.studio/.account). That parameter keys on the app name, not the package id — the showcase app isshowcase_app, the built-ins aresetup/studio/account. The documented spellings return{"type":"app","items":[]}, which reads exactly like "the app metadata is gone". The dogfood skill §1 carries the same shape and would benefit from the same correction.records-forms.crud-roundtripnegative expects a direct POST missing requiredstatusto answer400 VALIDATION_FAILEDwithcode:'required'.showcase_account.statusisrequired: truebut itsprospectoption carriesdefault: true, so the default satisfies it and the row is created withstatus:"prospect". Correct behaviour; the negative as written can never hold for this object.access-security.no-active-org-session-semanticscarries noautomated.ref, yetplugin-security/src/no-active-organization-write-refusal.test.tspins every one of its clauses at unit level. Per RUNNER rule 6's converse, that test is the naturalautomatedvalue for a future revision — and it would tell any runner up front that the showcase posture makes the item vacuous.access-security.rls-both-sidesclause 4 andcrud-permission-matrixboth namerunRlsProofsas the oracle without noting it requiresobjectstack verify --rls. Bareverifyprints no RLS section, so the flag is load-bearing.Fixture gaps
tenancy: singleon stock showcase makes the whole ADR-0123 tenant-semantics surface unscoreable (item 6). A walled fixture — or a documentedisolated/groupboot recipe — is what would unblock it.showcase_accountispublic_read_write.integration-system.…clauses 3 and 4 need a datasource with a bound credential and a directly-planted legacy-alias row; neither exists on stock showcase.share_open_tasks_with_manager,share_red_projects_with_execs) fail backfill at every boot withSHARING_NOT_ENABLED— they are inert fixtures.Anti-false-positive work this round
Three findings were disproven before they reached this issue, which is the protocol earning its cost:
executablePath.showcase_reassign_wizardwas deleted / is missing" — my parser readf.namefromdata.flows, which is an array of strings. The flow is present among 29.$nin: []is rejected 400" — I had spelled the operator"not in", which is not in the recognised set;$nin/ninboth answer 200 with the full visible set.Conversely, both real failures survived: reproduced on two independent boots each with fresh DBs and distinct ports, driven with plain
curl(no browser, so noautomation-input/hydration-raceexposure), wire-level proof of no credentials on the auth-hole probes, every listed trap ruled out, and a second independent re-derivation from the captured evidence and source alone before either was written down (RUNNER rule 7, which applies to all 18 items here since every one is P0).Parent: #9296 · baseline R0: #9298 · spec sweep: #9299 / PR #9309.