Skip to content

QA run · priority:P0 + 17.6-risk (42/42) · 617f25f8 · 2026-10-02 · 30 PASS / 3 PARTIAL / 9 FAIL / 0 BLOCKED / 0 NOT-RUN #21330

Description

@objectstack-fleet

17.6.0 release verification run: the standing P0 smoke plus the 23 checklist items most exposed to 17.6.0's changes, against the 17.6.0 release build. Text only; the durable truth stays in docs/qa/platform-checklist/areas/.

Environment fingerprint

  • Framework (subject): objectstack-ai/objectstack 617f25f8 (chore: version packages (#20639)), the commit @objectstack/*@17.6.0 was published from (tags @objectstack/<pkg>@17.6.0). Built in a dedicated worktree: pnpm install --frozen-lockfile && pnpm build → Tasks: 72 successful, 72 total.
  • Console: objectui pin .objectui-sha = 31971ff1e28f, built by pnpm objectui:build under Node v22.22.2 (the container's v22.22.0 is below the pinned objectui's jsdom engine floor; environment note). packages/console/dist/.objectui-sha = the pin. Single-zod canary over the shipped assets: exactly one zod version literal {major:4,minor:6,patch:5} (2450 files read). Spec-injection check: the bundle carries this tree's @objectstack/spec only.
  • Checklist: items, RUNNER.md and scripts/checklist-select.mjs taken from main at 393ae878; the revision of every item run is recorded below.
  • App / boots: examples/app-showcase, OS_PORT=<p> objectstack dev --ui --seed-admin -p <p> -d file:/tmp/qa1760/<lane>/<item>.db — one file DB and one port per item, lanes on disjoint ranges 41000–42199 with the egress proxy port 41333 excluded. Banner Seeds: com.example.showcase 132 rows; Flows: 30 flow(s) 20 bound … 7 draft. Members minted by admin invite-member + sign-up/email (stock audience posture invite_only), identity proved by GET /auth/get-session before each persona cell.
  • Execution: 9 parallel runner subagents (L1–L9) + 4 independent verifiers (V1–V4, RUNNER rule 7), Claude Code session session_018zT8d8NpiQ1ExhuNd5TxY6, 2026-10-02 03:55–05:25Z. Environment prep ≈ 30 min (framework build 7m40s, console build ≈ 15 min); per-lane runner time 14–41 min, of which 4–8 min env prep each.

Scope

One record for one release-verification selection, by maintainer direction: priority:P0 (node scripts/checklist-select.mjs priority:P0 --json → 19 runnable) + 23 named 17.6-risk items = 42 items. Hidden as blocked (not run): access-security.no-active-org-session-semantics. Planned (not run, not counted): records-forms.picklist-shared-across-objects, records-forms.picklist-org-append — see "Checklist-accuracy findings".

item rev verdict pin (automated.ref, run from the subject tree)
access-security.rls-both-sides 5 PASS verify --rls exit 0; private-owd 5/5; invoice-seed-isolation 12/12
access-security.write-path-guards 3 PASS owner-anchor 9/9 · static-readonly 3/3 · readonly-when-parent 5/5 · objectql static-readonly-strip 18/18
access-security.crud-permission-matrix 4 FAIL persona-matrix 46/46 · permission-zoo 13/13
access-security.owd-sharing-matrix 3 PASS private-owd 5 · public-read-owd 3 · controlled-by-parent 4 · invoice-cbp 4
access-security.anonymous-deny-surfaces 3 PASS anonymous-deny-surfaces 61/61
ai.mcp-stdio-fail-closed 4 PASS mcp plugin.test.ts 17/17
api-backend.query-contract-matrix 3 PASS objectql engine + filter-array-lowering 196/196 · rest request-schema-gate 16/16
api-backend.packaged-action-disabled-dispatch 3 PASS action-activation-dispatch 11/11 · activation-ledger-reach 11/11
automation.packaged-flow-disable-durable 2 PASS none
platform-core.seed-integrity 3 PASS none
cli.dev-boot-contract 3 PASS unified-db-resolution 7/7 · resolve-project-database 26/26
platform-core.boot-health 6 PASS none
integration-system.datasource-credential-refusal-matrix 2 PARTIAL none (service-datasource pins 86/86 alongside)
platform-core.console-login 4 PASS none
platform-core.nav-surfaces-render 5 PASS showcase-smoke.spec.ts 49 passed (4.8m)
platform-core.builtin-apps-nav-render 4 PASS none
records-forms.crud-roundtrip 8 PARTIAL none
approvals.account-app-entry 2 FAIL none
studio-authoring.first-run-loop 3 PASS none
access-security.fls-mask-and-strip 3 PASS permission-zoo + fls-read-mask-strip + client-liaison 26/26
access-security.record-access-explain 2 PASS permission-zoo explain case (in the 26/26)
access-security.audit-log-browser 2 FAIL (stale clause) none
access-security.public-form-intake 2 FAIL showcase-public-form + read-back-masking + config-change-audit pass · rest public-form-routes 23/23
access-security.packaged-flow-write-door-parity 1 PASS (unnamed) packaged-flow-write-door-parity.dogfood 5/5
api-backend.filter-comparand-conformance 2 FAIL (stale clause) engine-comparand-type-door 12/12 · driver conformance memory 23 / sqlite-wasm 22 / mongodb 22 / turso 22 / sql 46 (+2 live-DB skips)
api-backend.aggregate-contract-matrix 1 PASS none
api-backend.date-range-preset-matrix 1 FAIL (assertion defect) none
dashboards.cube-query 3 PASS none (runtime analytics-anonymous-deny 29/29 alongside)
dashboards.dataset-report-authoring 1 PASS dashboard-designer-roundtrip 3/3
dashboards.global-filters-rescope 2 PASS none
dashboards.strict-widget-rejects-stray-keys 2 PASS none
records-forms.field-type-matrix 2 PASS field-zoo-roundtrip + value-shape 91/91
records-forms.import-transform-matrix 1 FAIL (stale clause) none
records-forms.named-import-mapping 1 PASS none
automation.packaged-flow-subflow-disable-refusal 1 PASS flow-activation-ledger 47/47
automation.packaged-flow-clone-contract 1 FAIL automation-flow-clone 18/18
automation.flow-toggle-kill-switch 2 PASS none
automation.setup-packaged-automation-board 2 PARTIAL setup-packaged-automation-nav 4/4
integration-system.datasource-admin-lifecycle 3 PASS admin-routes + envelope + credential-migration + route-ledger 86/86
platform-core.metadata-authoring-roundtrip 2 PASS package-first-authoring 5/5
studio-authoring.view-authoring-live 1 FAIL (assertion defect) none
studio-authoring.custom-page-source-tiers 1 PASS none

Totals: 30 PASS / 3 PARTIAL / 9 FAIL / 0 BLOCKED / 0 NOT-RUN. Of the 9 fails, 4 are product defects (2 of them on P0 items, both already tracked) and 5 are checklist clauses that 17.6.0 or earlier design deliberately contradicts. Every P0 fail and every new product-defect candidate was re-derived by an independent verifier from a fresh reproduction.

vs #21056 (2026-10-01, ef1ed17f, Console db11afd4967c: 13 PASS / 6 FAIL)

# item · clause #21056 17.6.0 (617f25f8, Console 31971ff1e28f)
F1 access-security.rls-both-sides · A6 fail (#21052, withheld) FIXED — the standing probe now holds (fix 99398542, #21104, in the subject tree); item PASS
F2 access-security.crud-permission-matrix · A1 fail (#21057) STILL FAILING — same 403 "outside the delegated subtree"; seeded business units still carry organization_id: null (verifier V1 confirmed)
F3 api-backend.query-contract-matrix · A6 fail (stale clause) RECLASSIFIED → PASS — clause re-pointed in rev 3 (#21060 / #21111); 17.6.0 answers exactly the re-pointed codes
F4 integration-system.datasource-credential-refusal-matrix · N2 fail (#21058) FIXED by c6954d6 (#21133) — both #21056 bodies now 400 DATASOURCE_ADMIN_ERROR naming config.url / config.username
F5 approvals.account-app-entry · A7 fail (objectui#11326) STILL FAILING at the pin — now English on every path, SPA included; fixed upstream in objectui d0fba91aa, which is not an ancestor of 31971ff1e28f (verifier V4)
F6 studio-authoring.first-run-loop · A1 fail (objectui#11327) FIXED by the pin (one zod instance, objectui#11353) — the New package dialog renders all fields and POST /api/v1/packages → 201

Per-clause verdicts

Clauses are numbered A1..An (acceptance) and N1..Nn (negative) in array order. Evidence is server truth unless it says screenshot.

P0

access-security.rls-both-sides (rev 5) — PASS

# verdict evidence
A1 pass member A lists exactly A's two notes, B exactly B's one (2 runs)
A2 pass foreign by-id GET → 404 RECORD_NOT_FOUND both ways
A3 pass foreign PATCH → 403 PERMISSION_DENIED; admin re-read byte-identical
A4 pass admin list contains every id the run created
A5 pass verify --rls: per-persona 21 PROVEN (21 consistent, 0 HOLES) · 3 NOT PROVEN; total all personas: 38 PROVEN (38 consistent, 0 HOLES) · 226 NOT PROVEN; 10 of 10 declared position(s) probed; CRUD tail 15 verified, 0 gaps, 0 FAILED, 1 needs-fixture, 8 skipped
A6 pass the standing probe holds on fresh personas (2 runs); the 3 skipped objects are benign (no plain-text probe field / external read-only)
N1–N3 pass forged PATCH 403, row unchanged

access-security.write-path-guards (rev 3) — PASS

# verdict evidence
A1 pass POST contact lead_score → 201, x-objectstack-dropped-fields: lead_score;reason=readonly, stored null
A2 pass PATCH lead_score:99 → 200, same header, null after
A3 pass member POST owner_id=<other> → 403 "requires the transfer grant"; count 0
A4 pass self-owner insert 201; transfer / disown PATCH 403, owner unchanged
A5 pass createMany ×3 → every owner_id = caller
A6 pass paid-invoice locked fields dropped reason=readonly_when; mixed updateMany drops per row (results[0].droppedFields)
A7 pass updateMany with options.continueOnError:true → violating row VALIDATION_FAILED, compliant row persisted
N1 pass admin forge / transfer 201 / 200 persisted
N2 pass objectql pin "isSystem seeds the readonly column" (no live evidence: stock seeds never set lead_score)

access-security.crud-permission-matrix (rev 4) — FAIL

# verdict evidence
A1 fail 87 allow cells green in the pin; delegate in-subtree sys_user_position create → 403 "outside the delegated subtree" (3 boots + verifier 2 boots)
A2 pass pin: 77 deny cells 403 PERMISSION_DENIED; delegate C/U/D on sys_business_unit, sys_business_unit_member, sys_permission_set, sys_position 403; sys_user create/delete 405
A3 pass denied creates leave 0 marker rows
A4 pass auditor lists and reads C1's line by id
A5 pass ops PATCH of a foreign announcement 200 persisted; member 403 unchanged
A6 blocked(dependency #21158) anonymous POST and GET on /data/showcase_inquiry → 401 before and after binding guest → showcase_guest_portal; 17.6.0's deny baseline gives anonymous callers no channel to a permission set until #21158; the public-form lane still answers 201
A7 pass 164 pin cells + 24 delegate sys_* cells, one verdict each
N1–N2 pass admin payload controls 201; no withheld cell answered 2xx

access-security.owd-sharing-matrix (rev 3) — PASS

# verdict evidence
A1 pass private: B list excludes, by-id 404, PATCH 403 unchanged; owner 200
A2 pass public_read: B lists + reads 200, PATCH 403 unchanged
A3 pass ops (modifyAll) PATCH of foreign announcement 200
A4 pass public_read_write: contributor PATCH 200; member 403
A5 pass controlled_by_parent: list excludes, by-id 404, PATCH 403; owner 200
A6 pass auditor (VAMA) lists + reads C1's line
A7 pass all four OWD models driven per persona and verb
N1–N2 pass forged public_read PATCH 403; own-line probes 200

access-security.anonymous-deny-surfaces (rev 3) — PASS

# verdict evidence
A1 pass data, meta, actions, automation _status, batch, security/explain, 3 analytics faces → 401 UNAUTHENTICATED
A2 pass anonymous action on a nonexistent id → 401, not 404
A3 pass each body in exactly one envelope family
A4 pass analytics: anonymous 401 incl. malformed bodies; member 200, malformed 400 VALIDATION_FAILED
A5 pass member data / meta 200
A6 pass anonymous GET /api/v1/forms/contact-us → 200
A7 pass all 7 variant families probed
N1 pass anonymous automation DELETE → 401; flow still served

ai.mcp-stdio-fail-closed (rev 4) — PASS

# verdict evidence
A1 pass keyless → exit 1 "OS_MCP_STDIO_API_KEY is not set … Refusing to start an unscoped stdio server (ADR-0101)"; nothing listening
A2 pass unknown key → exit 1 "did not resolve to a valid identity"
A3 pass --log-level info: "principal-bound to OS_MCP_STDIO_API_KEY identity … (RLS/FLS/tenant applied)"
A4 pass member MCP invoice ids = member REST ids; foreign row "not found" / 404; liaison budget/spent absent on both
A5 pass member aggregate draft=1 on both doors; admin 3/5/5
A6 pass liaison sum / count_distinct / groupBy budget → "Field read denied: not permitted to aggregate [budget]"; REST 403
A7 pass mid-session revoke → "MCP stdio identity is no longer valid"; REST 401
A8 pass stdio off → clean boot, health 200

api-backend.query-contract-matrix (rev 3) — PASS

# verdict evidence
A1 pass 18 operator variants × 3 doors (POST $-object, POST AST, GET $filter) = the expected id sets over a 14-row baseline
A2 pass $top/$skip and limit/offset exact slices
A3 pass $select / fields exact keys; dotted account.name → 400 INVALID_FIELD naming expand
A4 pass asc/desc match; bad field → 400 INVALID_SORT
A5 pass contributor gets the raw contact id, admin the expanded object
A6 pass POST scalar $nin → 400 VALIDATION_FAILED at query.where.status.$nin (both spellings); GET → 400 INVALID_FILTER
A7 pass $pageSize → 400 UNSUPPORTED_QUERY_PARAM; unknown field → 400 INVALID_FIELD
A8 pass $in [] → 0 rows; $nin [] → 14/14
A9 pass every variant recorded
N1 pass {"limit":"ten"} → 400; undeclared key → 400 INVALID_FIELD

api-backend.packaged-action-disabled-dispatch (rev 3) — PASS

# verdict evidence
A1 pass disabled → 409 ACTION_DISABLED; 0 tasks changed; ledger row has no organization_id
A2 pass disabled + bad params → 409; re-enabled → 400 "Invalid action params…"
A3 pass 409 with a bogus recordId
A4 pass D4-refused action 403 byte-identical enabled vs disabled
A5 pass HTTP MCP run_action disabled → isError ACTION_DISABLED 409; re-enabled ok
A6 pass survives restart; re-enable updates the same row
A7 pass flow-type action 409
N1–N3 pass anonymous 401; absent row = active

automation.packaged-flow-disable-durable (rev 2) — PASS

# verdict evidence
A1 pass toggle → exactly one sys_metadata_activation row, no organization_id key
A2 pass trigger → 409 FLOW_DISABLED naming the ledger and both remedies
A3 pass two cold boots: /_status {enabled:false, bound:false}; boot line (at OS_LOG_LEVEL=info) names it unbound
A4 pass runs 1 → 1 across both boots
A5 pass re-enable updates the same row id; runs → 2
N1–N4 pass row present; restart does not re-arm; not deleted

platform-core.seed-integrity (rev 3) — PASS

# verdict evidence
A1 pass 19 seed sets / 132 rows, every live count equal to the authored seed
A2 pass Specimen — Full values verbatim (time canonicalised 14:30:00, password masked)
A3 pass banner Seeds: com.example.showcase 132 rows, no dropped-record line
A4 pass replay against the same DB: identical sweep
A5 pass deliberately unseeded fields null
N1–N2 pass no zero counts; no growth on restart

cli.dev-boot-contract (rev 3) — PASS

# verdict evidence
A1 pass health + ready 200; /_console/ login form; form sign-in → /_console/home with cookie + token
A2 pass restart with --admin-password changed99: old password 200, new 401
A3 pass -d, OS_DATABASE_URL, --database-driver memory, nothing-chosen (persists), legacy dev.db (one notice, no empty sibling) each resolve as specified; config tier by pin
A4 pass --fresh tempdir used, gone after SIGINT; #5594 carve-out survives
A5 pass busy port → ↪ server bound to port 41532 (requested 41531); health 200 there
A6 pass stale dist/objectstack.json warned with newest source + fix; boots
A7 pass no config → exit 1 with the remedy line
N1–N3 pass existing DB not wiped; legacy file read; shift reported

platform-core.boot-health (rev 6) — PASS

# verdict evidence
A1 pass health 200 {"status":"ok","version":"17.6.0"} in 38.7 s
A2 pass ready 200 {"status":"ready","state":"running"}
A3 pass 0 ERROR lines; the only ⚠ are the two stock NOT bound — disabled by deployment policy lines
A4 pass /_console/ 200 HTML; apps [showcase_app, setup, account]
A5 pass 12 authored data-nav objects = 12 served
N1–N2 pass no seed rejection, no ERROR

integration-system.datasource-credential-refusal-matrix (rev 2) — PARTIAL

# verdict evidence
A1 pass 15 inline-credential shapes refused at all three doors (build exit 2 / POST /datasources 400 / PUT /meta 422); every refusal names external.credentialsRef
A2 blocked(environment) credentialsRef shapes publish (build + admin door 201); "connects" unprovable — no real Postgres / Mongo in the sandbox
A3 pass 0 planted values across every admin-door and meta-door body
A4 pass hasSecret / redactedConfigKeys distinguish set from unset
A5 pass legacy alias rows (planted with the server stopped) redacted on both doors
A6 pass unknown driver's password / authToken absent on read
A7 pass echoed redacted config preserves the stored credential
A8 pass member 403 on every read door incl. external_catalog and sys_secret; anonymous 401
N1 pass 0 planted values
N2 pass fixed since #21056: both mongo pairings → 400 DATASOURCE_ADMIN_ERROR naming config.url / config.username
N3–N7 pass doors agree; aliases and unknown drivers redacted

platform-core.console-login (rev 4) — PASS

# verdict evidence
A1 pass form sign-in 200 → /_console/home launcher; cookie + localStorage token (2 runs)
A2 pass after reload meta/app 200, no login redirect
A3 pass after POST /auth/sign-out the old bearer → 401
A4 pass navigation after expiry → /_console/login?redirect=…
A5 pass re-auth returns to the same route; data GET 200
N1–N2 pass wrong password 401, visible alert, no cookie / token

platform-core.nav-surfaces-render (rev 5) — PASS

# verdict evidence
A1 pass pin 49/49; all chart surfaces pass the bounded poll
A2 pass 49 served destinations (+1 separator) = SURFACES exactly
A3 pass bad object route → named "Object Not Found" inside the live shell, 0 pageerrors (2 loads)
A4 pass no render failure encountered

platform-core.builtin-apps-nav-render (rev 4) — PASS

# verdict evidence
A1 pass Setup 45/45 and Account 8/8 render for admin; Browse Marketplace catalog content blocked(environment) — named "Failed to load marketplace · Forbidden" (egress to cloud.objectos.ai denied)
A2 pass all 9 Setup groups; ?id=studio → items []
A3 pass member: Setup → "This app can't be opened"; Account opens
A4 pass member meta/app?id=setup → items []
A5 pass Account nav 8 vs 8
A6–A7 pass gated / off-capability entries absent as specified

records-forms.crud-roundtrip (rev 8) — PARTIAL

# verdict evidence
A1 pass form create → 201, read back verbatim
A2 pass PATCH changes only annual_revenue (+ updated_at)
A3 pass delete 200; filtered 0; by-id 404
A4 pass list reflects each mutation after full reload
A5 pass History shows CREATE and UPDATE with option labels and the real actor
A6 pass CJK name round-trips byte-exact; $search=华宁 finds it
A7 pass + blocked(fixture) clone → new id, values copied, autonumber / formula re-derived, cloner owns; readonly-column half unobservable on the stock seed
A8 pass clone of an RLS-invisible source → 404 RECORD_NOT_FOUND; admin count unchanged
N1–N4 pass empty required blocked client-side; server 400 required / invalid_format, counts unchanged

approvals.account-app-entry (rev 2) — FAIL

# verdict evidence
A1 pass dist (stamp = pin) carries the approvals:inbox registration
A2 pass Inbox → Approvals renders tabs, rows, keyboard hints and drawer
A3 pass non-admin persona: apps [showcase_app, account], inbox entry present
A4 pass Approve → 200; tally 0/2 → 1/2, persona dropped from pending
A5 pass ?request= cold load opens the drawer
A6 pass record link keeps the com.objectstack.account segment
A7 fail zh-CN selected: nav 待我审批 (server-served), but h1 "Approvals Inbox", tabs "My Pending / Submitted by me / All", badge "Pending" on every path; only i18n-locale-en-*.js fetched
N1–N2 pass inbox rendered every load; Setup absent

studio-authoring.first-run-loop (rev 3) — PASS

# verdict evidence
A1 pass fixed since #21056: New package dialog renders Display name / Package ID / Object namespace / Version / Type / Description; Create → POST /api/v1/packages 201, same document
A2 pass published object served with exactly the 3 authored status options
A3 pass Records grid add → row in GET /data/repairs_repair_ticket
A4 pass app in the launcher; list shows the label chip, not the raw value
A5 pass one "Server is ready", one PID throughout
A6 pass authoring into the read-only package → 422 WRITABLE_PACKAGE_REQUIRED / 403 NOT_OVERRIDABLE / 403 ITEM_LOCKED; nothing stored
N1–N2 pass no silent acceptance; labels not raw values

17.6-risk items

access-security.fls-mask-and-strip (rev 3) — PASS

# verdict evidence
A1 pass contributor PATCH name → 200, persisted
A2 pass contributor PATCH budget → 403 "Field write denied … [budget]"; value and updated_at unchanged
A3 pass admin PATCH budget 200; budget_remaining recomputes
A4 pass screenshot: contributor's Edit dialog shows Budget / Spent disabled, name enabled
A5 pass liaison: by-id, list, select, $select all omit budget/spent/budget_remaining keys; filter / sort / cross-field {"$field":"budget"} → 403 (17.6.0 #20954); console hides the Financials section; admin sees all
N1 pass the UI lock is backed by the server 403

access-security.record-access-explain (rev 2) — PASS

# verdict evidence
A1 pass Studio › Access › Explain access lists the 10 layers with a THIS RECORD block, "Decided by: View/Modify All bypass"
A2 pass POST /security/explain → record:{visible:true, decidedBy:"vama_bypass"}
A3 pass explain verdicts agree with each persona's own GET (200 / 404)
A4 pass plain member → visible:false, decidedBy:"sharing", no EXPLAIN_FAILED
A5 pass the panel's response equals the direct API call layer by layer
N1 pass member explaining another user → 403 (needs manage_users)

access-security.audit-log-browser (rev 2) — FAIL (stale clause)

# verdict evidence
A1 fail (stale) login row (actor = member, sys_session) and config_change row served; the admin's delete row → 0 rows on the data API, although stored with correct attribution in the telemetry DB — 17.6.0 30c530e5 (#21194)
A2 pass /apps/setup/system/audit-log: page rows equal the API rows
A3 pass Action select config_change re-issues GET …/sys_audit_log?$filter={"action":"config_change"}
A4 pass drawer shows separate Before / After JSON panels (checked on an update row — no delete row is reachable in 17.6.0)
A5 pass page ↔ API reconcile both directions
A6 pass POST / PATCH / DELETE → 405 OBJECT_API_METHOD_NOT_ALLOWED; nothing forged
N1–N2 pass every event wrote a row; writes refused

access-security.public-form-intake (rev 2) — FAIL

# verdict evidence
A1 pass anonymous GET /api/v1/forms/contact-us → 200 with exactly name/email/company/message
A2 pass forged owner/org/created_by never land
A3 pass forged status / source → landed new / web
A4 pass anonymous submit 201, row persisted (17.6.0's deny baseline does not reach this route)
A5 pass GET /forms/contact-us/lookup/owner_id → 404 ENDPOINT_NOT_FOUND
A6 pass anonymous /_console/f/contact-us submit → 201, "Thanks!" panel (screenshot)
A7 fail detail withheld pending maintainer (RUNNER rule 2); reproduced twice by the runner and twice by verifier V1
N1–N2 pass no forged anchors; __proto__ body inert

access-security.packaged-flow-write-door-parity (rev 1) — PASS

# verdict evidence
A1 pass PUT /meta/flow/showcase_urgent_task_alert → 403 NOT_OVERRIDABLE (?package= → 403 ITEM_LOCKED); unchanged
A2 pass PUT /automation/showcase_urgent_task_alert → 403 NOT_OVERRIDABLE, same lock message — the item's "EXPECTED FAIL" is stale since 4b45afae (#20817)
A3 pass DELETE /automation/… → 403 NOT_OVERRIDABLE; GET still 200
A4 pass GET byte-identical to the pre-probe capture; flow still fires
N1–N3 pass refusals quote the package lock; doors scored separately

api-backend.filter-comparand-conformance (rev 2) — FAIL (stale clause)

# verdict evidence
A1 pass string / number / boolean / null / ISO date strings answer the local computation; bigint and Date by pin
A2 pass object comparand → 400 quoting the accepted types; no rows
A3 pass pin: a bigint beyond ±2^53 is refused (REST not applicable, as the item records)
A4 pass relation / virtual / scalar dotted heads → 400 INVALID_FIELD with the class prescription
A5 fail (stale) POST /query → 400 VALIDATION_FAILED at query.where.f_number.$eq; GET $filter and the engine → 400 INVALID_FILTER — the #20116 door split (cfc3bcf1 #20247, dd1b8031 #20325), same as query-contract-matrix rev 3

api-backend.aggregate-contract-matrix (rev 1) — PASS

# verdict evidence
A1 pass all 6 functions grouped and ungrouped = local computation; count(field) skips nulls
A2 pass day / week / month / quarter / year buckets reconcile; ISO Monday weeks; nulls share one null key
A3 pass empty set: counts / sum 0, avg / min / max null, grouped []
A4 pass array_agg / string_agg → 400 with the retirement text; median → 400 enum; never 500
A5 pass contributor grouped count = its own baseline; admin full
A6 pass variant record complete; 17.6.0 refusals spot-checked (groupBy json, sum text, count_distinct tags → 400 INVALID_FIELD)

api-backend.date-range-preset-matrix (rev 1) — FAIL (assertion defect)

# verdict evidence
A1 pass all 13 presets: macro query = literal-window query = local computation (clock 2026-10-02 UTC recorded first; boundaries checked on both sides)
A2 pass 13 discriminating rows
A3 fail (assertion) bare {"signed_on":"today"} (the step-5 example) → 400 INVALID_FILTER with the temporal-door message, not bareDateRangePresetComparandMessage; the preset message appears only in ordering positions ($gte:"this_week" → 400 VALIDATION_FAILED with it) — by design (18.filter-preset-ordering-comparand-refused.ts); the guarded failure (200 with zero rows on a date field) never occurs
A4 pass unknown preset → 400 INVALID_FILTER / FILTER_TOKEN_UNKNOWN

dashboards.cube-query (rev 3) — PASS

# verdict evidence
A1 pass /analytics/meta?cube=showcase_delivery lists the measures and dimensions (no done_rate, as rev 3 expects)
A2 pass count by status = /data groupBy = raw tally
A3 pass SUM / AVG(estimate_hours) by status reconcile; single-table GROUP BY
A4 pass cube aggregate runs in the caller's execution context
A5 pass slot served; empty-slot arm by pin (29/29); anonymous 401 (17.6.0)
A6 pass filters / query / format bodies → 400 with their retirement hints
N1–N4 pass no 200-empty, no 500

dashboards.dataset-report-authoring (rev 1) — PASS

# verdict evidence
A1–A2 pass draft PUT + publish 200; widgets carry dataset + values only
A3 pass screenshot: metric 10, 5 bars, 4 donut slices
A4 pass summary totals = /data SUM by status (244.0)
A5 pass matrix cells = /data groupBy [status, priority]
A6 pass joined blocks partition per measure (220 + 24 = 244 est_hours; 8 + 2 = 10 tasks)
A7 pass chart bars match the table
A8 pass summary / matrix / joined / tabular each rendered
N1–N2 pass joined block type → 422; dangling dataset → publish 422

dashboards.global-filters-rescope (rev 2) — PASS

# verdict evidence
A1–A2 pass region and custom date window re-query every bound widget with the right field per object
A3 pass opted-out widget never re-queries
A4–A5 pass widget values = /data counts under the same filters
A6 pass the composing status tile (kpi_awaiting_review) = /data (2 / 0)
A7–A9 pass Studio field override persists and drives the widget; Reset only when dirty; optionsFrom list from a server GROUP BY
N1–N4 pass

dashboards.strict-widget-rejects-stray-keys (rev 2) — PASS

# verdict evidence
A1–A2 pass 11 legacy widget keys → 422 naming the key, each with the dataset prescription
A3 pass chartFlavour → 422 echoing the key; near-keys get did-you-mean
A4 pass layer-misplaced keys get layer guidance
A5 pass no rejected PUT replaced the last good body
A6 pass corrected widget renders (screenshot)
N1–N2 pass metadata-editor Source tab edit → autosave 422, nothing persisted

records-forms.field-type-matrix (rev 2) — PASS

# verdict evidence
A1 pass pin 91/91
A2 pass screenshot then labels: every f_* field renders its own widget
A3 pass bad option → 400 invalid_option; missing name → 400 required
A4 pass password / secret masked on read; plaintext 0 hits
A5 pass formula / autonumber re-derived; caller-supplied values ignored (17.6.0 formula strip)
A6 pass dangling references → 400 reference_not_found per field
A7 pass expense and invoice roll-ups equal their lines; live line add moves the total

records-forms.import-transform-matrix (rev 1) — FAIL (stale clause)

# verdict evidence
A1 pass none / map / constant / split / join each produce the expected row values
A2 fail (stale) javascript mapping → 400 UNSUPPORTED_TRANSFORM naming the missing server-side sandbox, 0 rows — but the message no longer names framework#2611: f115b1f (#21188) states the decision in words
A3 pass lookup resolves the id; unknown name fails its row reference_not_found
A4 pass JSON through a csv mapping → 400 MAPPING_FORMAT_MISMATCH; xlsx accepted

records-forms.named-import-mapping (rev 1) — PASS

# verdict evidence
A1 pass 2 rows created with mapped fields and value maps
A2 pass re-import → 0 created, 2 updated, same ids
A3 pass unknown mapping → 404 MAPPING_NOT_FOUND, nothing landed

automation.packaged-flow-subflow-disable-refusal (rev 1) — PASS

# verdict evidence
A1–A2 pass disabling a called subflow → 409 DELETE_RESTRICTED naming the caller
A3 pass ledger 0 rows after refusals; caller run's subflow step succeeded
A4 pass enabling a child is never guarded
A5 pass caller off → child off 200 (the clause's "EXPECTED FAIL" note is stale: 36d043b #20724, 0d9349f #20759)

automation.packaged-flow-clone-contract (rev 1) — FAIL

# verdict evidence
A1 pass missing name / label → 400 VALIDATION_FAILED; nothing created
A2 pass clone differs from source only in name / label / status: draft; no ancestry keys
A3 pass clone bound as draft; both flows fire once per urgent create; notice = FLOW_CLONE_NOTICE
A4 pass same-name 409; unknown source 404; bad name 400; unknown body key 400
A5 pass subflow reference preserved
A6 pass PUT /automation/<clone> 200, persisted
A7 fail durability half now PASSES (cold restart: GET 200, still fires — cb4c31d #20907); reachability half FAILS — no Studio rail lists the clone (verifier V2 confirmed)

automation.flow-toggle-kill-switch (rev 2) — PASS

# verdict evidence
A1–A4 pass runs 0 → 1; off: 1 → 1; /_status flips both ways; on: → 2; unknown flow toggle 404

automation.setup-packaged-automation-board (rev 2) — PARTIAL

# verdict evidence
A1 pass served nav_packaged_automation entry; sidebar click lands on …/component/automation/packaged
A2 pass 30 packaged flow rows = /meta/flow packaged set; clone absent
A3 pass switch persists across reload, same ledger row
A4–A5 pass Clone only on flow rows; no lineage text
A6 blocked(fixture) admin half passes (409 text word for word in a row alert); plain-member half undrivable — a plain member cannot reach Setup (setup.access)
A7 pass clone dialog validation and success notice
A8 pass plain member does not reach the page ("This app can't be opened"); read postures recorded

integration-system.datasource-admin-lifecycle (rev 3) — PASS

# verdict evidence
A1 pass drivers catalog 200 for admin, 401 anonymous
A2 pass 11 routes: anonymous 401, no-capability member 403 manage_platform_settings
A3 pass POST /test draft ok:true, nothing persisted
A4 pass migrate-credential: already-bound / migrated / refused outcomes, no 400s
A5–A6 pass runtime create 201; secret never echoed
A7 pass bad drafts 400, never 500
A8–A9 pass 503 arm by pin; unknown 404 RESOURCE_NOT_FOUND

platform-core.metadata-authoring-roundtrip (rev 2) — PASS

# verdict evidence
A1–A2 pass draft view in a writable package listed in _drafts; publish → served and rendered as a tab
A3 pass record page authored in the metadata editor → draft PUT 200
A4–A5 pass writes into the read-only package → 422 WRITABLE_PACKAGE_REQUIRED; object overlay 403 NOT_OVERRIDABLE
A6 pass bad view body → 422 INVALID_METADATA, nothing stored

studio-authoring.view-authoring-live (rev 1) — FAIL (assertion defect)

# verdict evidence
A1–A2 pass draft save 200; nothing served before publish
A3 pass after publish the authored tab and form layout apply, no restart
A4 fail (assertion) GET /meta/view?object=repair_asset serves the authored bodies as repair_asset.default / repair_asset.form; the container name never appears — the object door returns expanded items by design (expandViewContainer, #7163)
A5 pass second publish re-orders the columns live

studio-authoring.custom-page-source-tiers (rev 1) — PASS

# verdict evidence
A1–A4 pass html pages render compiled static boards; react page binds live data (5 = /data total) and edits rows
A5 pass OS_PAGE_REACT=off disables react pages with the named message; html pages still render
A6 pass react fault → "React page error"; html fault → "HTML page failed to compile (3)" in the editor preview

Fails — reproduction rules and dispositions

Product defects:

R1 · access-security.crud-permission-matrix · A1 — product defect, already tracked at #21057 (fails closed: over-refusal, not an exposure). Verifier V1: CONFIRMED.

  1. Fresh boot; admin invites + signs up a delegate D and a target T.
  2. Admin POST /api/v1/data/sys_user_permission_set {"user_id":D,"permission_set_id":<id of showcase_field_ops_delegate>} → 201; POST /api/v1/data/sys_business_unit_member {"business_unit_id":"bu_field_ops","user_id":D} → 201.
  3. D POST /api/v1/data/sys_user_position {"user_id":T,"position":"contributor","business_unit_id":"bu_west_coast"}.
  4. Expected 2xx (adminScope = Field Operations + subtree). Actual 403 PERMISSION_DENIED "delegated 'insert' on sys_user_position rejected — business unit 'bu_west_coast' is outside the delegated subtree" — also for bu_field_ops itself. Admin with the identical payload → 201.
  5. Mechanism oracle: every seeded sys_business_unit row reads organization_id: null; after admin PATCHes the active org onto the seeded units, D's create → 201 and an out-of-subtree create (bu_hq_finance) stays 403.

R2 · access-security.public-form-intake · A7 — product defect. Detail withheld pending maintainer (RUNNER rule 2). Verifier V1: CONFIRMED.

R3 · automation.packaged-flow-clone-contract · A7 (reachability half) — product defect, not previously filed (it was the FOLLOW-UPS §8 D18 row, marked "safe to file"). Verifier V2: CONFIRMED.

  1. Admin POST /api/v1/automation/showcase_urgent_task_alert/clone {"name":"qa_urgent_alert_clone","label":"QA urgent alert clone"} → 200.
  2. POST /api/v1/data/sys_metadata/query {"where":{"name":"qa_urgent_alert_clone"}} → package_id: null, scope: platform.
  3. GET /api/v1/meta/flow?package=com.example.showcase → 30 flows without the clone; bare GET /api/v1/meta/flow → 31 with it.
  4. Console /_console/studio → "No writable packages yet"; /_console/studio/com.example.showcase/automations lists the 30 packaged flows, not the clone; …?surface=flow%3Aqa_urgent_alert_clone redirects to another flow.
  5. Expected (ADR-0126 §1.3 / §7.1, the Setup page's "Editing happens in Studio") the clone is listed on a Studio Automations rail; actual no Studio surface at the pin lists or opens a package-less flow (the rail is loadPackageSurfaces(client,'flow',packageId)).

R4 · approvals.account-app-entry · A7 — product defect in the console, objectui#11326, still present at the pin (fixed upstream in objectui d0fba91aa, not an ancestor of 31971ff1e28f). Verifier V4: CONFIRMED (the zh catalogue at the pin holds every expected string, so the clause is not stale).

  1. Fresh boot; sign in; open /_console/apps/com.objectstack.account/component/approvals/inbox.
  2. Avatar → Preferences → Language → 中文(中国); then a full load of the same URL.
  3. Expected h1 / tabs / badge in Chinese (审批中心 · 待我审批 / 我发起的 / 全部 · 待审批). Actual "Approvals Inbox", "My Pending / Submitted by me / All", "Pending"; localStorage['objectui-locale']="zh-CN", <html lang="zh-CN">, the server-served nav label 待我审批; only i18n-locale-en-*.js fetched although i18n-locale-zh-*.js is in dist. Mechanism at the pin: packages/i18n/src/locales/registry.ts keys built-in loaders by base language, and the provider asks with the region-tagged code.

Checklist clauses (no product defect — the docs PR linked in the close-out re-points them):

R5 · access-security.audit-log-browser · A1 — stale clause. Admin DELETE /api/v1/data/showcase_task/<id> → 200; admin GET /api/v1/data/sys_audit_log?filter={"action":"delete"} → expected (clause) 1 row, actual total 0; the row is stored with correct attribution (direct read of the telemetry DB). Changed deliberately by 30c530e5 (#21194): the ledger serves a non-system reader, administrators included, only rows about records it can read (17.6 notes, "The compliance ledger and the activity stream…").

R6 · api-backend.filter-comparand-conformance · A5 — stale clause. POST /api/v1/data/showcase_field_zoo/query {"where":{"f_number":{"$eq":{"x":1}}}} → 400 VALIDATION_FAILED (fields[0].field = query.where.f_number.$eq); GET …?$filter={"f_number":{"$eq":{"x":1}}} → 400 INVALID_FILTER; engine door → INVALID_FILTER. Every door refuses with the same contract sentence and none returns rows; the code differs because #20116 moved the POST refusal into the request schema (cfc3bcf1 #20247, dd1b8031 #20325) — the split query-contract-matrix rev 3 already records.

R7 · api-backend.date-range-preset-matrix · A3 — assertion defect. POST /api/v1/data/showcase_account/query {"where":{"signed_on":"today"}} → 400 INVALID_FILTER with the temporal-door message; {"where":{"signed_on":{"$gte":"this_week"}}} → 400 VALIDATION_FAILED with bareDateRangePresetComparandMessage. The preset message is deliberately an ordering-position refusal (18.filter-preset-ordering-comparand-refused.ts: "Ordering positions only at the schema door"); an equality on a date field is refused by the temporal door. The guarded failure (200 with zero rows) does not occur.

R8 · records-forms.import-transform-matrix · A2 — stale clause. PUT /api/v1/meta/mapping/qa_l7_js with one entry transform:"javascript"; POST /api/v1/data/showcase_inquiry/import {"format":"csv",…,"mappingName":"qa_l7_js"} → 400 UNSUPPORTED_TRANSFORM naming the missing server-side sandbox, 0 rows; the message no longer names framework#2611 since f115b1f (#21188: refusals state each decision in words instead of a tracker number).

R9 · studio-authoring.view-authoring-live · A4 — assertion defect. PUT /api/v1/meta/view/qa_repair_asset_views?mode=draft&package=<writable pkg> with {name, object:"repair_asset", list, form}; publish; GET /api/v1/meta/view?object=repair_asset → repair_asset.default and repair_asset.form carrying the authored config; the container name appears 0 times. The object door returns expanded <object>.<key> items by design (expandViewContainer, #7163).

Fixture gaps and environment notes

  • crud-permission-matrix A6: unobservable until security(core, plugin-security): an unauthenticated principal never resolves the permission sets bound to the guest anchor; ADR-0090 D9 is declared and seeded but not enforced #21158 gives anonymous callers a permission-set channel (17.6.0 deny baseline); the reason is a dependency, not a fixture.
  • datasource-credential-refusal-matrix A2: no real Postgres / MySQL / Mongo in the sandbox (ECONNREFUSED).
  • crud-roundtrip A7 readonly half: no readonly column is settable over HTTP on the stock seed.
  • setup-packaged-automation-board A6: needs a persona with setup.access but without manage_metadata (e.g. organization_admin); a plain member never reaches Setup.
  • dashboards global-filters D2 needs a writable package (the showcase package is read-only in Studio; duplicate → 422 DUPLICATE_SOURCE_NOT_A_BASE).
  • Sandbox egress denies cloud.objectos.ai (marketplace catalog).
  • Container memory (15 GB, 4 CPUs) was contended with 9 lanes: several vitest runs were SIGKILLed and Chromium renderers crashed until launched with --disable-dev-shm-usage; every affected run was repeated and its verdict rests on the repeat.
  • Killing a recorded pnpm wrapper PID leaves the objectstack dev child listening; teardown needs the process tree or the port's listener.
  • The showcase's dist/objectstack.json is converted forward on load at boot (ADR-0087 page-component-filter-record-to-rule-array) — harmless.

Close-out (extracted cards, checklist-accuracy findings, the docs PR) follows in a comment.


Generated by Claude Code

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions