Repository navigation
QA run · priority:P0 (23/23) · e5f194ba · 2026-10-22 · 19 PASS / 3 PARTIAL / 1 FAIL / 0 BLOCKED / 0 NOT-RUN #25227
Description
Pre-release P0 smoke for 18.1.0, run on latest main at the maintainer's request before merging the Version Packages PR (#24876). One selector (priority:P0), one record. The per-clause verdict tables are in the five comments below this body, one per runner group (an issue body has a size cap); this body carries the fingerprint, scope, the derived item verdicts, the one FAIL's reproduction rule, the findings and the probe line.
Environment fingerprint
- framework
e5f194ba13d8d9a2818c1ece98a9c206fa0c7623(origin/main at 2026-10-22 12:23Z);.objectui-shacd14d8eeffe11ae03b6acf82e663c1d293dfdf5f - tree:
pnpm install --frozen-lockfile, thenpnpm build --concurrency=2(76/76 tasks green, 7m15s).node scripts/check-dev-prereqs.mjsgreen. - console:
pnpm objectui:buildafter the framework build, never in parallel, withOBJECTUI_DEPS_BUILD_CMD='pnpm exec turbo run build --concurrency=2 --filter=@object-ui/console^...'. The first attempt died with a V8 heap OOM (exit 134 in@object-ui/app-shell#build, "Ineffective mark-compacts near heap limit") at node's default heap; the re-run withNODE_OPTIONS=--max-old-space-size=6144succeeded.dist/.objectui-sha= pin;pnpm check:console-shapasses. - container: 4 vCPU, 15 GiB; Chromium 141 headless at
/opt/pw-browsers/chromium(playwright-core 1.63.0). - boots: stock
examples/app-showcase,OS_PORT=<port> objectstack dev --ui --seed-admin -p <port> -d file:(scratch)/<run>.dbper runner, each runner on its own ports (476xx) and file DB, started withsetsidand stopped by process group. Walled boot (OS_TENANCY_POSTURE=isolated) from a scratch copy declaring@objectstack/organizations;os startboots for the MCP stdio item; v17 and 18.0.0-next.5 fixtures for the ceremony item, built from npm packages. Timestamps per runner are in the comments. - execution: five runner agents in parallel (security · api/automation · cli/mcp · boot/console/nav · approvals/crud/studio), plus a sixth, independent agent for the rule-7 re-derivation of the one FAIL.
git statusstayed clean;examples/app-showcase/.objectstack/installed-packages/never existed.
Scope
Selector priority:P0 → 23 runnable items, judged 23 of 23. Hidden (blocked, not run): access-security.no-active-org-session-semantics. No planned items matched.
| item | rev | surface | verdict | driven by |
|---|---|---|---|---|
| access-security.rls-both-sides | 7 | api | PARTIAL | pins + objectstack verify --rls + live; acceptance[5] D11 spot-check blocked(dependency) per its knownGaps |
| access-security.write-path-guards | 6 | api | PASS | pins + live (pins don't reach A4, bulk half of A5, A6) |
| access-security.crud-permission-matrix | 7 | api | PARTIAL | pins + live delegate × sys_* rows; acceptance[5] blocked(fixture) per its knownGaps |
| access-security.owd-sharing-matrix | 4 | api | PASS | pins + live, all four models |
| access-security.anonymous-deny-surfaces | 3 | api | PASS | pin + live, all seven families |
| access-security.walled-environment-metadata-operator-only | 5 | api | PASS | pins + a provisioned walled isolated boot (grant row placed outside HTTP, as the item's fixture prescribes) |
| ai.mcp-stdio-fail-closed | 6 | cli | PASS | pin + live os start boots with a stdio client |
| api-backend.query-contract-matrix | 5 | api | PASS | live (engine pins reach only the engine layer) |
| api-backend.packaged-action-disabled-dispatch | 6 | api | PASS | pins + live, incl. restart and live MCP run_action |
| approvals.account-app-entry | 4 | browser | FAIL | manual browser; re-derived independently (rule 7) |
| automation.packaged-flow-disable-durable | 5 | api | PASS | live, cold restart over the same file DB |
| cli.dev-boot-contract | 7 | cli | PASS | live |
| cli.organization-ownership-ceremony | 4 | cli | PASS | pins + v17 and 18.0.0-next.5 fixtures |
| identity-auth.default-organization-boot-invariant | 2 | api | PASS | pins + live |
| integration-system.datasource-credential-refusal-matrix | 5 | mixed | PASS | live, scratch PostgreSQL 16 for the connect leg |
| platform-core.boot-health | 9 | mixed | PASS | live |
| platform-core.seed-integrity | 4 | api | PASS | live, incl. a restart |
| platform-core.console-login | 4 | browser | PASS | manual browser |
| platform-core.nav-surfaces-render | 6 | browser | PASS | pin showcase-smoke.spec.ts 49/49 on own boot |
| platform-core.builtin-apps-nav-render | 7 | browser | PARTIAL | manual browser; Browse Marketplace catalog blocked(environment): egress to cloud.objectos.ai denied, server answers 502 MARKETPLACE_PROXY_FAILED with a named error state |
| platform-core.v18-boot-refusals | 6 | cli | PASS | pins + live |
| records-forms.crud-roundtrip | 13 | browser | PASS | manual browser + API |
| studio-authoring.first-run-loop | 6 | mixed | PASS | manual browser + API |
Derived: 19 PASS / 3 PARTIAL / 1 FAIL / 0 BLOCKED / 0 NOT-RUN. All three PARTIALs are the gaps their own items already record (two fixture debts, one sandbox egress block); no clause in them failed.
FAIL — approvals.account-app-entry acceptance[3] (release-blocking regression)
A non-admin pending approver who opens a request from the account app's Approvals inbox gets a blank request page: no fields, no decision actions, no can_act=false explanation. The admin opening the same URL gets the full page. The inbox's in-row Approve/Reject still works (server effects verified), so a non-admin can decide from the list, but Reassign, Send back, Request info, Reply and the request context are unreachable for them.
Reproduction rule (fresh showcase boot, own port and DB):
- As admin:
POST /api/v1/auth/organization/invite-member {"email":"E","role":"member"}; thenPOST /api/v1/auth/sign-up/email {"email":"E","password":"P","name":"QA Approver"}; resolve the user id U withGET /api/v1/data/sys_user?email=E;POST /api/v1/data/sys_user_position {"user_id":U,"position":"contributor","organization_id":<default org>}and the same with"position":"finance". - Launch a NEW sign-off after the positions exist: as E,
POST /api/v1/data/showcase_invoice {"name":"INV-X","owner":"E","account":<seeded account id>}, thenPATCH /api/v1/data/showcase_invoice/<id> {"status":"sent","issued_on":"2026-10-22"}(or have the admin submit one; both shapes reproduce).GET /api/v1/approvals/requests/R→ 200 withviewer.can_act: trueand U inpending_approvers. - Sign E in through the console form; Avatar › Profile → sidebar Inbox › Approvals (
/_console/apps/com.objectstack.account/component/approvals/inbox); click the row, or press Enter on it, or cold-load…/inbox?request=R. - Expected: the request's page with its fields and the declared decision action set. Actual:
/_console/apps/com.objectstack.account/sys_approval_request/record/Rshows only the heading "Approval Request areq_…", a Flow-initiated chip and empty Details/Timeline tabs; the header carries only "…" and refresh. - Control: the admin on the same URL sees "Invoice Dual Sign-off (parallel approval)" with its fields and Approve / Reject / Reassign / Send back / Request info / Recall / Reply.
Rule-2 / rule-7: reproduced 5× by the first runner (fresh contexts, form sign-in, clicks and direct URL loads, 7–8 s settle) and 3× by an independent second runner on its own boot with its own persona and two new requests (one the persona submitted, one the admin submitted). Identity proven per context with get-session; console stamp = pin; no coordinate clicks.
Mechanism (second runner, network trace + source at the pin): every request the page makes answers 2xx — GET /api/v1/approvals/requests/R 200 (viewer.can_act: true), GET /api/v1/approvals/requests/R/actions 200, GET /api/v1/meta/object/sys_approval_request and …/sys_approval_action 200, two POST /api/v1/security/explain 200. The console never calls the data door for the request. In objectui at cd14d8eeffe1, RecordDetailView.tsx folds the row the approvals door returned through withoutDeniedFields against /auth/me/permissions (readablePageRecord = readableRow(...)), and MePermissionsProvider.checkField answers false for any object the permissions answer omits; the persona's answer has no sys_approval_request entry (by design: the object is engine-owned, ADR-0103, and request-read-gate.ts / approvals-plugin.ts keep data-door read closed to non-administrators). So every field but id is dropped, including viewer and status, and every action's visible predicate on record.viewer.can_act fails. Causal check: rewriting only the persona's /auth/me/permissions response in the browser to include read on sys_approval_request / sys_approval_action made the same page render fully with the right action set. AppContent.approvalRecordReads.test.tsx stubs MePermissionsProvider, which is why objectui's tests miss it.
Regression: yes, against 18.0.0. 18.0.0 shipped objectui 616df6598b42 (the published @objectstack/console@18.0.0 dist stamp), which still opened requests in the side sheet; the 18.0 sweep's run #24542 passed this clause with the same persona shape. The side sheet was replaced by the served record page in objectui a91f6bbb1 (objectui#12484, 2026-10-21), which entered this repo's pin with #25160 (cf153eb2e535 → 56cf4d8e89b7; cf153eb2e535 does not contain it, 56cf4d8e89b7 does). Disposition: product defect, fix belongs in objectui; granting sys_approval_request read to members would hide the symptom by opening a door the platform keeps closed on purpose.
Findings outside the clauses
- Data API paging (pre-existing, not a regression):
GET /api/v1/data/showcase_account?$skip=-5(and?$skip=-1&$top=3, andPOST …/query {"offset":-5}) answer 500 INTERNAL_ERROR (server log: the SQL builder's "A non-negative integer must be provided to offset");?$top=abc,?$top=-1andPOST …/query {"limit":-1}answer 200 as if no limit was sent, while the POST door refuses{"limit":"ten"}400 VALIDATION_FAILED. Cause:packages/metadata-protocol/src/protocol.tscoerces withNumber(options.limit)/Number(options.offset)and never range-checks. Re-measured by the run lead on a separate boot. The same two lines are in the published@objectstack/metadata-protocol@18.0.0, so 18.0.0 behaves the same. Extracted as its own card (below). - Authorization-adjacent, low severity: one observation beside
ai.mcp-stdio-fail-closedacceptance[5]'s doors (field-level security, no value leaked) — detail withheld pending maintainer. It moves no verdict. - Transient: one
showcase-smoke.spec.tssurface (Hours by Status (Chart)) died once with PlaywrightTarget crashedbefore any assertion; it did not reproduce (6/6 twice in isolation, 49/49 on a full re-run). Treated as environment.
Checklist-accuracy findings and fixture gaps (for the wave anchor; not extracted)
- approvals.account-app-entry: steps[5–6] and acceptance[1], [3], [4] still say "drawer"; since objectui#12484 the surface is the served
sys_approval_requestrecord page, and the inbox also has in-row and bulk Approve/Reject, so acceptance[3] should name which surface it scores. - platform-core.boot-health acceptance[2] quotes the deployment-policy flow line with ⚠; the banner now prints it with ℹ.
- platform-core.builtin-apps-nav-render steps[5] describes Account › Approvals as
sys_approval_request/my_pending; it is served as theapprovals:inboxcomponent. - platform-core.nav-surfaces-render steps[0] says
test:smoke, which runs every spec ine2e/, not only the pin;examples/app-showcase/test-results/is not git-ignored, so a run without--outputleaves untracked files. - records-forms.crud-roundtrip acceptance[4]: no step sets a date (the localized-date half was checked on a seeded date field); acceptance[6]:
f_formulacannot show re-derivation with unchanged inputs, onlyf_autonumberdoes. - studio-authoring.first-run-loop steps[3]: the Changes panel goes straight to "Publish all".
- automation.packaged-flow-disable-durable: the urgent
showcase_taskcreate needs aproject(400 without it); the flow runs list capslimitat 100 (limit=200answers 400, which can read as "no runs"). - integration-system.datasource-credential-refusal-matrix steps[0]/knownGaps: with
driver:'mongo'plusconfig.password, the credential refusal is reported, not the name refusal; the name refusal appears only with no credential key. - platform-core.seed-integrity:
f_passwordreads back masked and should be listed as expected-masked. - api-backend.query-contract-matrix: no stock persona fits the contributor fixture; the runner minted one (a fixture of its own making).
- ai.mcp-stdio-fail-closed:
os startlogs atwarnby default, so its INFO-line oracles need--log-level info. - cli.organization-ownership-ceremony: on a v17 DB the apply blocks at
rewriteuntil a v18 boot has been refused once (not printed by the plan); the ceremony needs the deployment's auth composition (OS_AUTH_SECRETor--dev) or it blocks atauthored-conversion; on a raw v17 copyrefused_comment_reactions_unconvertedcomes first, andcomment-reactions --applyneeds--yesnon-interactively. Fixture gaps: the v17 DB came fromcli@17.7.0on a minimal app, and the next.5 fixture had no pending mail jobs (jobsRewritten 0). - platform-core.v18-boot-refusals: the SecurityPlugin rig is refused earlier by the "mounts no auth" gate; the
createIdentityObjectsPlugin()refusal is reached withos serve --dev --no-server --preset minimal. - access-security.write-path-guards and owd-sharing-matrix: their pins do not reach several clauses (write-path A4, bulk half of A5, A6; owd A2, A3, A5 on stock grants) — candidates for pin promotion.
- access-security.crud-permission-matrix knownGaps calls a baseline member's
/data/sys_userread "self-scoped"; measured, it lists every user of the single organization (org-scoped). Wording to check. objectstack verify --rlslistsshowcase_announcement(public_read) as "probe cannot read"; that is the probe persona's owner-scoped policy, not an OWD contradiction, but reads like one.- RUNNER environment facts worth adding:
pnpm objectui:buildneeds a larger V8 heap here (NODE_OPTIONS=--max-old-space-size=6144); on the sqlite-wasm driver a hand-placed grant row must be written while the server is stopped, or the server's next save overwrites it; a selective, non-reproducing PlaywrightTarget crashedis renderer-level, not a chart defect. - showcase fixture: the boot warns that sharing rule
share_new_inquiries_with_field_ops(business unitbu_field_ops) expands to no recipients on a fresh boot.
Off-clause probe line (rule 8)
About 60 runner-minutes in total, after the clauses had verdicts, as admin, plain members, contributor/liaison/approver personas, org owner and operator on the walled boot, and anonymous. Doors: data CRUD and query (empty / missing / unknown / negative / non-numeric values on ids and paging params), auth sign-in (empty email, missing password, unknown email, 32 bad-credential combinations, bearer after sign-out), MCP stdio admission (empty, whitespace and osk_ keys, every enable-switch spelling), approvals act/read as non-participant and with a forged actor, walled-boot doors beyond the clauses (draft, rollback, dashboard, translation writes, package PATCH, automation clone/delete), studio clone with unknown/empty ids, writes into the read-only showcase package. Found: the paging defect above, the withheld low-severity observation above, and a cosmetic one (record History shows a currency change raw, "— → 1234567", while the header shows "$1,234,567.00"). No existence leak on foreign vs nonexistent ids, no account enumeration on sign-in.
Handover
None — every selected item has a verdict.
Activity
os-sales commentedon Oct 22, 2026
Per-clause verdicts 1/5 — access-security (6 items)
4 PASS / 2 PARTIAL / 0 FAIL / 0 BLOCKED / 0 NOT-RUN. The two PARTIALs come only from legs the items themselves record as unreachable (
fixtures.knownGaps).Environment
single):OS_PORT=47601 setsid pnpm -C examples/app-showcase exec objectstack dev --ui --seed-admin -p 47601 -d file:(scratch)/main.db. Started 2026-10-22T12:46:30Z, bannerTenancy: single, stopped through its process group at 12:57Z.isolated): a scratch full copy of examples/app-showcase; every dependency alink:to its workspace or store path, plus@objectstack/organizations→link:packages/plugins/organizations, installed withpnpm install --ignore-workspace --offlineinside the scratch dir only. Booted withOS_TENANCY_POSTURE=isolated OS_PLATFORM_OWNER_EMAIL=admin@objectos.ai OS_AUTH_MEMBERSHIP_POLICY=invite-only … objectstack dev --ui --seed-admin -p 47602. Started 12:58:06Z. BannerTenancy: isolated,Organizationsmounted, nothing DEGRADED;/auth/configreportedtenancyPosture:"isolated", multiOrgEnabled:true, degradedTenancy:false. Restarted once around a fixture write (below); stopped at 13:00:37Z.GET /auth/get-sessionbefore use (auth-state-leak trap).@objectstack/verify'sbootStack, the fullservecomposition answered through Honoapp.requestin-process (packages/verify/src/harness.ts): the HTTP layer without a socket. The objectql pins are the engine layer. The rest and runtime walled pins are plugin/domain unit layer, which is why the walled item's live legs were also driven.Pin results (all
--maxWorkers=1):Test Files 13 passed (13) · Tests 226 passed (226).Test Files 3 passed (3) · Tests 63 passed (63)(wall-environment-metadata-refusal 23, wall-package-doors-refusal 23, wall-catalog-refusal 17).engine-insert-static-readonly-strip.test.ts+engine-readonly-strip-caller-values.test.ts:Test Files 2 passed (2) · Tests 55 passed (55); the named cases "isSystem seeds the readonly column — the exemption the ruling names" and "an isSystem caller is untouched by any of this" both ✓.meta-migrate-stored-walled-refusal.test.ts:Tests 6 passed (6).packages-walled-write-refusal+automation-walled-definition-write+meta-walled-catalog-refusal:Test Files 3 passed (3) · Tests 61 passed (61).objectstack verify --rls(12:53:20–12:53:54Z, exit 0): CRUD tail── 15 verified, 0 gaps, 0 FAILED, 1 needs-fixture, 8 skipped (0 mismatches); per-persona line (probe persona, unit = objects)── 21 PROVEN (21 consistent, 0 HOLES) · 3 NOT PROVEN (0 member-visible, 0 probe-blocked, 3 skipped);10 of 10 declared position(s) probed; total line (unit = object × persona)══ all personas: 38 PROVEN (38 consistent, 0 HOLES) · 226 NOT PROVEN (131 member-visible, 62 probe-blocked, 33 skipped).access-security.rls-both-sides — rev 7 — PARTIAL
Pins (showcase-private-owd dogfood, HTTP;
verify --rls) plus a manual leg on Boot 1 with fresh members A and B (baseline only) and the admin.["A note 1 …","A note 2 …"], B's["B note …"], from the server row sets. Pin showcase-private-owd (5 tests, HTTP) ✓GET /data/showcase_private_note/<B id>→ 404 RECORD_NOT_FOUND (not-found-shaped denial). Pin ✓PATCH …/<B id> {"body":"forged"}→ 404 RECORD_NOT_FOUND; admin re-read byte-identical. Extra ADELETE→ 403 PERMISSION_DENIED, row still presentverify --rlsper-persona line21 PROVEN (21 consistent, 0 HOLES); total lineall personas: 38 PROVEN (38 consistent, 0 HOLES)over 10 of 10 positions; norls-holerow. Layer: verify harness, HTTP in-processshowcase_project_membership"no plain-text probe field";showcase_ext_customerandshowcase_ext_order"external read-only object (federated datasource "showcase_external"; no inserts)" — all benignaccess-security.write-path-guards — rev 6 — PASS
Pins (owner-anchor-and-bulk-writes, showcase-static-readonly, showcase-readonly-when-parent dogfood, HTTP; the two objectql engine pins) plus manual legs on Boot 1 for the clauses the pins do not reach.
POST /data/showcase_contact {…,"lead_score":10}→ 201 withX-ObjectStack-Dropped-Fields: lead_score;reason=readonly; re-readlead_score=null. Pin ✓PATCH {"lead_score":99}→ 200; re-readlead_score=nullPOST showcase_private_note {"title":"planted …","owner_id":<Bob>}→ 403 PERMISSION_DENIED; admin filtered count 0. Pin ✓PATCH owner_id=<Bob>→ 403,PATCH owner_id=null→ 403 (both PERMISSION_DENIED), owner re-reads Alice;POST owner_id=<Alice>→ 201, persists. Pin ✓POST …/createManybare array of 3 → 201; all 3 read back owner_id = Alice. Live onlyPATCH tax_rate=99→ 200, droppedtax_rate;reason=readonly_when, re-read 8; paid linePATCH quantity/unit_price/product→ all three droppedreadonly_when; mixedupdateMany[paid line, draft line]:results[0].droppedFields=[{fields:["quantity"],reason:"readonly_when"}],results[1]none; re-reads paid qty 2 (unchanged), draft qty 9 (applied). Pin showcase-readonly-when-parent ✓ for the single-row halfupdateMany[L3 q5, L4 q-1, L5 q6] withcontinueOnError:true→ [ok, VALIDATION_FAILED, ok]; re-reads L3=5, L4=4 (unchanged), L5=6. Default run → [ok, VALIDATION_FAILED, NOT_ATTEMPTED], L5 unchangedPOST owner_id=<Bob>→ 201, owner Bob. Pin case ✓access-security.crud-permission-matrix — rev 7 — PARTIAL
access-matrix.jsonrecount: 45 entries = 41 showcase_* rows + 4showcase_field_ops_delegate × sys_*rows. Pin showcase-crud-persona-matrix (HTTP verify harness): 41 rows × 4 verbs = 164 cells, 87 allow / 77 deny against the baseline union, admin payload control first; passed. The 4 delegate rows driven by hand on Boot 1: admin granted the set viaPOST /data/sys_user_permission_set(201); delegate D placed in bu_field_ops, target T in bu_west_coast.Out-of-subtree create (bu_hq_finance) → 403 PERMISSION_DENIED "…is outside the delegated subtree". Contrast baseline member: sys_user_position create → 403.
GET showcase_invoice_line/<C1 line>200 and in list, baseline A's line list → 403access-security.owd-sharing-matrix — rev 4 — PASS
Pins (showcase-private-owd, showcase-public-read-owd, controlled-by-parent, showcase-invoice-cbp; HTTP) — the last two boot their own SecurityPlugin configuration, so all four models were also driven live on Boot 1 with A (baseline), C1/C2 (
showcase_contributorgranted 201), ops and auditor.access-security.anonymous-deny-surfaces — rev 3 — PASS
Pin showcase-anonymous-deny-surfaces (36 cases, HTTP) plus a live run of every family on Boot 1 with no Authorization header and no cookie.
GET /data/showcase_private_note,GET /meta,POST /actions/showcase_task/showcase_mark_done/anon-probe-id,GET /automation/_status,POST /batch {"operations":[]},GET /security/explain, analytics query/sql/meta → all 401 UNAUTHENTICATED. Pin ✓anon-probe-id→ 401, not 404GET /data/showcase_private_note200,GET /meta200GET /api/v1/forms/contact-us→ 200DELETE /automation/showcase_reassign_wizard→ 401; authed GET afterwards → 200access-security.walled-environment-metadata-operator-only — rev 5 — PASS
Walled boot provisioned per RUNNER "A walled boot is a fixture of its own". Operator: admin@objectos.ai (named by
OS_PLATFORM_OWNER_EMAIL). Org owner O and contrast owner X minted by operatorcreate-user→change-password→organization/create→set-active.Fixture write made outside HTTP: the operator saved package
com.qa.catalog(201) and permission setqa_meta_authorwithsystemPermissions:["manage_metadata"](200). Both HTTP grant doors refused as the item predicts (operator insert 403 "…organization_id is outside the caller's organization scope"; O's self-insert 403 D12 condition (c)). The grant row{user_id:O, permission_set:qa_meta_author, organization_id:<O's org>}was inserted into the run's own scratch DB while the server was stopped, then restarted. Guard-the-guard: X (no grant)PUT /meta/object/…→ 403 "…requires themanage_metadatacapability"; O's identical PUT → 403 "…requires the platform operator: this deployment runs the 'isolated' tenancy posture" — O's grant is live and the refusal comes from the wall.e5f194baPUT /meta/object/qa_walled_probe403 FORBIDDEN;PUT /meta/permission/showcase_member_access403 FORBIDDEN;POST /meta/view/showcase_task.in_progress/publish403 FORBIDDEN; view DELETE 403 FORBIDDEN;POST /meta/_migrate-stored403 FORBIDDEN;DELETE /packages/com.example.showcase403 PERMISSION_DENIED;POST /packages403 PERMISSION_DENIED;POST /security/permission-sets/showcase_member_default/clone403 PERMISSION_DENIED;POST /automation403 PERMISSION_DENIED. Operator read-backs: object 404, permission set byte-identical, view 200, package 200, would-be package/clone/flow 404groupwas not booted; it is covered only by the pins.Off-clause probes (~10 min)
Data doors as a baseline member against foreign, nonexistent and blank ids: GET/PATCH 404 and DELETE 403 uniformly (no existence oracle); bulk update/delete over a foreign id → per-row RECORD_NOT_FOUND / PERMISSION_DENIED, row unchanged. Anonymous: 32 combinations (no auth, garbage/empty bearer, bogus session cookie × data/meta/actions/flows/analytics/security doors) all 401 UNAUTHENTICATED; a bearer after sign-out → 401. Walled org owner: draft/rollback/dashboard/translation writes, object DELETE → 403 FORBIDDEN; package PATCH, automation clone/delete → 403 PERMISSION_DENIED; read-backs unchanged. Findings: none.
Generated by Claude Code
os-sales commentedon Oct 22, 2026
Per-clause verdicts 2/5 — api-backend · automation · identity · seed · datasource (6 items)
6 PASS / 0 PARTIAL / 0 FAIL / 0 BLOCKED / 0 NOT-RUN.
Environment
OS_PORT=47611 setsid pnpm -C …/examples/app-showcase exec objectstack dev --ui --seed-admin --log-level info -p 47611 -d file:(scratch)/main.db, fresh DB, started 2026-10-22T12:50Z, ready 12:50:34Z, stopped by process group 13:02:15Z.initdb -A scram-sha-256, role/dbqa_dswith a password), for the datasource connect legs.ada@example.com(create-user → change-password →showcase_contributorgranted viaPOST /data/sys_user_permission_set; a fixture of this run's making, since contributor invoice RLS keys on that email); anonymous.api-backend.query-contract-matrix — rev 5 — PASS
Pins: objectql
engine.test.ts+engine-filter-array-lowering.test.ts→Test Files 2 passed · Tests 196 passed(engine layer, stub driver — covers C4 only at that layer); restrequest-schema-gate.conformance.test.ts→Tests 16 passed(door over a MOCKED protocol). Every clause driven live. Baseline:GET /data/showcase_account?$top=500→ 14 rows.$-object, POST AST, GET$filterAST), id-set equality with the local answer: eq(9), ne(5), gt>1e7(9), gte≥12e6(9, boundary incl.), lt(5), lte≤12e6(6), gt on date(6), in(12), not_in(2), between5e6,5e7, contains 'orth'(1), not_contains(13), starts_with 'Con'(1), ends_with 'Industries'(2), like '%Industries'(2), like 'Contoso' no wildcard (1, not wrapped), is_null(12), is_not_null(2). 0 failures$orderby=name&$top=3&$skip=2= rows 3–5 of the sorted baseline;$top=4&$skip=9= rows 10–13; POSTlimit/offsetandtop/offsetidentical$select/fields + dotted refusalname,statuson all 14 rows (noid), GET and POST; POSTfields:['account.name']on showcase_invoice → 400 INVALID_FIELD namingexpand; GET twin also 400 INVALID_FIELD$orderby=no_such_fieldand POSTorderBy:[{field:'nope'}]→ 400 INVALID_SORTcontact:{name:"Prospect 13"}; contributor's identical query → own row withcontactleft as the raw id, no contact fields; contributorGET /data/showcase_contact→ 403 PERMISSION_DENIED{status:{$nin:'done'}}→ 400 VALIDATION_FAILED,fields[0].field = query.where.status.$nin, message names$nin,status, array; AST twin → 400 VALIDATION_FAILED atquery.where; GET$filtertwin → 400 INVALID_FILTER; no rows on any door?$pageSize=5→ 400 UNSUPPORTED_QUERY_PARAM listing the supported params;?not_a_field=x, POSTwhere:{not_a_field}, GET$filteron an unknown field → 400 INVALID_FIELD$in:[]→ 200, 0 rows;$nin:[]→ 200, 10 rows (task total 10); GET twins the sameop:*andparam:*variants recorded;$count=true→total:14;$searchanswers equal local;$expand=account→ expanded{name}{limit:"ten"}→ 400 VALIDATION_FAILED;{not_a_query_key:{…}}→ 400 INVALID_FIELD; no rows; declaredfilterspelling served (control)api-backend.packaged-action-disabled-dispatch — rev 6 — PASS
Pins: dogfood
packaged-activation-ledger-reach→Tests 11 passed(full kernel boot, in-process HTTP); runtimeaction-activation-dispatch.test.ts→Tests 11 passed(unit, stub engine). REST, MCP and restart legs live.{metadata_type:'action', name:'showcase_mark_done', package_id:'com.example.showcase', active:false}, KEY SET[active, created_at, created_by, id, metadata_type, name, package_id, updated_at, updated_by](noorganization_id); invoke withrecordId:'no-such-record-0000'→ 409 ACTION_DISABLED "Action 'showcase_mark_done' is disabled — it is switched off for this installation in the packaged-metadata activation ledger (sys_metadata_activation, ADR-0126 §8)…"; task snapshot unchanged{params:{bogus_key:1}}: disabled → 409 ACTION_DISABLED; re-enabled identical → 400 VALIDATION_ERROR "Invalid action params: Action param "p_text" is required; Unknown action param "bogus_key"…"POST /api/v1/mcpinitialize →tools/list(12 tools incl. run_action) →tools/call run_action {actionName:'showcase_portfolio_snapshot'}ok; after disableisError:truewith{"error":{"code":"ACTION_DISABLED","message":<same sentence as REST>,"status":409}}; re-enabled → ok[ObjectQLPlugin] Activation ledger: 3 packaged action(s) are switched off …; post-restart 409; re-enable 200 then invoke 200 (progress 0 → 100); same row idqTiyulfRmAcji8qi,active:trueautomation.packaged-flow-disable-durable — rev 5 — PASS
No
automated.ref(supporting only: service-automationflow-activation-ledger.test.ts47 passed, engine layer). Boots 1 and 2 on the same file DB. The first baseline create answered 400 becauseprojectis required, so the sequence was toggle off → on → baseline urgent create (run completed) → off, all on the same row.{id:'_93udVOz4uQ8b5bH', metadata_type:'flow', name:'showcase_urgent_task_alert', package_id:'com.example.showcase', active:false}; key set has noorganization_idPOST /automation/showcase_urgent_task_alert/trigger→ 409 FLOW_DISABLED "Flow 'showcase_urgent_task_alert' is disabled — it is switched off for this installation in the packaged-metadata activation ledger (sys_metadata_activation, ADR-0126 §7.2)…"_statusenabled:false, bound:false; boot-2 log[Automation] Activation ledger: 1 packaged flow(s) are switched off for this installation and were left unbound — 'showcase_urgent_task_alert'.; banner18 boundvs 19 on boot 1active:true;_statusenabled/bound; next urgent create → runs 2, newest run completed on the new taskidentity-auth.default-organization-boot-invariant — rev 2 — PASS
ensure-default-organization.test.tsTests 57 passed; runtimeidentity-objects-default-organization.pin.test.tsTests 4 passed[default-org] created the Default Organization (org_mvjjgxp3h7i41p75)→ 12:50:31.260ZDev admin seeded; orgcreated_at12:50:22.744Z vs admincreated_at12:50:31.039Zowner, membermember; both sessionsactiveOrganizationId: org_mvjjgxp3h7i41p75Note: admin create-user answered
membershipCreated:falseyet amemberrow existed afterwards (reconciled at sign-in, presumably); no clause affected.platform-core.seed-integrity — rev 4 — PASS
ShowcaseSeedDataimported via tsx: 19 datasets, 132 rows; live totals 0 mismatches (account 14, contact 32, inquiry 3, product 4, project 5, task 10, category 4, showcase_business_unit 9, sys_business_unit 5, team 2, project_membership 3, field_zoo 2, invoice 12, invoice_line 5, expense_report 4, expense_line 13, preference 1, announcement 2, client_brief 2)14:30→14:30:00and datetime.000Z; f_password reads back maskedSeeds: com.example.showcase 132 rows;[Seeder] Seed loading complete {"inserted":132,"updated":0,"skipped":0,"errored":0}{"inserted":0,"updated":0,"skipped":132,"errored":0}integration-system.datasource-credential-refusal-matrix — rev 5 — PASS
No pins. Live boots, the scratch PG, and
os buildon 14 scratch apps outside the tree. Legacy rows planted by direct INSERT into the run's ownsys_metadatawhile the server was stopped (boot 2 logged[metadata_spec_invalid] … Registered anywayfor each).?password=,?authToken=, mongodb options.auth.password, nestedpwd,${PG_HOST}placeholder, root aliaspasswd, turso encryptionKey onfile:, connector inlineauthentication); 12 point atcredentialsRef/credentialRef, encryptionKey gets the removal prescription; bound control builds. RuntimePOST /api/v1/datasources: 21 variants → 400 DATASOURCE_ADMIN_ERROR naming the path, no value echoed.PUT /meta/connector/:namewith inlineauthentication→ 422 INVALID_METADATA pointing atauth: {type, credentialRef};{type:'none'}→ 200POST /api/v1/datasources {name:'qa_pg', driver:'postgres', …, secret:…}→ 201;POST /datasources/qa_pg/test→{ok:true, latencyMs:8}; wrong secret →ok:false "password authentication failed"GET /data/sys_metadata?type=datasource; doors agree on the redacted config; meta door serves onlyexternal.credentialsRef: "sys_secret:sec_…"; raw DB still holds the planted cleartext (redaction is on read)hasSecret:truevsfalsedistinguishable with the same visible config; legacy rows list withheld keys inredactedConfigKeyspasswd,pwd,token,jwt,auth_token,authtoken,encryptionKey, URL userinfo — none readable on any door, aliases listed inredactedConfigKeysapiSecretreads back as written (the ruled #21921 boundary)PATCHwith the config as read → 200,hasSecretstill true, samecredentialsRef,/testok; PUT → 405; withheld keys carried forward on a legacy rowOff-clause probes (~8 min, boot 2, admin / member / anonymous)
Data GET and
/querywith empty, negative and non-numeric paging values, unknown operators, malformed$between/$in:null; activation and toggle doors with unknown ids, wrong object segments, string booleans, member flips (403), member ledger read (403), generic POST tosys_metadata_activation(405); anonymous MCPtools/call(401);$search/filter side channels over stored metadata (none). Findings: the paging defect in the body (negative$skip/offset→ 500; non-numeric or negative$top/limitignored), extracted separately.Generated by Claude Code
os-sales commentedon Oct 22, 2026
Per-clause verdicts 3/5 — CLI and MCP stdio (4 items)
4 PASS / 0 PARTIAL / 0 FAIL / 0 BLOCKED / 0 NOT-RUN.
All boots from a scratch copy of examples/app-showcase (tar copy, node_modules re-linked to the workspace packages) or scratch projects outside the tree; ports 47621–47627; sequential within this runner, ~85 min (about a quarter of it fixture building).
Tier 1 pins (
--maxWorkers=1; no pin here is*.e2e.*, so none neededOS_TEST_TIERS)src/__tests__/plugin.test.tsorganization-ownership-boot-guard.test.ts+.kernel.test.tssrc/authz-identity-objects-boot-refusal.test.tsai.mcp-stdio-fail-closed — rev 6 — PASS
Pin (plugin unit) + live
os start -p 47626 -d file:(scratch)/mcp.db(--log-level infowhere an info line is the oracle), 13:05–13:22 UTC. Fixture provisioned onos dev --seed-adminover the same file: a contributor-bound member owning one invoice it created, a client_liaison-bound member, admin; each minted its ownosk_key withPOST /api/v1/keys. Client: a node script speaking newline-delimited JSON-RPC over the child's stdin/stdout.tools/listcarried query_records, get_record, aggregate_records. stdout carried only JSON-RPC lines in every session, 0 bytes on every refused boot.OS_MCP_STDIO_ENABLED=true, key unset: exit 1, no listener, stderr[MCP] The stdio transport is enabled (OS_MCP_STDIO_ENABLED / autoStart) but OS_MCP_STDIO_API_KEY is not set. … Refusing to start an unscoped stdio server (ADR-0101).osk_unknown: exit 1,did not resolve to a valid identity (unknown / revoked / expired / owner-less). Refusing to start stdio (ADR-0101).; revoked key: exit 1,[MCP] OS_MCP_STDIO_API_KEY is refused (API_KEY_REVOKED): This API key (osk_fxDvtA-J…) was revoked. … Refusing to start stdio (ADR-0101).; neither servedINFO [MCP] stdio transport principal-bound to OS_MCP_STDIO_API_KEY identity 2Yvp5P4E… (RLS/FLS/tenant applied)= the contributor's sys_user id[{draft:1}]== member REST; admin REST and MCPdraft 3 / paid 5 / sent 5{"error":{"code":"PERMISSION_DENIED","message":"[Security] Field read denied: not permitted to aggregate [budget] on 'showcase_project'","status":403}}, no number; REST the same 403; control count-by-status identical on MCP and RESTPATCH /api/v1/data/sys_api_key/<id> {"revoked":true}→ 200; next query_recordsisError: true, content{error:{code:"API_KEY_REVOKED", status:403, message}}, no recordsINFO [MCP] Transport not auto-started (HTTP is served per-request at /api/v1/mcp regardless). …cli.dev-boot-contract — rev 7 — PASS
No automated.ref; all live, 12:46–12:55 UTC, under a wrapper recording
$?.dev --ui --seed-admin -p 47621 -d file:…/dev.db: health 200,/api/v1/ready200{"status":"ready","state":"running"}; banner prints Environment ID, Artifact, Database--admin-password changed99: admin123 → 200, changed99 → 401-d file:→ that file;OS_DATABASE_URL=file:→ that file (beat a legacy file present);-d :memory:→:memory:, sign-in 200; config-declared default datasource → its own file, not objectstack.db; nothing chosen →<cwd>/.objectstack/data/objectstack.db, persistent across boots; legacy dev.db only → served with exactly 1⚠ Reading legacy database file … migrate with: mv …notice--freshtempdir, carve-out honest🧪 Fresh OS_HOME: /tmp/objectstack-dev-…; dir gone after SIGINT, exit 130; project'sshowcase_external.dbsurvives (documented #5594 carve-out)⚠ Port 47621 is in use — serving on 47622 instead.,↪ server bound to port 47622 (requested 47621); health on 47622 200⚠ dist/objectstack.json is OLDER than your sources — this boot serves the STALE build.+ newest source + fix line; health 200✗ Config file not found …+ remedy line; exit 1/_console/→/_console/login; screenshot shows the "Welcome back" sign-in card (Email, Password, Sign In, "Sign in as dev admin")Exit codes: plain
os devSIGINT → 0 (8 boots);--freshSIGINT → 130; no-config → 1. Negatives held:--database-driver memory→ INVOCATION ERROR, exit 2;OS_DATABASE_DRIVER=memory|mingo|in-memory→ exit 1 naming--freshand:memory:;-d memory:///mingo://→✗ An OS_DATABASE_URL with the "memory://" scheme selects the in-memory (mingo) engine …, exit 1.platform-core.v18-boot-refusals — rev 6 — PASS
v17-shaped DB from published
@objectstack/cli@17.7.0booting a minimal one-object app on a fresh file DB, then a few rows through its HTTP doors (68 tables, 4 adr-0093/0104 markers, no adr-0131); copies only. v18 boot:OS_DATABASE_URL=file:COPY OS_AUTH_SECRET=… OS_SECRET_KEY=… os serve --no-ui -p 47624, 12:58 UTC.✗ This database carries no organization-ownership marker: 17.x or an 18.0 pre-release (18.0.0-next.*) created it, … still carry a physical organization_id column … Run 'os migrate organization-ownership --out PLAN' … then … --apply --plan-file PLAN --backup-taken, and start again.; 0 hits for17.x release/fresh installs only; same text on a second copycreateIdentityObjectsPlugin()new SecurityPlugin():os serve --dev --no-server --no-ui --preset minimal→ exit 1,✗ SecurityPlugin cannot boot: its authorization store reads sys_user, sys_member, and this kernel does not register sys_user, sys_member. … mount createIdentityObjectsPlugin() from @objectstack/plugin-auth.cli.organization-ownership-ceremony — rev 4 — PASS
Pins + live on copies of the v17 DB above and of an 18.0.0-next.5 DB, 12:59–13:31 UTC, under the deployment's composition (
OS_AUTH_SECRET+OS_SECRET_KEY, or--devfor the next.5 leg).--applywithout a plan refusedkind: organization-ownership-plan,ceremony: ADR-0131 D10, 135 table entries; DB sha256 (+ -wal) identical and row diff 0 on 3 copies;--apply --backup-takenwithout--plan-file→ exit 1✗ Refused: --apply executes only a plan you reviewed. …, checksum unchangedcomplete: true,blocking: 0, all 13 steps done; adr-0131 marker rowblocking 0,shapeGeneration 1==ORGANIZATION_OWNERSHIP_SHAPE_GENERATIONos migrate organization-ownership; migrated copy:✓ Server is ready,/ready200, v17 admin password signs in 200os migrate apply --allow-destructive --yes --json→ exit 1,refused_organization_ownership_unmigrated, the 8 tables named, applied 0, main-file checksum unchanged, row diff 0@objectstack/*@18.0.0-next.5+ showcase source at tag commit 820fd5a,os dev --seed-admin, then members, password resets, verification resends, invitations, global mail settings, an activated declared webhook mirror, tasks — all through HTTP. Run subject over a copy: exit 1 namingsys_flow_credential, sys_secret; refused boot row diff 0. Plan exit 0; apply exit 0, complete, blocking 0; second boot ready. 4/4 identity mails moved tosys_identity_emailunder original ids, 3/3 invitation mails stay insys_email; 6/6 globalsys_setting_auditrows moved tosys_platform_setting_audit, 1 tenant row untouched; completed mail jobs unchanged; per-table count changes exactly equal the apply report. After upgrade: three accounts sign in with their 18.0 passwords (200; wrong → 401); a token from a moved reset mail resets the password (200); one task PATCH wrote exactly onedeclared:showcase_task_changeddelivery rowNegative held: no refusing arm or plan run altered a DB (checksums and row diffs above).
Fixture gaps / deviations
cli@17.7.0booting a minimal app, not a 17.x showcase (17.x showcase source does not load on the run subject's spec).Off-clause probes (~12 min)
os startstdio admission with empty, whitespace and prefix-only keys and every enable-switch spelling (all failed closed, 0 bytes stdout); aggregate doors over field-level-security-hidden fields as the liaison; the ceremony's partial-apply/resume path. Findings: one low-severity authorization-adjacent observation, detail withheld pending maintainer (no value leak); a UX note that the ceremony plan's printed summary does not surface its "boot this release once" precondition (in the body's accuracy findings).Generated by Claude Code
os-sales commentedon Oct 22, 2026
Per-clause verdicts 4/5 — boot, console login, nav (4 items)
3 PASS / 1 PARTIAL / 0 FAIL / 0 BLOCKED / 0 NOT-RUN.
One boot served all four items:
OS_PORT=47631 setsid pnpm -C examples/app-showcase exec objectstack dev --ui --seed-admin -p 47631 -d file:(scratch)/boot.db, fresh DB, process start 2026-10-22T12:46:30Z, health 200 at 12:47:04Z; bannerTenancy: single, 53 plugins,Seeds: com.example.showcase 132 rows; stopped by process group. Headless Chromium via/opt/pw-browsers/chromium, own context and port. Personas: seeded admin; seeded member Mei (sys_member.rolemember), identity checked withget-sessionbefore each member cell. Sequential within this runner.platform-core.boot-health — rev 9 — PASS
Boot-window log copied at the first health 200, before any other request.
GET /api/v1/health200{"status":"ok","version":"18.0.0"}, 34.1 s from launch (polled every 0.5 s)GET /api/v1/ready200{"status":"ready","state":"running"}Flows: 32 flow(s) 19 bound … · 8 draft; only two ℹ "NOT bound — disabled by deployment policy" lines (showcase_task_due_reminder time_relative, showcase_scheduled_digest schedule); grep of the window forERROR/⚠/reject/dropped/FATAL/error→ 0 lines; 2 informational WARNs in diagnostics (print engine; sharing-rule note)GET /api/v1/meta/app?id=showcase_app200 with 1 item; bare/meta/applistsshowcase_app, setup, accountnav_*ids insrc/ui/apps/index.ts; diff empty both waysGET /_console/200text/html; charset=utf-8(18301 B)droppedplatform-core.console-login — rev 4 — PASS
Real form sign-in (role-targeted fill and click); both auth halves present (
auth-session-tokenin localStorage and thebetter-auth.session_tokencookie). pageerrors: 0./_console/home; screenshot: header, "Your apps" launcher (Showcase tile), My work, Recently Accessed; first authed calls all 200/apps/com.example.showcase/showcase_projectstays put; get-session 200, meta/app 200POST /api/v1/auth/sign-out200; old bearer →GET /api/v1/meta/app?id=showcase_app401 UNAUTHENTICATED/_console/login?redirect=%2Fapps%2Fcom.example.showcase%2Fshowcase_task; screenshot shows the login form, not a dead shellINVALID_EMAIL_OR_PASSWORD, noset-cookie, no token, red alert "Invalid email or password. Please try again.", stays on /loginplatform-core.nav-surfaces-render — rev 6 — PASS
Pin:
SMOKE_API_URL=http://localhost:47631 OS_TEST_CHROMIUM_EXECUTABLE_PATH=/opt/pw-browsers/chromium pnpm -C examples/app-showcase exec playwright test --config=playwright.config.ts e2e/showcase-smoke.spec.ts(webServer reused the boot). Layer: HTTP + real browser.49 passed (2.1m). The first full run was 48 passed / 1 failed:Hours by Status (Chart)died withlocator.waitFor: Target crashed(renderer crash before any assertion); the 6 chart surfaces in isolation went 6/6 twice and the full re-run 49/49 — non-reproducing, environment-class, nothing filed/_console/apps/com.example.showcase/object_that_does_not_exist: header and full sidebar alive, main "Object Not Found — The object "object_that_does_not_exist" does not exist in the current configuration…"; same on a fresh load;/page/page_that_does_not_exist→ "Page Not Found" in the shellplatform-core.builtin-apps-nav-render — rev 7 — PARTIAL
Manual browser + API; no pin. Admin's apps
["showcase_app","setup","account"];?id=studio→ 200items: [](expected: studio is not loaded on a stock boot).GET /api/v1/marketplace/packages→ 502MARKETPLACE_PROXY_FAILED(egress to cloud.objectos.ai denied) — the catalog content is blocked(environment, knownGap / #22017 row 10); the surface renders honestly. All 9 settings namespaces and the hub render; Users, Organization, Business Units, Teams, Sessions, OAuth Applications render; a user record's Preferences and Linked Accounts tabs render/_console/apps/com.objectstack.setup→ console'sGET /api/v1/meta/app/com.objectstack.setup403 → "You don't have access to this app — This app exists, but your account is not authorized to open it…"; same on a deep link; Account opens on ProfileGET /api/v1/meta/app?id=setup→ 200items: [];GET /api/v1/meta/app/com.objectstack.setup→ 403; member's bare list["showcase_app","account"]?id=setupempty, so gated Setup entries are unreachable; Account nav 8 vs 8, empty diff (presence-only control, as the clause says)nav_organizationsandnav_account_membershipsabsent (single tenancy);nav_business_unitsandnav_notificationspresent and render;nav_jwksabsentOS_SSO_ENABLEDunset); sys_verification / sys_device_code entries absentOff-clause probes (~12 min, admin and member)
Sign-in with empty email+password (400
INVALID_EMAIL), unknown email (401, same answer as a wrong password — no account enumeration), missing password (400VALIDATION_ERROR), none setting a cookie or token. In-SPA expiry after a server sign-out shows the named "Sign in required" state with Retry and no stale data. An unknown app shows "This app can't be opened" with Retry / Back to home. One client-courtesy note: on one alternate Setup route spelling the console renders settings chrome to a non-entitled member instead of the app-level refusal; the server refuses the data read 403 and nothing is exposed. Findings filed: none.Generated by Claude Code
os-sales commentedon Oct 22, 2026
Per-clause verdicts 5/5 — approvals entry, CRUD round-trip, Studio first-run loop (3 items)
2 PASS / 0 PARTIAL / 1 FAIL / 0 BLOCKED / 0 NOT-RUN.
Boot 1:
OS_PORT=47636 setsid pnpm -C …/examples/app-showcase exec objectstack dev --ui --seed-admin -p 47636 -d file:(scratch)/main.db, 2026-10-22T12:46:30Z → 13:12Z, covered every clause. Boot 2 (13:12:50Z, same DB) only for crud-roundtrip's readonly clone leg (a row planted while stopped) and the probes. Headless Chromium 141 via playwright-core 1.63.0, form sign-in every time (bearer token and cookie both present, checked). The approvals FAIL was then re-derived by an independent runner on its own boot (port 47641, 13:17–13:23Z) with its own persona — see the body.approvals.account-app-entry — rev 4 — FAIL
Persona minted at runtime: admin
invite-member→sign-up/email→ adminPOST /data/sys_user_positioncontributor(201) andfinance(201);get-sessionpositions["org_member","contributor","finance","everyone"], role member, no Setup. Fixture: the persona created invoice INV-UI2-APPR (owner = its own email) and PATCHed itstatus=sent, issued_on→ launchedareq_9d00f955-…onflow:showcase_invoice_signoff, persona inpending_approvers,decision_progress {got:0, need:2}.dist/.objectui-sha=.objectui-sha(cd14d8ee…);grep -o approvals:inbox dist/assets/index-*.js→ 1 hit in the entry chunk, context{ref:\approvals:inbox`,label:`Approvals Inbox`,source:`@object-ui/console`,component:…Suspense…}`/_console/apps/com.objectstack.account/component/approvals/inbox; screenshot: "Approvals Inbox", tabs My Pending (1) / Submitted by me / All, one row (Invoice Dual Sign-off · INV-UI2-APPR · Pending), keyboard hint bar; no component-not-found stateGET /api/v1/meta/app→["showcase_app","account"](no setup);?id=setup→items: []; account nav keepsnav_account_approvals(component,approvals:inbox)POST /approvals/requests/<id>/approve200; progress 0/2 → 1/2, persona dropped frompending_approvers, timeline row appended,viewer.can_acttrue → false. Rendered half fails: opening the request (row click, Enter, or?request=) lands on/_console/apps/com.objectstack.account/sys_approval_request/record/<id>, which renders only the heading "Approval Request areq_…" and empty Details/Timeline tabs — no fields, no action set, no can_act=false explanation, before and after the decision. Admin on the same URL: full page with Approve / Reject / Reassign / Send back / Request info / Recall / Reply. Reproduction rule and mechanism in the issue body?request=deep-links on this component route…/component/approvals/inbox?request=<id>→ redirects to…/sys_approval_request/record/<id>, the exact request; its blank body is the acceptance[3] defect, not double-countedhref="/_console/apps/com.objectstack.account/showcase_invoice/record/<id>"; persona's directGET /api/v1/data/showcase_invoice/<id>→ 200Rule-2 checks: reproduced 5× on fresh contexts (title click, status-cell click, j+Enter,
?request=cold load, direct URL with a 7–8 s settle); URL-level navigation reproduces with no click (no coordinate artifact); identity proven per context; console stamp = pin. Independent re-derivation: 3 more reproductions with a different persona and two new requests, an admin control, and a causal check (see body).records-forms.crud-roundtrip — rev 13 — PASS
Admin, plus the
search:qa-contributor-bound-memberrecipe for clauses 6–7 (invite → sign-up → sys_user_position contributor); premise: the member's invoice list total 0 and a foreign invoice by id → 404 RECORD_NOT_FOUND; own row visible.POST /api/v1/data/showcase_account {"industry":"finance","status":"active","sales_region":"amer","name":"os-qa-ui2r1"}201; filtered GET total 1, values identical,website:null(not defaulted), owner_id = adminPATCH {"annual_revenue":1234567}200; full-row diff: onlyannual_revenueandupdated_atchangedDELETE200{success:true}; filtered GET total 0; by id 404 RECORD_NOT_FOUNDos-qa-ui2r1-华宁201, byte-identical on read (hex compared);$search=华宁→ 2 rows; list quick-search UI shows bothPOST /data/showcase_account/<id>/clone {overrides:{name:…}}201, only id/timestamps/name differ; contributor clone of an admin-owned zoo row → owner_id/created_by/f_owner = contributor,f_autonumber0002 → 0003 re-derived; clone of a row whoselead_scorewas planted 42 → 201 withdroppedFields:[{"object":"showcase_contact","fields":["lead_score"],"reason":"readonly"}], clone readslead_score:nullnamefields:[{field:"name",code:"required"}]{field:"tax_id",code:"invalid_format",message:"Tax ID must look like 12-3456789."}; dialog stays open; 0 rowsstudio-authoring.first-run-loop — rev 6 — PASS
Admin, boot 1, loop 13:05:14 → 13:11:51 UTC (the acc[0] pin is objectui-only, so driven by hand).
com.example.repairs→POST /api/v1/packages201status:"installed"; switcher lists "维修中心 · com.example.repairs · Writable"; a second create kept a pre-setwindowmarker with 0loadeventsPUT /meta/object/repairs_repair_ticket?mode=draft…200; Publish all → "Published 1 item in this package (one atomic release)";GET /api/v1/meta/object/repairs_repair_ticket200 with exactly the 3 authored status options (404 before publish)POST /data/repairs_repair_ticket201; GET total 1,status:"in_progress"/meta/appaddsrepair_centerwithnav_repairs_repair_ticketserve --devchild pid before and after the loop, no file editscom.example.showcase→ 422 WRITABLE_PACKAGE_REQUIRED; add-field onshowcase_account→ 403 ITEM_LOCKED; without package/draft → 403 NOT_OVERRIDABLE; nothing landedOff-clause probes (~6 min; approver member, contributor, admin)
Approvals: a non-participant's GET of a foreign request → 404 REQUEST_NOT_FOUND; approve of a foreign request → 403; approve while forging the admin as
actor_id→ 403, tally unchanged. Clone of unknown/empty id → 404. Contributor PATCH of an invisible invoice → 404. Forged app into the read-only package → 422. Finding: record History renders a currency change raw ("— → 1234567") while the header shows "$1,234,567.00" (cosmetic, objectui display).Generated by Claude Code
os-sales commentedon Oct 22, 2026
Close-out (extraction):
.objectui-sha).This record stays open until #25228 is resolved and the approvals item is re-run on the moved pin.
Generated by Claude Code
objectstack-fleet commentedon Oct 22, 2026
Re-test ·
approvals.account-app-entryacceptance[3] on the moved pin · PASSSession
session_016ZEJXR398Mp5xcwuMncpK9, the maintainer-requested re-run of this record's one FAIL (decision on #25228). It ran on PR #25262's head: framework8010b641plus the pin move to objectui0d12e95361b2(objectstack-ai/objectui#13326, the fix for objectstack-ai/objectui#13324). The record stays open until #25262 merges.Environment:
pnpm build --concurrency=2all green;pnpm objectui:buildat the pin,packages/console/dist/.objectui-sha=0d12e95361b20b1eb9012f97d2d36e5fa7f14374; the built entry chunk carries theapprovals:inboxregistration. Stockexamples/app-showcaseon its own port and file DB,--seed-admin; headless Chromium 141.Fixture, this record's reproduction rule: a new persona, invited and signed up, holds positions
contributor+finance. It owns a new invoice and PATCHed it tosent+issued_on, which launched a newshowcase_invoice_signoffrequest. Before, as the persona:GET /api/v1/approvals/requests/:id→ 200,viewer.can_act: true, persona inpending_approvers,decision_progress0 of 3.get-sessionreads the persona;GET /api/v1/meta/applistsshowcase_app,account, nosetup/_console/apps/com.objectstack.account/component/approvals/inbox: tabs My Pending (1) / Submitted by me / All, the request row, the keyboard hint bar/_console/apps/com.objectstack.account/sys_approval_request/record/:idrenders the heading "Invoice Dual Sign-off (parallel approval)", the invoice fields, "Approvals — 0 of 3", waiting-on chips, and Approve / Reject / Reassign / Send back / Request info / Send reminder / Recall / ReplyPOST /api/v1/approvals/requests/:id/approve→ 200; the page reads "Approvals — 1 of 3", the persona is gone from waiting-on, the approver actions are gone (the submitter's Send reminder / Recall / Reply remain), and Timeline lists Submit + Approve with the commentGET …/requests/:id→viewer.can_act: false, persona dropped frompending_approvers,decision_progress1 of 3;GET …/requests/:id/actions→ submit + approveOn the old pin
cd14d8eeffe1, the same steps give the blank page this record's FAIL describes. The pre-merge rehearsal on objectui PR head8a3ef97is objectstack-ai/objectui#13326 comment 6344288566.Checklist-accuracy note (not a fail), for the wave anchor: acceptance[3]'s "replaces the action bar with the can_act=false explanation" names an element of the side drawer that objectui#12484 retired. The served request page shows no separate "you can no longer act" sentence for any viewer; its declaration carries none. The rest of the clause (server-declared action set, tally moves, user dropped from
pending_approvers, timeline row appended, decision actions leave the bar) holds as written.objectstack-fleet commentedon Oct 22, 2026
Close-out (checklist-test §5). Every fail in this record has a disposition, both extracted cards are closed, and the condition comment 6342183324 set for closing (#25228 resolved, the approvals item re-run on the moved pin) is met. The
Extracted:lines are in comment 6342183324; this comment adds no new extraction.approvals.account-app-entryacceptance[3]: a product defect. The fix landed in objectui (approval record page: a row read through the approvals door is re-filtered against /auth/me/permissions, so a non-admin approver sees a blank request page (18.1.0 release blocker, objectstack#25228) objectui#13324, fixed by fix(console,permissions): a non-admin approver sees the whole approval request page (objectui#13324) objectui#13326, squash0d12e95361b2). This repo moved its console pin past it in chore(console): move the objectui pin cd14d8eeffe1 → 0d12e95361b2, past objectui#13326, so a non-admin approver sees the approval request page #25262, merged ascc8d886d4. Non-admin approvers get a blank approval request page since the console pin moved past objectui#12484 (18.1.0 release blocker) #25228 is closed as completed. The re-run of the clause as a non-admin on the moved pin passed (comment 6347043108).access-security.rls-both-sidesacceptance[5] D11,access-security.crud-permission-matrixacceptance[5] guest_portal,platform-core.builtin-apps-nav-rendermarketplace catalog): no clause in them failed. Each gap is one that its own item already records infixtures.knownGaps; the marketplace one is the sandbox egress block its knownGap names. This run found no new gap, so none is parked.Closed on the conditions in comment 6342183324, and on the maintainer's instruction of 2026-10-19 quoted on #24908 (「qa-run的卡片处理完的是不是可以关闭」). Session
session_016ZEJXR398Mp5xcwuMncpK9.Generated by Claude Code