Repository navigation
Description
domain:devx#1 · os-warren · session_01JQwxbX9avDwM2goWd7zimE
Part of #27377 (wave 4, W4-C3b1)
QA run record for the 18.3.0 release-candidate sweep, card W4-C3b1: the unpinned browser and mixed items of studio-authoring, records-forms, approvals, automation and dashboards, 36 items. Every clause was driven live in a real browser (Playwright chromium) plus its API half, against the Console built in-repo from the subject. Item verdicts are derived from the clause verdicts, never hand-assigned. Run under .claude/skills/checklist-test/SKILL.md and docs/qa/platform-checklist/RUNNER.md. Compared against wave 3 record #27441.
The per-clause verdict table is too long for one issue body (about 130 KB of one-line evidence), so it is posted as the three comments that follow this record, in item order. Everything else is here.
Environment fingerprint
- Framework:
7fa9873f38f7a0158e3ce17338dcc0f446b113e3(main, 2026-11-02T02:44:25Z), detached and pinned; it never followed main.ui/is the same commit.npm_config_nodedir=/opt/node22 pnpm install --frozen-lockfile(34 s),pnpm exec turbo run build --concurrency=2 --filter=!@objectstack/docsunderscripts/pm/os-verify-lock.sh(15m57s, exit 0),pnpm console:build --concurrency=2(cache hit + copy, 3 s),node scripts/check-dev-prereqs.mjsgreen (106 artifacts). Chromium build 1243 matches playwright-core. - Boot (per runner): a tar copy of
examples/app-showcaseunder the runner's own scratch dir,node_modulessymlinked to the tree. Command:OS_PORT=PORT OS_STORAGE_LOCAL_ROOT=RUN/uploads OS_HOME=RUN/home setsid node packages/cli/bin/run.js dev --ui --seed-admin --no-watch -p PORT -d file:RUN/data.db. A canary on 41300 measured health 200 in about 12 s,/_console/200, andGET /api/v1/meta/appnamingshowcase_app, setup, account. - Ports 41310–41414: one block of five per runner (41310 R1a, 41320 R1b, 41330 R2a, 41340 R2b, 41350 R3, 41360 R4a, 41370 R4b, 41380 R5, 41390 R6, 41400 R7, 41410 R8). Every runner had its own file DB and browser context.
- Extra boots, each on the runner's own port and DB: walled
groupboots (OS_TENANCY_POSTURE=group OS_PLATFORM_OWNER_EMAIL=admin@objectos.ai OS_AUTH_MEMBERSHIP_POLICY=invite-only) for approvals.decision-action-matrix A9 (41322) and for an approvals probe (41311); a fresh stock DB for the decision matrix (41321); a fresh DB for an automation clone probe (41341). - Seed:
--seed-admin(admin@objectos.ai), the stock showcase seed and its demo personas. Members were minted per RUNNER, by invite then sign-up or by admin create-user. Each persona's identity was proved withGET /api/v1/auth/get-sessionbefore its cells. Writable scratch packages (POST /api/v1/packages) carried the authoring fixtures; theqaact-placement-specimenandqagrouprecipes were installed into the scratch app copies only. - Execution: 11 runner subagents, at most 3 browser runners at a time, staggered. The orchestrator read every runner's result, checked every FAIL disposition against source and the ledger, and checked each extracted defect against the board before filing.
- Hygiene: after the run,
git statusis clean,examples/app-showcase/.objectstackdoes not exist, and no listener remains on 41300–41415. Scratch packages, install-local recipes and drafts were deleted or discarded by each runner, or live only in its scratch DB. - Time: environment prep about 74 min (22 min orchestrator: install, build, console, canary; 52 min summed across runners: copies, boots and fixtures) against about 256 min of testing plus 67 min of off-clause probes, summed across runners. Prep to testing is about 1 : 3.5 (1 : 4.4 with probes). Wall clock: 03:33Z to 05:54Z for the runs.
Scope
Selector release-18.3.0-w4-c3b1: the unpinned browser/mixed list of anchor #27377 wave 4, card W4-C3b1, as computed at the subject (/mnt/project-files/release-18.3.0/W4-C3b1-items.txt). No planned items in the set. Revisions, read from the ledger at the subject (node scripts/checklist-select.mjs ID --json):
approvals.per-group-signoff r11 · approvals.quorum-m-of-n r9 · approvals.inbox-metadata-actions r8 · approvals.viewer-gating-submitter-side r7 · approvals.decision-action-matrix r10 · automation.flow-run-step-nesting r8 · automation.rollup-summary-filter r7 · automation.flow-node-type-matrix r12 · automation.flow-error-handling r6 · automation.packaged-flow-clone-contract r10 · dashboards.strict-widget-rejects-stray-keys r3 · dashboards.chart-type-matrix r7 · dashboards.global-filters-rescope r5 · records-forms.form-view-gallery r6 · records-forms.action-location-matrix r18 · records-forms.calendar-interactions r4 · records-forms.field-group-visible-when r11 · studio-authoring.view-authoring-live r5 · studio-authoring.draft-publish-lifecycle r10 · studio-authoring.authoring-validation-not-persisted r5 · studio-authoring.packaged-automation-studio-lock r6 · studio-authoring.doc-on-app-menu r9 · studio-authoring.held-edit-leave-guard r5 · studio-authoring.flow-edge-type-authoring r4 · studio-authoring.summary-field-authoring r5 · studio-authoring.api-written-keys-survive-studio-save r3 · studio-authoring.dashboard-widget-and-filter-types r5 · studio-authoring.report-authoring-options r3 · studio-authoring.app-and-nav-properties r5 · studio-authoring.authored-page-slots-and-variables r3 · studio-authoring.access-permission-set-lifecycle r4 · studio-authoring.access-facets-runtime r4 · studio-authoring.access-owd-and-suggestions r5 · studio-authoring.no-entry-property-matrix r4 · studio-authoring.uninstalled-package-refused r3 · studio-authoring.expression-editors r8
Clause ids: A# is acceptance[#] and N# is negative[#], both zero-based.
studio-authoring.expression-editors was run here as the live retest of the #27482 residual: its browser autosave clauses are judged live; its A7 is a pin-only clause, recorded skipped here because the pin belongs to card W4-C2b.
Derived item verdicts
29 PASS / 0 PARTIAL / 7 FAIL / 0 BLOCKED / 0 NOT-RUN (36 of 36 judged).
- FAIL (7), with disposition:
- automation.packaged-flow-clone-contract (A1, A2, A6): stale spec, ADR-0146 D4 / Flow clone door → "new similar": the copy lands in a writable target package, by default the paired <base>.custom, never package-less #28149.
- studio-authoring.packaged-automation-studio-lock (A0 New half, A2): stale spec, ADR-0146 D3 / Studio: a base and its <base>.custom as one surface — "customized" / "added" marks, "only customizations", the pair folded under its base, New lands in the pair #28572 and D4 / Studio: switch a read-only package's flow off for this deployment from the Automations pillar — ledger switch on the item, posture-aware, refusal verbatim #28148.
- studio-authoring.access-permission-set-lifecycle (A3): stale spec, ADR-0146 D3 / Studio: a base and its <base>.custom as one surface — "customized" / "added" marks, "only customizations", the pair folded under its base, New lands in the pair #28572.
- studio-authoring.draft-publish-lifecycle (A10): stale spec, metadata audit: sys_metadata_audit documents denied codes (not_overridable, not_creatable, invalid_metadata, destructive_change) that no audit write stamps — enforce or remove #28355 (refused saves now write a denied audit row).
- studio-authoring.expression-editors (A2, A4, A6, N0): stale spec against the Studio: an expression field that says "Valid CEL" should read the draft's server verdict at its own path (RLS predicate, flow edge condition) #26858 ruling and fix(app-shell): a server error at an RLS clause's path holds the permission-set autosave #27612's shipped design; the re-point waits on the open Studio: an expression the engine refuses (RLS USING on a formula field, a null-unsafe validation predicate) keeps autosaving — the server verdict is shown on the field but never holds the save #27482.
- studio-authoring.dashboard-widget-and-filter-types (A4): product defect (a partly filled nested list input autosaves and is refused).
- studio-authoring.no-entry-property-matrix (A1, A2, N0): product defect, four inert keys and one unrendered key (titleFormat, field.searchable, list-view sharing.lockedBy without a type, view.form drawerSide/drawerWidth, the report null bucket label).
- PASS (29): every other item.
Deltas against wave 3 (#27441, at c4c515a)
| item | wave 3 | wave 4 | note |
|---|---|---|---|
| studio-authoring.held-edit-leave-guard | FAIL | PASS | #27464 holds: a nav rename then reload raises beforeunload (2 of 2) |
| studio-authoring.summary-field-authoring | FAIL | PASS | #27461 and #27462 hold: an unconfigured rollup is held with no PUT; Sum/Avg offers number and currency only |
| studio-authoring.app-and-nav-properties | FAIL | PASS | #27457 holds: a bare-string CEL nav visible shows for the admin, hides for the member |
| studio-authoring.authored-page-slots-and-variables | FAIL | PASS | #27457 holds: size(page.items) > 0 evaluates on first render |
| studio-authoring.access-owd-and-suggestions | FAIL | PASS | #27458 holds: the 409 opens "Make the relationship required?" and the confirm saves with force |
| studio-authoring.no-entry-property-matrix | NOT-RUN | FAIL | every variant row driven this time; 5 rows fail, 4 knownGaps[1] keys not judgeable as the item says |
| studio-authoring.dashboard-widget-and-filter-types | FAIL | FAIL | #27459 holds for the empty item; a different shape fails (partly filled optionsFrom) |
| studio-authoring.expression-editors | FAIL | FAIL | same stale-spec clauses; #27452 holds; #27482 still open |
| automation.packaged-flow-clone-contract | PASS | FAIL | stale spec after ADR-0146 D4 (#28149), not a regression |
| studio-authoring.packaged-automation-studio-lock | PASS | FAIL | stale spec after ADR-0146 D3/D4 (#28572, #28148), not a regression |
| studio-authoring.access-permission-set-lifecycle | PASS | FAIL | stale spec after ADR-0146 D3 (#28572), not a regression |
| studio-authoring.draft-publish-lifecycle | PASS | FAIL | stale spec after #28355, not a regression |
| approvals.viewer-gating-submitter-side, automation.flow-run-step-nesting, dashboards.strict-widget-rejects-stray-keys, records-forms.calendar-interactions | (not in wave 3) | PASS | new to the set |
| the other 20 items | PASS | PASS | unchanged |
Wave-3 off-clause cards re-checked here: #27451 holds (GET /api/v1/approvals/requests/UNKNOWN/actions → 404 REQUEST_NOT_FOUND), #27460 holds (a two-dimension sankey draws 8 links for 8 cells), #27445 holds (an undeclared filterBindings key → 422 at save and at publish), #27452 holds (no stale server error under newly edited RLS text). No PASS turned into a product-defect FAIL.
Reproduction rule for every fail
R-1 · automation.packaged-flow-clone-contract · A1 / A2 / A6 · stale spec (ADR-0146 D4, amended 2026-10-31; #28149; notice reworded by #27886)
- Boot the showcase on its own port and DB; sign in admin@objectos.ai.
POST /api/v1/automation/showcase_urgent_task_alert/clone {"name":"qa_urgent_alert_clone","label":"QA urgent alert clone"}→ 200data:{flow:{… status:"obsolete"}, packageId:"com.example.showcase.custom", notice:"The copy is switched off and does not run until you switch it on, and it still uses the same subflows, actions and objects as the original."}.GET /api/v1/automation/qa_urgent_alert_clone→_packageId:"com.example.showcase.custom", status obsolete.POST /api/v1/data/showcase_task {title, project, priority:"urgent"}three times; read both flows' runs → base +1 per create, clone 0.- Cold restart on the same DB;
/_console/studio/~org/automations→ "No flows outside a package…";/_console/studio/com.example.showcase/automationslists the clone, "In com.example.showcase.custom".
Expected (clause r10): a package-less clone, status draft, armed (double-fire), listed on~org. Actual: as above, matchingpackages/runtime/src/flow-clone.ts(FLOW_CLONE_STATUS,FLOW_CLONE_NOTICE) andresolveCloneTarget. Reproduced with two clones over two processes.
R-2 · studio-authoring.packaged-automation-studio-lock · A0 (New half) / A2 · stale spec (ADR-0146 D3 #28572 "New lands in the pair"; D4 #28148 "the ledger switch, on the item")
- Form sign-in as admin;
GET /api/v1/packages/com.example.showcase→writable:false. /_console/studio/com.example.showcase/automations?surface=flow%3Ashowcase_urgent_task_alert(rail, "Read-only" badge, header switch "On for this deployment").[data-testid=studio-rail-add]("New automation") is present and opens the New dialog, which targets the.custompair.[role=switch][data-testid=auto-ledger-switch]is enabled; clicking it postsPOST /api/v1/automation/showcase_urgent_task_alert/toggle {"enabled":false}→ 200, then{"enabled":true}→ 200. The flow definition stays byte-identical (GET /api/v1/meta/flowmd5 unchanged).
Expected (clause r6): no New; the switch disabled with no request. Actual: as above, matchingStudioDesignSurface.tsx(newInPair,ledgerRow→ FlowLedgerSwitch). Reproduced on 2 fresh loads each. The lock itself holds (A1, A3, N0, N1 pass: forged writes 403 ITEM_LOCKED / NOT_OVERRIDABLE, inspector inputs disabled).
R-3 · studio-authoring.access-permission-set-lifecycle · A3 · stale spec (ADR-0146 D3, #28572)
- Form sign-in →
/_console/studio/com.example.showcase/access?surface=permission:showcase_contributor. getByTestId('studio-rail-add')("New permission set") is present and enabled beside the "Read-only" chip.- Click → "New permission set" → "QR7 Pair Set" → Save as draft →
POST /api/v1/packages/com.example.showcase/ensure-customization201{"packageId":"com.example.showcase.custom","created":true}, thenPUT /api/v1/meta/permission/qr7_pair_set?mode=draft&package=com.example.showcase.custom&surface=studio200.
Expected (clause): a read-only package offers no New set. Actual: New lands in the pair, never in the base; the packaged set's own controls are all disabled (192 matrix checkboxes) and forged writes are refused (A4 passes). Reproduced on 2 fresh loads.
R-4 · studio-authoring.draft-publish-lifecycle · A10 · stale spec (#28355)
- As admin:
PUT /api/v1/meta/dashboard/qa_lifecycle_probe?mode=draft&package=PKGwith a widget carrying a stray key ("object":"showcase_task") → 422 INVALID_METADATAwidgets.0 [unrecognized_keys]. GET /api/v1/meta/dashboard/qa_lifecycle_probe/audit.
Expected (clause r10): a 422 spec refusal writes no row. Actual: one row per refusal,{"source":"protocol.saveMetaItem","operation":"save","outcome":"denied","code":"invalid_metadata","actor":"ADMIN_ID",…}, by design ofpackages/metadata-protocol/src/protocol-refusal-audit.ts(REFUSAL_AUDIT_CODES= NOT_OVERRIDABLE, NOT_CREATABLE, INVALID_METADATA, DESTRUCTIVE_CHANGE). A NO_DRAFT 404 still writes none, and every row carries the authenticated actor. Reproduced 2 of 2 with two different bodies.
R-5 · studio-authoring.expression-editors · A2 / A4 / A6 / N0 · stale spec (#26858 ruling "no client copy of any publish rule"; #27612's shipped design; re-point waits on #27482)
POST /api/v1/packages {"manifest":{"id":"com.qa.rsx","name":"QA R7 Expr","version":"0.1.0","type":"app","namespace":"qrx"}}.PUT /api/v1/meta/object/qrx_job?mode=draft&package=com.qa.rsx(sharingModel public_read_write; numberest_hours; selectstatus; formulaf_formula=record.est_hours * 1.1); publish-drafts.PUT /api/v1/meta/permission/qrx_job_editor?mode=draft&package=com.qa.rsxwith objects.qrx_job read/edit and one select RLS policyusing: "record.status == 'open'"; publish.- Form sign-in →
/_console/studio/com.qa.rsx/access?surface=permission:qrx_job_editor→ Row-level security → USING (read filter) =record.f_formula > 5.
Expected (clause as written): an error-tier lint and the save held, no PUT. Actual: "Valid CEL" for about 1.7 s, thenPUT …/qrx_job_editor?mode=draft&package=com.qa.rsx&surface=studio200, then the server's INVALID_FIELD verdict at the field; publish → 422rls-predicate-unenforceable. Each newly refused text autosaves once more; an edit to another clause is held. Non-pushdownable and parse-fault predicates are held with 0 PUTs. Reproduced on 3 fresh loads. This is the residual Studio: an expression the engine refuses (RLS USING on a formula field, a null-unsafe validation predicate) keeps autosaving — the server verdict is shown on the field but never holds the save #27482 describes; the item's own knownGaps[3] says to score these fail until Studio: an expression the engine refuses (RLS USING on a formula field, a null-unsafe validation predicate) keeps autosaving — the server verdict is shown on the field but never holds the save #27482 is ruled.
R-6 · studio-authoring.dashboard-widget-and-filter-types · A4 · product defect (extracted)
- A writable package with an app whose navigation names a dashboard with one metric widget (here
com.qa.r3, dashboardr3_holdon datasetr3_ticket_metrics). /_console/studio/PKG/interfaces→ the dashboard → Properties → Filters → Global Filters → "Add item" (0 PUTs, held as designed).- Fill Field (
region), set Input Type = select (eachPUT /api/v1/meta/dashboard/r3_hold?mode=draft&package=PKG&surface=studio200). - Type
r3_ticketinto Options From → Object (#mdf-globalFilters.N.optionsFrom.object) and Tab → the PUT carries"optionsFrom":{"object":"r3_ticket"}→ 422 INVALID_METADATA,globalFilters.N.optionsFrom.valueField [invalid_type]; …labelField [invalid_type]. - Edit the dashboard Description, then its Label → each PUT carries the same incomplete item and is refused 422; the banner reads "Changes not saved: the server refused this draft…".
- Reload → no leave prompt (
new Event('beforeunload',{cancelable:true})→ defaultPrevented false); the Description and Label edits are gone.
Expected (clause): the editor never autosaves an incomplete filter and no other edit is lost. Actual: as above. Reproduced 3 times on fresh loads. The Studio: "Add item" on a dashboard list property autosaves an empty item (422), and every later edit is refused and lost until reload #27459 shape (an empty Add item) holds. Suspected source:listItemHeldInput()inui/packages/app-shell/src/views/studio-design/metadataError.tsholds an item only when the unset input sits directly below the item.
R-7 · studio-authoring.no-entry-property-matrix · A1 / A2 / N0 · product defect (extracted, one card per key)
Each row: author the key on a writable package (PUT /api/v1/meta/TYPE/NAME?mode=draft&package=app.qar8 → POST /api/v1/packages/app.qar8/publish-drafts → published; the live read carries the key verbatim), then read its runtime effect.
- object.titleFormat
'{code} @ {city}'onqar8_tf2with no authored nameField: the served object carries a stampednameField:"code"; record{code:'ZX-42', city:'Lyon'}→ detail H1 and list primary read "ZX-42", not "ZX-42 @ Lyon". 3 repros. - field.searchable
trueon acodefield and anhtmlfield ofqar8_fsrch2:GET /api/v1/data/qar8_fsrch2?$search=QQCODEand?$search=QQHTML→[];?$search=QQCODE&$searchFields=snippet→ 400 INVALID_FIELD "Field 'snippet' on object 'qar8_fsrch2' is not searchable…". A field with searchable absent is scanned exactly like one with true. 3 repros. - view.list
sharing: {lockedBy: 'admin@objectos.ai'}(notype) onqar8_vl.v_sharing: the view opened from the switcher shows no lock indicator and no "Locked by". 3 repros. Mechanism (orchestrator, from source):ui/packages/plugin-list/src/ListView.tsxrenders the indicator only whenschema.sharing.typeis set, whileViewSharingSchema.typedefaults tocollaborativein the spec. - view.form
drawerSide:'left', drawerWidth:'480px'onqar8_fdr.form: New and/_console/forms/qar8_fdr.formrender exactly as the control (no drawer); a bad enum is refused 422, so the key is validated and never read. 2 repros. The spec describe says the drawer half "stays open on spec(ui): FormViewSchema declares drawerSide / drawerWidth and buttons.reset, but no console entry point renders a drawer form or a reset button; implement or retire them (ADR-0049) #27153", which is closed. - dataset dimension
nullLabel:'No region (R8)'with reportqar8_report(summary, rows region):/_console/apps/com.example.showcase/report/qar8_reportshows the null row as "—"; the page's ownPOST /api/v1/analytics/dataset/queryanswer carriesfields.region.nullLabel"No region (R8)". 3 repros.
Expected (clause): every key has its declared runtime effect, or is refused. Actual: as above. The fourknownGaps[1]keys (mailAssociation, ackPlaintextMasking, lifecycle.ttl, dimensions) were left unjudged as the item says. Every other row of the matrix passed; see the clause comments.
Fixture gaps, partial coverage and handover
- No item is partial, blocked or not-run.
studio-authoring.app-and-nav-propertiesA5 leaves its requiresObject cell unjudged as its knownGaps[0] says; the clause's other keys passed.studio-authoring.expression-editorsA7 isskipped(pin-only, run by W4-C2b). studio-authoring.view-authoring-live: end-user surfaces were scored as the admin, which the item allows (no non-admin grant on a runtime-authored object).records-forms.field-group-visible-when: the qa_group_gate recipe as written is refused 422security-owd-unset(no sharingModel); provisioned withsharingModel: 'public_read_write'added.records-forms.calendar-interactions: the week/day time-grid move and the resize-end variants cannot be exercised on the stock fixture (date-only start field, no end field).dashboards.global-filters-rescopeD2: the field-override combo offers only the dataset's declared dimensions, so the dateRange re-target needs a dataset with two date dimensions; region → status was re-targeted instead.records-forms.action-location-matrixA2:open_docstargets https://objectstack.ai, which this container's proxy refuses; the url leg is scored on the issued navigation.- The checklist-accuracy findings (about 45 lines) go to the anchor [sweep] v18.3.0 release test sweep #27377 in the final report, not here.
Off-clause probes (RUNNER rule 8)
About 67 minutes in total across 11 runners, on the same boots and as the same personas (admin, demo personas, minted members; a walled group boot for one approvals probe).
Doors probed: approvals decision, action-route, reassign, recall, remind and comment doors; automation runs, trigger, resume, clone and toggle; the actions dispatcher; metadata save, draft read, history, audit, diff, rollback and publish-drafts; analytics dataset query; share-links and export; the named form page; dashboard and report save; flow publish and registration; book tree and doc read; the avatar menu.
Boundary classes tried: empty string, blank id, missing id, unknown id, wrong type, inverted or non-numeric versions, limit=0, partial nested objects, duplicate names, undeclared cycles, opposite posture (member on admin doors, draft vs active, walled vs single), and readers outside the asserted scope.
Findings that pass the filing gate are extracted as cards and listed in the close-out comment. One more:
- approvals.per-group-signoff · observed beside the clauses · security finding — detail withheld, reported to PM
Observations not filed, judged below the filing gate or by design:
POST /api/v1/actions/sys_approval_request/approval_approvewith recordId missing → 404 ENDPOINT_NOT_FOUND rather than 400.POST …/requests/areq_unknown/approve→ 404 whose error text is the bare id, while the GET says "Approval request 'areq_unknown' not found".- every POST/PUT/DELETE on /automation logs "[Registry] Overwriting flow" once per org-scope flow.
GET …/history?limit=0→ 200 empty;/diff?from=abc→ 400 VALIDATION_FAILED while/rollback {"toVersion":"abc"}→ 400 INVALID_REQUEST;/historyand/auditon an unknown name → 200 empty while/diff→ 404.- the ITEM_LOCKED remedy sentence still names the clone route while NOT_OVERRIDABLE names the pair; both are true remedies.
- RLS "Add tab" autosaves an empty-key row
tabPermissions: {"": "visible"}, which save and publish accept. /_console/studio/PKG/access?surface=permission:does_not_existopens the first set with no notice.- duplicate widget ids on a dashboard are accepted; the runtime draws both tiles correctly.
- a form view for a nonexistent object saves and publishes 200 and renders nowhere.
- a summary rolling up a nonexistent child object saves valid and stays null (dangling references are tolerated as for lookups).
- an app whose navigation holds only doc entries opens on a blank main region.
field.referenceViain the header highlight strip shows the raw id text; the details-grid placement renders the card.- a Studio RLS CHECK on a select policy reads "Valid CEL" beside a 422 that names it: this is Studio RLS editor: a CHECK on a select policy (or an empty USING) reads "Valid CEL", then the autosave is refused 422 and the banner shows only the path code, not the server's explanation #28664, fixed by PR fix(app-shell): the Studio RLS editor stops offering CHECK on a select or delete policy, and a refused save shows the server's explanation #28884, which merged at 03:29Z, after this subject.
Activity
objectstack-fleet commentedon Nov 2, 2026
domain:devx#1 · os-warren · session_01JQwxbX9avDwM2goWd7zimE
Per-clause verdicts, part 1 of 3 (text oracle evidence)
Continuation of this record. One line per clause, A then N, in the scope order of the record body. Runner tag in the record fingerprint. Double braces inside quoted templates are spaced apart so the write door does not read them as tokens.
approvals.per-group-signoff r11 — PASS
approvals.quorum-m-of-n r9 — PASS
Verdict rests on the item's RUNTIME FIXTURE (fixtures.requires[1]), applied on this fresh boot: POST /api/v1/data/sys_user_position {user_id:usr_showcase_auditor_demo, position:finance, organization_id:org_mvyq5oga23juzfjh} → 201; same for qa.legal (KIHGwGl3…) position legal → 201 (pre-list: finance/legal/manager held only by the admin, nothing to unbind); POST /api/v1/automation/showcase_expense_signoff/toggle {enabled:false} → 200 (re-enabled {enabled:true} → 200 after the quorum steps); fresh draft reports QA-R1A-Q1 / QA-R1A-Q2 + one 6000 showcase_expense_line each (total_amount 6000) → PATCH status submitted → showcase_committee_quorum requests areq_68f7fea9… / areq_e58d1503…, slate [admin, Ada, admin, qa.legal, admin] = 3 distinct users. The admin is submitter and third holder (as the fixture says) and never decided either fresh request.
approvals.inbox-metadata-actions r8 — PASS
Personas proven by GET /api/v1/auth/get-session: admin@objectos.ai (hVpCVmor…, org_mvyrb68z…), auditor.demo@example.com (usr_showcase_auditor_demo, same org). Entry path: form sign-in → /_console/apps/com.objectstack.account/component/approvals/inbox → tab → click the Request cell (cell 1 on My Pending after the checkbox; cell 0 on Submitted by me / All) → served page /_console/apps/com.objectstack.account/sys_approval_request/record/‹id›; screenshot confirmed render before every DOM read.
visibleadmits can_act (sys-approval-request.object.ts); "…" menu holds only Refresh/Add to favorites/Share.POST /api/v1/actions/sys_approval_request/approval_approve {"recordId":"areq_72d3…","params":{"comment":…} }→ 200{"success":true,"data":{"request":{…} }}, forwarded in-process to the declared target POST /api/v1/approvals/requests/{id}/approve (runtime action-execution.ts forwardApiAction); re-read → status approved, viewer can_act false, page re-renders with no decision buttons. Also admin UI approve of EXP-DEMO → re-read status approved. Send reminder click (admin, INV-1006) →POST /api/v1/actions/sys_approval_request/approval_remind→ 200 (forwarded to …/requests/{id}/remind); re-read status=pending. See AF-2 on the verify's literal path.approve hVpCVmor… via_override=false "R1b UI spot-check Approve"; INV-1006 [submit(admin)] → +1remind hVpCVmor… "R1b UI spot-check Send reminder"; EXP-2001 2 rows → +1approve usr_showcase_auditor_demo. Exactly one row per action, actor named.approvals.viewer-gating-submitter-side r7 — PASS
approvals.decision-action-matrix r10 — PASS
Boot B2 (A0–A8, N0–N2) and B3 (A9). Personas proven by get-session on B2: admin@objectos.ai (sPoyRDLE…), auditor.demo (usr_showcase_auditor_demo), phone.demo (usr_showcase_phone_demo), all org_mvyrl3hg…. Budget requests raised by PATCH showcase_project budget (admin = submitter AND manager/exec slate holder).
POST …/areq_b844…(EXP-DEMO)/approve→200, actions [submit]→[submit, approve:admin]; reject (Mobile App 210000) →200, +reject:admin; revise →200, +revise:admin; resubmit →200, +resubmit:admin; recall (Legacy Sunset 300000) →200, +recall:admin; reassign →200, +reassign:admin; remind (INV-1010) →200, +remind:admin; request-info as Ada (reassign target) →200, +request_info:usr_showcase_auditor_demo; comment as admin →200, +comment:admin. Third revise (maxRevisions) → revise + reject("Auto-rejected: revision limit (2) exceeded"), the designed pair.pausedat exec_review before the exec approve andcompletedafter.wait_revision.GET /approvals/requests?status=pending&approverId=‹admin›(the My Pending query) contains R before = true, after = false.PATCH /api/v1/data/showcase_project/4brU… {name}→ 200; after manager approve, at exec_review (lockRecord true): same PATCH → 409 RECORD_LOCKED "…is locked while an approval is in progress…", name unchanged.POST /api/v1/approvals/requests/R/approve {comment}→ 500{"code":"READ_BACK_FAILED","error":"the write to approval request '…' was recorded, but the updated row is not visible inside the caller's organization scope… The write is NOT rolled back — read the request back with a system or matching-organization context."}; set-active B → GET R 200 status=approved, pending_approvers []; actions [submit, comment, approve(admin, via_override=false)]. Admitted as R's pending approver (knownGaps note), not via the override arm.GET /api/v1/approvals/requests/areq_does-not-exist/actions→ 404 REQUEST_NOT_FOUND (also for ' ' and for a real request the caller cannot read).automation.flow-run-step-nesting r8 — PASS
{parentNodeId:loop_tasks, regionKind:loop-body, iteration 0/1/2}, each followed by send_reminder{parentNodeId:guard_reminder, regionKind:try, iteration}matching its guard (0/1/2); no end step logged.{parentNodeId:each_task, regionKind:loop-body, iteration:0|1}with nobranchkey. All 8 steps (and runs run_2e7334be, run_a185ed06, run_ef916949) parse under ExecutionStepLogSchema from packages/spec/dist (0 parse failures)./_console/studio/com.example.showcase/automations?surface=flow:showcase_batch_reminders› Runs tab (FlowRunsPanel), expanded run_2e7334be; screenshot shows "For each task LOOP" with indented "↳ ITERATION 1 / 2 / 3" headers, under each "Guarded iteration TRY_CATCH", under that "↳ TRY" and "Send Reminder NOTIFY" indented a further level — a nested tree, 1-based labels.start:success | loop_tasks:loop:success, 0 steps with parentNodeId=loop_tasks; the panel shows the same two rows.automation.rollup-summary-filter r7 — PASS
automation.flow-node-type-matrix r12 — PASS
GET /api/v1/automation/actions→ 200, 20 descriptorsdecision,assignment,loop,parallel,try_catch,get_record,create_record,update_record,delete_record,screen,script,http,connector_action,notify,save_notice,wait,subflow,map,approval_revise,approval— every variant but start/end, none extra. Palette: clonedshowcase_task_completed→qa_r2a_palette(POST /automation/…/clone {name,label} → 200; withoutlabel→ 400 VALIDATION_FAILED as steps[1] says), opened in Studio, clicked "Add node" after the canvas screenshot confirmed render; the popover lists Create Record, Update Records, Get Records, Delete Records, Decision, Loop, Assignment, Parallel, Try / Catch, Map, Approval, Screen, HTTP, Notify, Connector Action, Script, Subflow, Wait, Save notice, Revise Window (= the 20) plus End (the base palette's structural item).t=…,v1=…→ 202) create_task success, task XSSyp8gTJetZ7Nrm created · update_record run_c2bbf836 (showcase_reassign_wizard) apply success · delete_record run_0c8cc981 purge success (closed inquiries gone: remaining statusesnew,contacted) · get_record run_0c8cc981 purge_check success · http run_1a44faeb (scratch self-pingqa_r2a_http_ping, GET http://127.0.0.1:41330/api/v1/health) ping success · notify run_80071a07 notify success · save_notice run_80071a07 tell_saver success, and the PATCH answer carriednotice: "Marked R2a chain task … done; its assignee is being notified"· script run_80071a07 summarize success · screen run_c2bbf836 collect success · wait run_327dcbfe hold success · subflow run_2037c80d (showcase_task_done_notify_owner) call_notify success, child showcase_notify_owner run_79dd0f3c completed · map run_ca21f358 signoffs success · connector_action run_5e0e7744 / run_86708114 / run_a70be9a4 ping/ping/echo success · parallel run_7ca0abcd (showcase_nested_fan_out_reminders) fan_out_audiences success ×2 and run_bb27dbae (showcase_project_escalation) alert success · try_catch run_ef916949 guarded_push success · approval run_6c88cfc5 (seeded EXP-2001) dual_signoff success · approval_revise run_3872c091 wait_revision success. end: run_80071a07 status completed with no end step logged (taken end logs none); theend:skippedrow of run_0c8cc981 is not cited for end. showcase_fan_out_notify run_1d861105 failed only at slack_posthttp: fetch failed(no egress, knownGaps — parallel and http proven on the runs above instead).collect:screen) → POST …/runs/:id/resume {inputs:{new_assignee} } 200 → completed, task 5E0h8xnfNJhS69th assignee rewritten (the browser leg, Tasks row "Open menu" › "Reassign…" › Submit, also completed run_4eb88166 and wrote assignee auditor.demo@example.com) · wait run_327dcbfe paused at hold → completed 60.04 s later (startedAt 04:41:27.435, completedAt 04:42:27.475) · approval run_6c88cfc5 paused → admin approve + auditor.demo approve (both 200) → completed with mark_approved · approval_revise run_3872c091 paused at manager_review → revise 200 → paused at wait_revision → resubmit 200 → paused at manager_review (round 2) → approve 200 → completed · map run_ca21f358 paused → approve item 1 → still paused → approve item 2 → completed with notify_done · subflow (child pauses) showcase_project_closure run_993231fa paused at signoff with child run_e3eef03b paused at ask_signoff → approve → both completed.{parentNodeId:loop_tasks, regionKind:loop-body, iteration:0..2}; parallel run_7ca0abcd notify_owner{parentNodeId:fan_out_audiences, regionKind:parallel-branch, iteration:0, branch:0}; try_catch run_ef916949 push{parentNodeId:guarded_push, regionKind:try, retryAttempt:0}and record_failure{regionKind:catch}.qa_r2a_bogus(start → bogus_node → end) → 200, read-backGET /api/v1/meta/flow/qa_r2a_bogusnode types["start","bogus_node","end"]; (2) boot.logWARN Flow 'qa_r2a_bogus' references node type(s) with no registered executor or descriptor — … {"unknownTypes":["bogus_node"],"knownTypes":[start,end,decision,assignment,loop,parallel,try_catch,get_record,create_record,update_record,delete_record,screen,script,http,connector_action,notify,save_notice,wait,subflow,map,approval_revise,approval]}(22 = the 20 descriptors + start/end; lists in meta, not message); (3) POST …/qa_r2a_bogus/trigger → 400{"success":false,"error":{"code":"FLOW_FAILED","message":"No executor registered for node type 'bogus_node'"} }; (4) run run_4dd80a64 status failed, step weird{status:failure, error:{code:NO_EXECUTOR} }.automation.flow-error-handling r6 — PASS
{parentNodeId:guarded_push, regionKind:try, status:failure, error:{code:NODE_FAILURE, message:"http: fetch failed"} }; index 6 = record_failure (update_record){regionKind:catch, status:success}— every try failure precedes the catch step.sync_status:"failed",sync_error:"Node 'push' failed: http: fetch failed".qa_r2a_unhandled(POST /api/v1/automation → 200, package-unbound/~org; start record-after-create on showcase_inquiry → http POST https://api.example.com/v1/tasks → end, no try_catch). Trigger-fired run run_f8d44fc6 (trigger record-after-create, fired by creating inquiry Y-le8oRPOQJIWIpL) status failed, error"Node 'push' failed: http: fetch failed"; the api-triggered run run_21805154 also failed with the same error (trigger answered 400 FLOW_FAILED).2026-11-02T04:47:02.464Z ERROR Trigger-fired run of flow 'qa_r2a_unhandled' failed (trigger 'record_change') — no caller holds this result and nothing retries the run; … {"error":"Node 'push' failed: http: fetch failed"}; the api-triggered failure 3 s later logged no ERROR line (correct per the clause).try_catchtry-region produces no step at all — after a caught failure an operator cannot tell what failed, how many attempts ran, or which node threw #7546).automation.packaged-flow-clone-contract r10 — FAIL
data.flowdiffs from the source on exactly name, label, status (+ the source's_provenance/_packageId/_packageVersionabsent) and carries noclonedFrom; but status isobsolete, not thedraftthe verify pins, and the stored read GET /api/v1/automation/qa_urgent_alert_clone carries_packageId:"com.example.showcase.custom"(sys_metadata.package_id = com.example.showcase.custom) — the clause's "none of the 10 dropped keys present … a surviving _packageId defeats the feature (package-owned)" is the pre-Flow clone door → "new similar": the copy lands in a writable target package, by default the paired <base>.custom, never package-less #28149 package-less model; the copy is now package-owned BY RULING (lands in‹base›.custom). Facets all present (description, version, type, nodes, edges, runAs deep-equal). See F-1.status:"obsolete"; across 3 urgent showcase_task creates with the clone as cloned, base runs 0→1→2→3 while clone runs stayed 0 (no double-fire);_statusrowqa_urgent_alert_clone enabled:false bound:false status:obsolete. The notice equals the CURRENT FLOW_CLONE_NOTICE verbatim ("The copy is switched off and does not run until you switch it on, and it still uses the same subflows, actions and objects as the original.") — but it no longer warns of a double-fire / carries a toggle instruction (The flow clone notice is a paragraph of engine prose; say it in one line (#27356 row 18) #27886). See F-1.manage_metadatacapability."; neither left a flow (GET → 404).dashboards.strict-widget-rejects-stray-keys r3 — PASS
PUT /api/v1/meta/dashboard/qa_stray_key_probe?mode=draftwith a valid dataset widget + each of the 11 legacy keys (object, categoryField, categoryGranularity, valueField, aggregate, aggregation, rowField, columnField, xAxisField, yAxisFields, measures) → 422 INVALID_METADATA pathwidgets.0code unrecognized_keys, message "Unrecognized key(s) on this dashboard widget:‹key›." naming the literal key in all 11.object+categoryField+valueField+aggregate, pivotrowField/columnField) was removed — bind adatasetand selectdimensions+valuesby name."chartFlavour→ 422 "Unrecognized key(s) on this dashboard widget:chartFlavour." with no did-you-mean (no declared key is close — absent honestly); suggester proven live on near keys:chartConfg→ "Did you meanchartConfg→chartConfig?",dimension→ "Did you meandimension→dimensions?".widgets.0.chartConfig"datasetis not part of the chart config — it is the dashboard widget's own key (ADR-0021), a sibling ofchartConfig"; chartConfig.drillDown → "it is a REACT-TIER prop …‹ObjectChart … drillDown={ {…} } /›… On a REPORT the switch isdrilldown"; aggregate and objectName likewise rejected with their own guidance (objectName: "dataseton a dashboard widget (ADR-0021), or theobjectNamePROP on a react‹ObjectChart›").GET /api/v1/meta/dashboard/qa_stray_key_probe→ 404 RESOURCE_NOT_FOUND and?state=draft→ 404 NO_DRAFT; after a clean draft was saved+published (seq 2), a categoryField draft PUT → 422 and the GET still returns the clean revision (0 occurrences of categoryField),?state=draft→ 404 NO_DRAFT; a non-draft PUT withmeasuresalso → 422./_console/apps/showcase_app/dashboard/qa_stray_key_probescreenshot shows "Tasks by Status" bar chart, 5 bars (Backlog/To Do/In Progress/In Review/Done, 2 each) in a 590×350 recharts SVG; its dataset query → 200 with 5 rows; 0 role=alert.dashboards.chart-type-matrix r7 — PASS
Ground truth (POST /api/v1/analytics/dataset/query, clock 2026-11-02T04:55Z UTC): status 5 buckets (2 each), priority 4 (Low 1/Medium 4/High 3/Urgent 2), created_at month buckets 3 (2026-09:1, 2026-10:8, 2026-11:1 — this boot date crosses two month boundaries), project accounts 3, status×priority 8 non-empty cells, progress 6 buckets, enum = 20 members (chart.zod.ts read, matches variants).
/_console/apps/showcase_app/dashboard/showcase_chart_gallery(18 widgets, 0 role=alert) + scratch qa_single_value_gallery: bar 371×350 SVG 5 rect bars; column 4 bars; horizontal-bar 5 horizontal bars; line/area 590×350 curve over Sep/Oct/Nov 2026; pie 5 sectors; donut 4 sectors (ring); funnel 5 trapezoids; scatter 6 symbols; treemap 5 depth-1 rects; sankey 5 links/6 nodes; combo 3 bars + 1 line; radar 1 polygon over 4 angle ticks; table real ‹table› 3 rows × 4 cols + Total; pivot ‹table› 5 status rows × 4 priority cols + totals; metric "10 Tasks" / "47.0" / "$616,000.00"; kpi/gauge/solid-gauge/bullet each "10 Tasks" on the scratch board.MCC= 3 points = 3 month buckets, area pathMCC= 3 = 3 (x ticks Sep/Oct/Nov 2026).recharts-bar-rectangle(task_count, left axis 0–8) + 1recharts-line-curve(avg_progress, right axis 0–60), 2recharts-yAxis; legend "Avg Progress | Tasks"; screenshot shows bars with a green line crossing on dual axes.examples/app-showcase/test/coverage.test.tsrun at 7fa9873 in TREE (pnpm exec vitest run --maxWorkers=1) → "Test Files 1 passed, Tests 13 passed" (declaration layer only; render checks above are the browser half).packages/spec/src/ui/dashboard-compareto.test.tsat 7fa9873 → "Test Files 1 passed, Tests 17 passed".recharts-sankey-link= the 8 non-empty status×priority cells, 9 nodes (5 statuses + Low/Medium/High/Urgent), no omitted-rows note, no chart error; screenshot shows every status flowing into its priorities (not only the first category's flows).dashboards.global-filters-rescope r5 — PASS
/_console/apps/showcase_app/dashboard/showcase_revenue_pulse) initial 10 dataset queries; region → EMEA re-issued 9POST /api/v1/analytics/dataset/query(200), the 6 invoice widgets with runtimeFilter{"$and":[{"issued_on":{…} },{"region":"emea"}]}, the 3 bound account widgets with{"$and":[{"signed_on":{…} },{"sales_region":"emea"}]}; tiles moved (Invoices 7→1, Accounts Signed 3→1).issued_on {"$gte":"2026-09-01","$lte":"2026-10-31"}on invoice widgets andsigned_on {…same bounds}on account widgets (bar reads "2026-09-01 – 2026-10-31 | EMEA | Reset").{"dimensions":[],"measures":["account_count"]}, no runtimeFilter) appears only in the initial load; 0 such queries after region, custom window or Reset; tile reads "14" in all four phases.GET /api/v1/data/showcase_accountcounted independently: sales_region=emea and signed_on in 2026-08-04..2026-11-02 = 1 (Wonka Brands 2026-09-03); Invoices tile 1 = data API emea invoices in window 1; custom window EMEA invoices 0 = tile 0.account_count 11(= 14 − 3 prospects) and last-90-days → 3 (data API count of non-null signed_on in window = 3).{"$and":[{"status":"in_review"},{"$and":[{created_at window},{"status":"in_review"}]}]}→ tile 2 = API aggregate (status in_review ∧ in_review) 2; → Done: tile 0 = API (in_review ∧ done) 0; At-Risk (Red) stays 1 (no project query re-issued; API health=red count 1), Active Projects 2 and Total Budget $1,090,000.00 unchanged./_console/studio/com.qa.r3/interfacesthe Hours Total widget's Filter Bindings row "Region" field combo set tostatus, and Ticket Count's Region "Apply" unchecked → twoPUT /api/v1/meta/dashboard/r3_rescope?mode=draft&package=com.qa.r3&surface=studio200 carryingfilterBindings {"region":"status"}/{"region":false}; Publish all →POST /api/v1/packages/com.qa.r3/publish-drafts200 publishedCount 1;GET /api/v1/meta/dashboard/r3_rescope→ rs_count{'region': False}, rs_hours{'region': 'status'}; reload + Region=North → rs_hours query carries{"status":"north"}, rs_status{"region":"north"}, rs_count issues no query."region":"emea"; scratchacctfilter (optionsFrom {object: showcase_account, valueField: id, labelField: name}) issuesPOST /api/v1/analytics/dataset/querywith an inline dataset over showcase_account, dimensions [id, name],limit 1000(server GROUP BY) → 14 options = all 14 accounts; picking one shows the label "Vandelay Industries" while the query commits{"account":"14hF3lvOO0IbhYRn"}.limit 1000).PUT /api/v1/meta/dashboard/qa_fb_probe2withfilterBindings {"dateRange":"issued_on"}on a dashboard with no filters → 422 ruledashboard-filter-binding-unknown"declares no filters, so the binding is never read" (GET → 404); a draft with keyregoinsaves 200 (draft door) with_diagnostics.valid:falsecode dashboard-filter-binding-unknown, and its publish → 422 with hint 'Did you mean "region"?'.records-forms.form-view-gallery r6 — PASS
/record/6qa9ihKO_XZ-NkQF/edit(RecordFormPage) shows section "Task", 2-col grid (grid-cols-1 @md:grid-cols-2), labels in order Title*, Project*, Assignee, Status*, Priority, Due Date; tabbed overlay (PUT ?mode=draft 200 seq=1 + publish 200 seq=2, isDefault:true) → page form showsrole=tab["Overview","Schedule","Details"] with Overview=[Title,Project,Assignee,Status], Schedule=[Start Date,End Date,Due Date,Progress], Details=[Estimate (h),Labels,Work Location,Notes]; wizard overlay (seq=3/4) → /new and /edit show step chrome "1 Basics · 2 Assignment · 3 Schedule", "Step 1 of 3", Next/Back; task-desk (nav "Task Desk · drawer & modal" → /page/showcase_task_desk): row click opensrole=dialog"Edit task" at x=1016 w=384 h=900 of a 1400 viewport (right side panel over the list), "+ New task" opensrole=dialog"New task" centred x=188 w=1024 (dialog). One screenshot per variant taken in RUN/shots.wizard-step:step_assign/step_scheduledisabled while on step 1; Next with title but no project stays "Step 1 of 3" with "Project is required"; 0 data writes through steps 1→2→back→2→3; final Save → exactly onePOST /api/v1/data/showcase_task {"status":"backlog","priority":"low",…,"title":"R4b wizard task A","project":"Y84_4rM_pGWCrmrH","assignee":"r4b-assignee@example.com"}→ 201 id Vqa-VkF47wZ7HG0q. On /edit: 0 writes across steps, onePATCH {"priority":"medium"}→ 200 at final Save.typeenum in @objectstack/spec 18.3.0 (ADR-0050) — … Writetype: 'tabbed'… declaresubforms…", "'drawer' was removed … a list opens a clicked record in the drawer (navigation.mode…)", "'modal' was removed … the console's New and Edit dialogs open the record form themselves (sized by the form'smodalSize)…". Refused at the DRAFT PUT; the following publish answered 404 NO_DRAFT each time. GET showcase_task.form before vs after the three: byte-identical (type wizard overlay still served).objectstack-fleet commentedon Nov 2, 2026
domain:devx#1 · os-warren · session_01JQwxbX9avDwM2goWd7zimE
Per-clause verdicts, part 2 of 3 (text oracle evidence)
Continuation of this record. One line per clause, A then N, in the scope order of the record body. Runner tag in the record fingerprint. Double braces inside quoted templates are spaced apart so the write door does not read them as tokens.
records-forms.action-location-matrix r18 — PASS
Fixture: qaact-placement-specimen installed via POST /api/v1/marketplace/install-local → 200 {manifestId:'com.objectstack.qaact.specimen', hotLoaded:true}; GET /meta/object/showcase_task → 13 actions, last three qaact_control [list_item,record_header], qaact_visible_throws (visible cel 'qaact_undeclared_root.flag == true'), qaact_no_locations (NO locations key); ledger landed in RUN/app/.objectstack/installed-packages (TREE has none). Placement matrix (admin, 1440x900, each surface screenshot-confirmed before the DOM read):
records-forms.calendar-interactions r4 — PASS
Surface: /_console/apps/com.example.showcase/showcase_task/view/calendar (view switcher "Calendar", month grid November 2026, footer "9 records loaded for these dates"; screenshot-confirmed before every DOM read). Cards are [role=button][aria-label=title] inside [role=gridcell][aria-label="‹weekday›, ‹Month› ‹d›, 2026, …"]; drags are ref-targeted Playwright dragTo (HTML5 DnD) card → target gridcell.
records-forms.field-group-visible-when r11 — PASS
visibleWhen: "record.kind == 'pro'"→ 200 seq=6, GET reads fieldGroups[0].visibleWhen = "record.kind == 'pro'" (bare string, verbatim), highlightFields ['kind']; PUT envelope {dialect:'cel',source:"record.kind == 'pro'"} → 200 seq=7, GET reads the envelope verbatim; batch20 pin "accepts the bare-string CEL shorthand and normalizes it to the envelope" + "accepts the full Expression envelope…" green (vitest, packages/spec, 70/70 across the two files, at 7fa9873); render identical for both spellings (A3 run once per spelling, same results). Provisioned withsharingModel: 'public_read_write'added — see accuracy findings.visibleOnis not a field-group key — the section predicate slot isvisibleWhen… WritevisibleWhen: ‹CEL›— FALSE hides the whole group, header included."; GET after reads the prior envelope unchanged.{ group }refs per record. Live, after a PATCH pre-filling pro_tier "Gold-R4b"/pro_seats 7: FORM (/record/7bjI5bP0C8Dx_Ajj/edit) kind=Basic → "Pro details" header count 0, labels [Name, Kind]; select Kind=Pro (unsaved) → header 1, labels [Pro tier, Pro seats, Name, Kind]; back to Basic → 0. DETAIL kind=basic → header 0; after a saved flip (form Save, PATCH {"kind":"pro"}) → "Pro details | PRO TIER | Gold-R4b | PRO SEATS | 7"; after saved flip to basic → header 0. Same four observations with the envelope and with the bare string. Pin record-details.groupVisibleWhen-11630.test.tsx: 13/13 passed (cd ui && pnpm exec vitest run --maxWorkers=1 …).name; re-read values intact). Boundary recorded: visibility is display-tier (drawing only).int(record.kind) > 0(accepted 200 at the door) → detail and form both render (Name/Kind present, no error boundary, no page error) with the section HIDDEN for kind=pro and kind=basic → fail-CLOSED on both pages, with a named console warning "[object-ui] A conditional predicate failed to evaluate (visibleWhen of group 'pro_details' of record:details … — fail-closed, the group is hidden) … Reason: [runtime] found no matching overload for 'int(…'". Unknown-field predicaterecord.nonexistent_field == 'x'is refused at the door (422 expression-invalid "unknown fieldnonexistent_field"), so it never reaches render. Direction recorded: fail-closed, matching the spec docblock — no drift left to file (see accuracy findings).studio-authoring.view-authoring-live r5 — PASS
Persona note: no permission set grants a non-admin access to the runtime-authored object, so the end-user surfaces were scored as the admin (as the item's personas[1] permits). Surface:
/_console/apps/showcase_app/qfive_asset(redirects to/apps/com.example.showcase/qfive_asset).PUT /api/v1/meta/view/qfive_asset_views?mode=draft&package=com.qa.rfive{object:qfive_asset, list:{grid, columns:[serial,name,status]}, form:{sections:[QA Identity Block(serial,name), QA Money Block(cost)]} } → 200{"success":true,"version":"hmac-sha256:661260bc…","seq":3,"state":"draft"}.["Name","Serial","Status","Cost","Notes","Actions"](same as before the draft);GET /api/v1/meta/view/qfive_asset_views→ 404 RESOURCE_NOT_FOUND andGET /api/v1/meta/view?object=qfive_asset→ 200items:[]— no draft leakage.POST …/qfive_asset_views/publish→ 200 seq 4; client reload only: list headers["Serial","Name","Status","Actions"]; New dialog "Create QF Asset … QA Identity Block / Serial / Name / QA Money Block / Cost" (screenshot: two titled sections, Serial+Name side by side); row menu Edit dialog "Edit QF Asset … QA Identity Block … QA Money Block / Cost"; boot.log has exactly one✓ Server is readyand no restart/recompile line across draft→publish→render (03:55:26–03:56:27Z), same listener throughout.GET /api/v1/meta/view?object=qfive_asset→ 200 itemsqfive_asset.default(viewKind list, config.columns ["serial","name","status"]) andqfive_asset.form(viewKind form, sections "QA Identity Block"/"QA Money Block"),_packageId com.qa.rfive.["Serial","Name","Status","Actions"]before re-publish; publish → 200 seq 6; reload → headers["Status","Name","Cost","Serial","Actions"](screenshot confirms reordered + added Cost column).Server is readyin boot.log).studio-authoring.draft-publish-lifecycle r10 — FAIL
All forensics consulted on the live
os devREST server (port 41380), never a simulated dispatch. Dashboard drafts staged with?package=com.qa.rfive(writable).PUT /api/v1/meta/dashboard/qa_lifecycle_probe?mode=draft&package=com.qa.rfive(bar widgetby_status, dataset showcase_task_metrics, dimensions [status], values [task_count], title "REV1 Tasks by Status") → 200{"success":true,"version":"hmac-sha256:29133d59…","seq":7,"state":"draft"}; runtimeGET /api/v1/meta/dashboard/qa_lifecycle_probe→ 404 RESOURCE_NOT_FOUND.POST …/qa_lifecycle_probe/publish→ 200 "Published draft … [seq=9]"; GET → 200{type:"dashboard",name:"qa_lifecycle_probe",item:{…widgets:[{id:"by_status",…title:"REV1 Tasks by Status"}]} }."title":"REV1 Tasks by Status"; publish → 200 seq 11; GET"title":"REV2 Tasks by Status".If-Match: hmac-sha256:29133d59…→ 409{"code":"METADATA_CONFLICT","currentVersion":"hmac-sha256:85733b71…"}; the draft still reads "REV2", not the stale write.r5badobj(no namespace prefix) →POST /api/v1/packages/com.qa.rfive/publish-draftsHTTP 200, bodydata.success:false, outcome:"refused", publishedCount:0, failed:[{name:"r5badobj",code:"NAMESPACE_PREFIX"}]; qfive_pd_one, qfive_pd_two and the pending app qfive_app all 404 afterwards. Bad draft discarded (DELETE …/object/r5badobj?state=draft&package=…200), re-POST → 200data.success:true, outcome:"published", publishedCount:3(app + both dashboards, one commitId); both dashboards then serve ("PD one count"/"PD two count"). (N4 shape observed, not scored.)objectkey → 422 INVALID_METADATA "widgets.0 [unrecognized_keys] … Unrecognized key(s) on this dashboard widget:object"; publish then → 404 NO_DRAFT; live GET still "REV2 Tasks by Status"./_console/studio/com.qa.rfive/interfaces→ nav entry "QA Dash" (app draft qfive_app staged in the package naming the dashboard): header chip "Draft · 2 unpublished", Changes button "Changes · 2", canvas "Design · includes unpublished changes" (screenshot). After the REST per-ref publish + reload: no Draft line, "Changes · 1" (the app). After publish-drafts + reload: no Draft line, button reads "Changes" with no pill (screenshot).GET …/history→ 200 events v1 create seq7, v2 publish seq9 ("publish draft"), v3 create seq10, v4 publish seq11, ascending, actor = admin id on each.GET …/diff?from=2&to=4→ 200added:[],removed:[],changed:[{path:"widgets",from:[…"REV1 Tasks by Status"…],to:[…"REV2 Tasks by Status"…]}]—label/namenot listed.POST …/rollback {"toVersion":1}→ 200 "Reverted to version 1 … restoredFromVersion:1"; GET → "REV1 Tasks by Status";/_console/apps/qfive_app/dashboard/qa_lifecycle_probescreenshot shows the widget "REV1 Tasks by Status". Bad toVersion:{},"abc",0,-1→ each 400{"code":"INVALID_REQUEST","error":"'toVersion' (positive integer) is required"};999→ 404 VERSION_NOT_FOUND.GET …/auditrows for save draft (allowed), both publishes (allowed), two 409s (denied, code metadata_conflict) and both rollbacks (allowed) each carry actorKiZpgpkw…(the admin), the NO_DRAFT 404 wrote no row; but the clause also pins "a 422 INVALID_METADATA spec refusal … write[s] no row", and the trail now carries{"operation":"save","outcome":"denied","code":"invalid_metadata","actor":"KiZpgpkw…"}for each 422 (2 of 2). See F-1.{"toVersion":4}sent withX-Actor: someone_else_entirely→ 200; audit row (rollback, "restored from version 4") and history row (seq 19, op revert, "revert to version 4") both actorKiZpgpkw…;someone_else_entirelyoccurs 0 times in /audit and /history.studio-authoring.authoring-validation-not-persisted r5 — PASS
PUT /api/v1/meta/object/qa_invalid_probe?mode=draft(fieldamountwith label only) → 422{"error":"object/qa_invalid_probe failed spec validation: 1 issue — fields.amount.type [invalid_value]","code":"INVALID_METADATA","issues":[{"path":"fields.amount.type",…}]}.qfive_bare,GET /meta/view?object=qfive_bare→ items []):PUT /api/v1/meta/view/qa_invalid_views?mode=draft{object, type:"grid", columns:[…], list:{…} } → 422 INVALID_METADATA, issue "Unrecognized key(s) on this view container:type,columns. •typebelongs to a single VIEW, not to the container. Wrap it:defineView({ list: { type, data, columns, … } })… The container's own keys arelist,form,listViews,formViews."GET …/qa_invalid_probe→ 404 RESOURCE_NOT_FOUND and?state=draft→ 404 NO_DRAFT; viewGET …/qa_invalid_views→ 404,?state=draft→ 404 NO_DRAFT,GET /meta/view?object=qfive_bare→ items []; after the browser 422 below,qfive_bare?state=draft→ 404 NO_DRAFT and the live field stays"code":{"type":"text","label":"Code"}./_console/apps/setup/metadata/object/qfive_bare(redirects to/_console/studio/~all/metadata/object/qfive_bare) → Source tab, body withcodemissingtypepasted into the Monaco editor (clipboard paste, ref-targeted) → autosavePUT /api/v1/meta/object/qfive_bare?mode=draft→ 422; screenshot shows two red banners: "Fields → code → type: Invalid option: expected one of …" and "This metadata does not match the spec — 1 validation error(s). Fields → code → type: …", plus an "Unsaved" chip.qa_invalid_probe(amount type number) → 200state:"draft"seq 22; correctedqa_invalid_views{object:qfive_bare, list:{type:grid, columns:[name,code]} } → 200state:"draft"seq 23._diagnosticsbanner was met on these saves and none was scored.studio-authoring.packaged-automation-studio-lock r6 — FAIL
pnpm exec vitest run --root . --maxWorkers=1 …automationsReadOnly-11124.test.tsx …ledgerSwitch-28148.test.tsx): 2 files, 18 passed — component layer (jsdom); the 11124 pin answers_status404, i.e. the no-ledger-row fallback that a live boot never takes, so no clause is scored from it.[data-testid=studio-rail-add]aria-label "New automation" (count 1, both loads), which opens the "New automation" dialog (Flow name / When it runs / Save as draft); per ui/…/StudioDesignSurface.tsx (AutomationsPillaropenCreateFlow=(!packageReadOnly || newInPair), create targetcustomization.ensurePackage()) it saves into com.example.showcase.custom — the clause's "New-flow button absent" pins the pre-ADR-0146 shape. See F-2.data-testid=auto-ledger-switch, aria-checked true, NOT disabled, text "On for this deployment", described "This flow ships in a read-only package. The switch turns it on or off for this deployment only; the package is not changed."; a click fired POST /api/v1/automation/showcase_urgent_task_alert/toggle {"enabled":false} → 200 and read "Off for this deployment" (restored with a second click → {"enabled":true} 200), reproduced on 2 fresh loads; the write lands in sys_metadata_activation (row flow/showcase_urgent_task_alert package com.example.showcase active 1), the flow definition md5 unchanged. The clause's PASS shape ("disabled … no request fires"; "a switch that fires a request is now a FAIL") is the pre-Studio: switch a read-only package's flow off for this deployment from the Automations pillar — ledger switch on the item, posture-aware, refusal verbatim #28148 definition switch. See F-2.disabled; a fill on the first text input timed out (not editable); after a page reload and re-select every control still disabled with the original values — inert, nothing taken, nothing vanished (2 fresh loads).studio-authoring.doc-on-app-menu r9 — PASS
Baseline (step 1): GET /meta/book → [showcase_manual (public)], /meta/doc → 9 docs, /meta/app?id=showcase_app → 0 type:"doc" entries (knownGaps[0] holds). Fixture, all in com.qa.r6doc over the metadata API with ?package= then publish-drafts: docs qsixd_gated_doc / qsixd_open_doc / qsixd_open_doc2 authored and published BEFORE the books; books qsixd_a_empty (label "AAA Empty Book", claims none — first by label), qsixd_gated (audience {permissionSet: showcase_auditor}, claims qsixd_gated_doc), qsixd_org (audience org, claims both open docs); app qsixd_app nav {doc}, {book}, {book,doc} over the gated and open corpus. Reader r6reader@example.com holds showcase_auditor via a sys_user_permission_set row (201); outsider r6outsider@example.com holds none; both admin create-user mustChangePassword:false, identities proved with get-session before each cell; each in its own browser context.
Teardown (step 8): DELETE app, 3 books, 3 docs ?package=com.qa.r6doc → 200 each; the set assignment row deleted 200; corpus re-reads as baseline (books [showcase_manual], 9 docs, 0 doc nav entries); reader's GET /meta/doc/qsixd_gated_doc → 404.
studio-authoring.held-edit-leave-guard r5 — PASS
Retest of wave-3 A1 FAIL (#27464, closed): HOLDS — nav rename + reload now raises
beforeunload(2 of 2 fresh loads). Driver: Playwright, form sign-in, ref/role-targeted clicks,page.on('dialog')registered before every departure and answered deliberately (dismiss = Stay, accept = Leave), a fresh page + fresh held edit per departure cell; departures: (a) pillar linkgetByRole('link',{name:‹pillar›}), (b) rail item, (c) header package button "QA R5" → "QA R5 B", (d)[data-testid=studio-home], (e)page.reload(). Server truth from Node per cell:GET /api/v1/meta/_drafts?packageId=com.qa.rfive+GET /api/v1/meta/TYPE/NAME?state=draft&package=com.qa.rfive. Package fixture: object qfive_asset (+ qfive_bare), flows qfive_flow/qfive_flow2, app qfive_app (nav dashboard entry), dashboard qa_lifecycle_probe, permission qfive_editor, second package com.qa.rfiveb.page.reload()raised a dialog of typebeforeunloadon Stay and on Leave for picklist, lookup, step, widget, Access tick (gap 4–6 ms) and nav rename (gap 306–309 ms; the Studio Interfaces: an in-progress navigation rename does not raise the beforeunload guard, so a reload or tab close silently loses it #27464 retest); clean reloads of Data, Automations, Interfaces and Access raised none.PUT …/object/qfive_asset?mode=draft&package=com.qa.rfive&surface=studio200 seq 52 bodypriority:{type:"select",label:"Priority",options:[{value:"high",label:"High"}]}; lookup target QF Bare → PUT 200, bodylinked_thing:{type:"lookup",reference:"qfive_bare"}; step Object = QF Bare →PUT …/flow/qfive_flow…200 seq 54 node{type:"get_record",config:{objectName:"qfive_bare"} }; widget Dataset Task Metrics + measure task_count →PUT …/dashboard/qa_lifecycle_probe…200 seq 55 widget{id:"widget_1",type:"bar",dataset:"showcase_task_metrics",values:["task_count"]}; Access Stay → autosave draftqfive_asset:{…allowDelete:true}; nav Stay → rename committed and saved (history seq 50 create / 51 update). Each completion draft was discarded after its read._drafts{"drafts":[]}before and after and the item draft404 NO_DRAFTbefore and after (picklist a–e, lookup a/e, step a/b/e, widget a/e, Access a/d/e); nav-rename Leave a/d/e: the pre-existing app draft (from the Stay cells) unchanged —GET /meta/app/qfive_app/historygained no row after 04:22:20Z, label still "QA Dash Renamed Lost Lost". Leave URLs: /automations, /data?surface=object:qfive_bare, /com.qa.rfiveb/data, /_console/studio, reloaded page with the held strip gone.Observation (not a clause): the nav rename shows no "Not saved yet: the rename of the navigation item …" strip (engine.studio.held.navRename exists in i18n) — the guard fires regardless; recorded under "not filed".
studio-authoring.flow-edge-type-authoring r4 — PASS
Fixture: com.qa.r6flow, object qsixf_ticket (published), flow qsixf_edges Start→Push(http)→Check(decision)→End A / End B, Push→End Err, Check→Revise (conditional) and an untyped Revise→Push loop added as a draft over a published baseline (live baseline had no loop). Edges selected in /_console/studio/com.qa.r6flow/automations through each edge's hit path (
svg pathholding‹title›Check → End A‹/title>, element-dispatched click), inspector controls by role/label."type":"conditional"; Push→End Err Fault (error path) →"type":"fault"; Revise→Push Back-edge (revise loop) →"type":"back"(the canvas's "Cycle detected …" banner cleared); Start→Push set Fault then Normal → the second PUT carries{"id":"e_start","source":"start","target":"push"}with no type key. Type options offered: Normal, Conditional, Fault (error path), Back-edge (revise loop).{"type":"conditional","label":"Open path","condition":{"dialect":"cel","source":"record.status == 'open'"} }(inspector showed the same), e_b"isDefault":true(checkbox read checked), _diagnostics {valid:true}.record.name ==→ autosave 200; after a screenshot, the inspector shows data-testid flow-edge-server-error "invalid CEL predicate: Unexpected token: EOF > 1 | record.name == ^ — predicates are bare CEL …", the Problems tab reads "Problems 1" listing it at "Check → End A · SCHEMA"; draft read _diagnostics {valid:false, errors:[{path:"edges.3.condition", code:"expression-invalid", …}]}. (The Problems half, silent in wave 3, now counts it.)studio-authoring.summary-field-authoring r5 — PASS
Fixture: com.qa.r6sum, qsixs_parent (public_read_write) + qsixs_child (controlled_by_parent; name/memo text, due datetime, amount number, price currency, parent master_detail required), published 200.
"child_count":{"type":"summary",…,"summaryOperations":{"object":"qsixs_child","function":"count"} }. Same held shape on a second fresh load ("Child Count a", 0 PUTs) and for "Child Sum" (held "Child field to aggregate … needs a value" until the field was picked). Retest of Studio: adding a Rollup field autosaves it unconfigured, and the Sum/Avg child-field picker offers text and lookup fields #27461 — fixed live.studio-authoring.api-written-keys-survive-studio-save r3 — PASS
Method per variant: API
PUT /api/v1/meta/TYPE/NAME?mode=draft&package=com.qa.rfivewriting the keys →GET …?state=draft&package=com.qa.rfivekept → one Studio edit (form sign-in, Playwright, autosave PUT body captured off the request) → draft re-read and deep-diffed →POST /api/v1/packages/com.qa.rfive/publish-drafts→ liveGET /api/v1/meta/TYPE/NAME.label "QF Asset" → "QF Asset Renamed"(Settings › Basic info); fieldfields.notes.inlineHelpText undefined → "QA help text R5"(field inspector Help text); flownodes[1].label "Set X" → "Set X Renamed"(canvas node); appnavigation[1].label "QA Dash" → "QA Dash Renamed"(nav Edit, double-click rename); dashboardwidgets[0].title → "REV2 Tasks by Status Studio"; permissionobjects.qfive_asset.allowEdit false → trueplus an added emptyfields: {}(matrix editor normalisation, no key lost). Every API-written key equal before/after: object titleFormat, indexes [{fields:[serial],unique:"organization"}], userActions {exportCsv:false,import:false}, editMode "page", searchableFields, publicSharing {enabled:true,maxExpiryDays:7}, imageField "photo"; field serial.maskingRule/searchable, cost.step 0.5/useGrouping false, price.currencyConfig {fixed,EUR}, notes.rows 7/sortable false/widget "markdown-lite", due.dueLike true; flow description, successMessage, errorMessage, variables (incl. y_api), errorHandling {retry,maxRetries 2}, requiredPermissions; app description, branding, areas, contextSelectors; dashboard requiredPermissions ["studio.access"]; permission description, isDefault true, packageId.PUT …?mode=draft&package=com.qa.rfive&surface=studio→ 200 (seq 25, 26, 30, 32, 35, 39); every captured body carries every API-written key with the same value (only differences vs the kept draft: the edit itself and the server-added_diagnosticsomitted; permission alsofields:{}).data.success:true, publishedCount:5(object, flow, app, dashboard, permission); live GETs carry every key as written (object 18/18 incl. the Studio label + help text, flow 6/6, app 4/4, dashboard requiredPermissions + Studio title, permission 3/3 + allowEdit true).Note (dashboard): the first attempt set
requiredPermissions:["showcase.export_data"](a capability the admin does not hold); the author's own draft read then answered 403 PERMISSION_DENIED, so the variant was re-run withstudio.access(held via admin_full_access). See Probe findings.studio-authoring.dashboard-widget-and-filter-types r5 — FAIL
Fixture: package com.qa.r3 (POST /api/v1/packages 201, writable), object r3_ticket (18 seeded rows over 3 statuses × 3 regions × 6 opened_on months, numeric score/hours, account lookup), dataset r3_ticket_metrics (PUT ?mode=draft&package=com.qa.r3 → publish 200; dims status/region/opened_on/account/score, measures ticket_count/hours_sum/avg_score), app r3_app whose navigation names dashboard r3_board.
/_console/apps/r3_app/dashboard/r3_board(21 widgets, 0 role=alert, 21 dataset queries 200): bar 3 bars (590×350 SVG), horizontal-bar 3, column 3, line path 6 points = 6 months, area 6, pie 3 sectors, donut 3, funnel 3 trapezoids, scatter 18 symbols = 18 score buckets, treemap 3 rects, sankey (dims status+region) 7 links = 7 non-empty API cells + 6 nodes, combo 6 bars + 1 line on 2 y-axes, radar 1 polygon / 3 angle ticks, table 3 rows × 4 cols + Total 18/198/31.61, pivot 3 status rows × South/West/North + totals (= API cells); gauge "18 Tickets", solid-gauge "198 Hours", metric "18 Tickets", kpi "31.61 Avg Score", bullet "198 Hours" (= API totals). No collapsed frame.{"status":"open"}(gauge 18→8), text "north"+Enter →{"region":{"$contains":"north"} }(gauge 6), date Last 90 days →{"opened_on":{"$gte":"2026-08-04","$lte":"2026-11-02"} }(gauge 10), number 40+Enter →{"score":40}(gauge 1), lookup Vandelay →{"account":"14hF3lvOO0IbhYRn"}(gauge 6); widget_6 carries explicit inspector bindings (status/region/opened_on/score/account) and maps the same; unbound widget_3 (filterBindings all five false, authored in the inspector) issued 0 queries and stayed "18 Tickets" across all five; Reset restored "Status Sel: All | All time | Account Lookup: All".optionsFrom:{object:"r3_ticket"}→PUT …/dashboard/r3_hold?mode=draft&package=com.qa.r3&surface=studio422 "globalFilters.N.optionsFrom.valueField [invalid_type]; …labelField", the next dashboard Description and Label edits each PUT 422 for the same filter, the page shows "Changes not saved: the server refused this draft", beforeunload is not guarded (defaultPrevented false, no prompt) and after reload both edits are gone. See F-1.studio-authoring.report-authoring-options r3 — PASS
Fixture: same package com.qa.r3 / dataset r3_ticket_metrics (dims status, region; measures ticket_count, hours_sum) / app r3_app; each report created in
/_console/studio/com.qa.r3/interfaces→ nav "+" → Report → name + dataset + measure → Save as draft (PUT /api/v1/meta/report/NAME?mode=draft&package=com.qa.r3&surface=studio200, app nav PUT 200), type/rows/columns/order/chart/blocks set on the Studio properties panel (every completing PUT 200), then each published (POST /api/v1/meta/report/NAME/publish200)./_console/apps/com.qa.r3/report/…, 0 role=alert): R3 Tabular (tabular, rows status+region, hours_sum) → flat table "Status / Region / Hours" with 7 detail rows (Open/North/42 … Closed/West/27) and no group totals; R3 Summary (summary, rows status) → grouped rows Open 30 / Closed 27 / Pending 24 + Total 81; R3 Matrix (matrix, rows status, columns region, ticket_count) → cross-tab header "Status / North / South / West / Total", rows Open 3/3/2/8, Pending 3/—/2/5, Closed —/3/2/5, Total 6/6/6/18 (= API status×region cells); R3 Joined (blocks b_status ticket_count by status, b_region hours_sum by region) → two tables, "Status / Tickets" Open 8/Pending 5/Closed 5/Total 18 and "Region / Hours" North 72/South 45/West 81/Total 198.{"region":"west"}, order[{by:hours_sum,direction:desc}], chart{type:bar,xAxis:status,yAxis:hours_sum}; the page's own query carriesruntimeFilter {"region":"west"}, order {"hours_sum":"desc"};POST /api/v1/analytics/dataset/query {datasetName:r3_ticket_metrics, selection:{dimensions:[status],measures:[hours_sum],runtimeFilter:{region:west},order:{hours_sum:desc} }}→ Open 30, Closed 27, Pending 24 = rendered rows in the same order (unfiltered would be Open 99/Pending 54/Closed 45; data API west sum open 30/closed 27/pending 24); the embedded bar chart draws 3 bars Open≈30, Closed≈27, Pending≈24 on a 0–32 axis, same order.PUT /api/v1/meta/report/r3_rep_bad_chart?mode=draft&package=com.qa.r3(joined + chart) → 422 pathchart"ajoinedreport draws no chart … Deletechart";PUT …/r3_rep_bad_block?…(block b2 without dataset) → 422 pathblocks.1.dataset"blockb2binds none … Bind the block to a dataset"; after each,GET ?state=draft→ 404 NO_DRAFT andGET→ 404 RESOURCE_NOT_FOUND (nothing stored).Item verdicts: dashboards.strict-widget-rejects-stray-keys PASS · dashboards.chart-type-matrix PASS · dashboards.global-filters-rescope PASS · studio-authoring.dashboard-widget-and-filter-types FAIL (A4) · studio-authoring.report-authoring-options PASS.
Retests: #27460 (two-dimension sankey) HOLDS, on both the stock-dataset scratch board and the package board · #27445 (filterBindings key naming an undeclared filter) HOLDS · #27459 (empty Add item) HOLDS for its own shape, but a nested variant fails (F-1).
Trap checks for every verdict above: stale-console-bundle is ruled out by construction (the Console is built from this same commit's ui/). For hydration-race, every DOM read came after a settle wait and after a screenshot showed the surface. For single-datapoint, every grouping widget read 3 or more buckets. For automation-input, clicks are role- or testid-targeted, and fills go through Playwright fill and then Tab or Enter; the server-received PUT bodies prove each value committed.
objectstack-fleet commentedon Nov 2, 2026
domain:devx#1 · os-warren · session_01JQwxbX9avDwM2goWd7zimE
Per-clause verdicts, part 3 of 3 (text oracle evidence)
Continuation of this record. One line per clause, A then N, in the scope order of the record body. Runner tag in the record fingerprint. Double braces inside quoted templates are spaced apart so the write door does not read them as tokens.
studio-authoring.app-and-nav-properties r5 — PASS
Fixture: com.qa.r6nav — published objects qsixn_project (5 rows: 3 open, 2 closed) and qsixn_task (1 row), view container qsixn_project with listViews.closed_projects (filter status=closed), permission set qsixn_reader (read project+task) assigned to member r6member@example.com (admin create-user, mustChangePassword:false; get-session positions ["org_member","everyone"]); admin get-session positions include "org_owner". App qsixn_app (package's first app) via PUT ?mode=draft&package= then publish: branding {primaryColor #E11D48, accentColor #0EA5E9, logo /_console/vite.svg}, contextSelectors [active_pkg → /api/v1/packages], areas area_main [a_closed viewName, a_owner visible "'org_owner' in current_user.positions" (bare string), a_ghost requiresObject qsixn_nosuch_obj] and area_ops [b_rec recordId+recordMode edit, b_filt filters {status:open}, b_badge badge HOT/secondary], top-level navigation with one entry per key; second app qsixn_hidden_app hidden:true. Each persona in its own browser context, form sign-in, identity proved in-page with get-session.
visible, pagevisibleWhen) runs on the legacy evaluator and fails, though the spec defines a bare string as CEL shorthand #27457 (wave-3 A3) — fixed live.studio-authoring.authored-page-slots-and-variables r3 — PASS
Fixture: com.qa.r6pg — published object qsixp_item (Alpha/Beta/Gamma rows, no default record page), app qsixp_app (Items, Vars Page). Pages written through the API (?mode=draft&package=) and published with publish-drafts.
slots.tabssave → 422 with the ADR-0085 tombstone "page.slots.tabswas removed in @objectstack/spec 18.3.0 (ADR-0085) …" (step note confirmed). qsixp_item_record {type:record, object:qsixp_item, kind:slotted, isDefault:true, slots highlights/details/discussion → element:text} published; /_console/apps/com.qa.r6pg/qsixp_item/record/lV9JvqPWwX2NpmeG (screenshot first, two fresh loads) shows "SLOT-HIGHLIGHTS authored block" where the Owner/Amount highlights stood, "SLOT-DETAILS authored block" in the Overview body where "More details / NOTES" stood, and "SLOT-DISCUSSION authored block" inside the Activity card in place of the composer/feed; the un-overridden header ("Alpha Pg Item", Edit, ⋯) and the Overview/Activity tab strip are the defaults seen on the pre-page baseline.size(page.items) > 0) +VAR-RECID (page.rec == ''), and six false-on-defaults controls (page.name != '', count > 5, flag == false, obj.k == 'w', size(items) > 1, rec != '') all absent; no console error. Retest of console: a bare-string CEL predicate (navvisible, pagevisibleWhen) runs on the legacy evaluator and fails, though the spec defines a bare string as CEL shorthand #27457 (wave-3 '"size" is not a function') — fixed live. Two fresh loads.studio-authoring.access-permission-set-lifecycle r4 — FAIL
Fixture: com.qa.rsl with two published objects qrl_project / qrl_task (private OWD); read-only legs on stock com.example.showcase / showcase_contributor.
project_editor; "Save as draft" →PUT /api/v1/meta/permission/project_editor?mode=draft&package=com.qa.rsl&surface=studio200, toast "Permission set “Project Editor” created";GET …/project_editor?state=draft&package=com.qa.rsl→ 200{"name":"project_editor","label":"Project Editor",…,"_packageId":"com.qa.rsl"}.No matches for "zzqq"+ "Clear search"; empty package (before the first create) → main "No permission sets in this package yet." + rail "No permission sets yet — click “+” above to create one".data-testid=studio-rail-add"New permission set" is enabled, opens the dialog, and Save as draft →POST /api/v1/packages/com.example.showcase/ensure-customization201 (packageId: com.example.showcase.custom, created:true) thenPUT …/qr7_pair_set?mode=draft&package=com.example.showcase.custom200, toast "Permission set “QR7 Pair Set” created" (2 fresh loads). By design under ADR-0146 D3 (Studio: a base and its <base>.custom as one surface — "customized" / "added" marks, "only customizations", the pair folded under its base, New lands in the pair #28572: "A New on the read-only base lands in the pair through the ensure door") → disposition stale spec (F-2).PUT /api/v1/meta/permission/showcase_contributor?mode=draft&package=com.example.showcase(label HACKED, objects {}) → 403ITEM_LOCKED"Cannot overlay 'permission' in package 'com.example.showcase': that package is read-only…"; forgedPUT …/qr7_forged_new?mode=draft&package=com.example.showcase→ 422WRITABLE_PACKAGE_REQUIRED; showcase_contributor live read before/aftercmpbyte-identical; qr7_forged_new live 404 and draft 404 NO_DRAFT.studio-authoring.access-facets-runtime r4 — PASS
Fixture: com.qa.rsf, published qrf_ticket (public_read_write; status open/closed/blocked; amount) with an INLINE action
qrf_restricted_echo(type script, requiredPermissions [showcase.restricted_ops], record_header/record_more/list_item) + qrf_note; rows T1 open/10, T2 closed/20 (admin-owned), T3 open/30, T4 blocked/40 (owned by member qr7o); set S = qrf_member_set (qrf_ticket R+U) assigned to member M = qr7m@example.com via sys_user_permission_set; control member qr7s (no set). Identity proved with GET /api/v1/auth/get-session before each persona leg (email qr7m / qr7s).POST /api/v1/actions/qrf_ticket/qrf_restricted_echo→ 403 "requires capability [showcase.restricted_ops] — caller is missing [showcase.restricted_ops]"; Studio System capabilities › Add capability › "Restricted Showcase Operations" → autosave PUT 200, draft"systemPermissions":["showcase.restricted_ops"]; published → M's record page (/_console/apps/account/qrf_ticket/record/…) shows "Restricted Echo" and the action answers 200{"ok":true,"record":"S_ET2QhxAhNbRqU-"}; chip toggled off, published (systemPermissions: []) → 403 again and the record page buttons read ["Edit","Overview","Activity"] (no Restricted Echo).rowLevelSecurity= [{name:qrf_open_only, object:qrf_ticket, operation:select, using:"record.status == 'open'", enabled:true}, {name:qrf_upd_guard, object:qrf_ticket, operation:update, using:"record.amount >= 0", check:"record.status != 'blocked'", enabled:true}] exactly as typed; Test dialog on policy 0: {status:open} → "Allowed", {status:closed} → "Denied".GET /api/v1/data/qrf_ticket→ [T1 open, T3 open] = admin's 4 rows filtered by status=='open'; MPATCH …/T1 {"status":"blocked"}→ 403 PERMISSION_DENIED "You are not allowed to save this record with the values you entered…" (row still open);PATCH …/T1 {"amount":11}→ 200 (amount 11 read back); RLS read policy switched off in Studio (draft enabled:false) and published → M reads all 4 rows.showcase_app, picker Hidden → autosave PUT 200, draft"tabPermissions":{"showcase_app":"hidden"}(key is the whole app name).GET /api/v1/me/apps→ apps [account] only (control qr7s → [showcase_app]); M's Home (screenshot-confirmed) "Your apps 0", no Showcase tile; control qr7s Home "Your apps 1 · Showcase".studio-authoring.access-owd-and-suggestions r5 — PASS
Fixture: com.qa.rso, published qro_project (private), qro_step (private, master_detail
project→ qro_project, not required), qro_doc (private, no master); rows P1, S1, D1, D2; isDefault sets qro_default_set (read qro_doc) and qro_default2 (read qro_project) written via PUT ?mode=draft&package=com.qa.rso and published; member M = qr7s@example.com (no set of the package), identity proved by GET /api/v1/auth/get-session before its reads.PUT /api/v1/meta/object/qro_doc?mode=draft&package=com.qa.rso&surface=studio200 carrying"sharingModel":"public_read", none for qro_project / qro_step.PUT …/qro_doc…422 INVALID_METADATA "declares sharingModel 'controlled_by_parent' but no requiredmaster_detailfield…" shown in the panel, qro_doc draft stays public_read (nothing written), undo → "No changes" and the banner clears; on qro_step Save → 409 DESTRUCTIVE_CHANGE now opens the "Make the relationship required?" dialog naming "project → qro_project"; "Make required and save" →PUT …/qro_step?force=true&mode=draft&package=com.qa.rso&surface=studio200, toast "Saved 1 object draft(s)."; publish-drafts → live qro_stepsharingModel: controlled_by_parent, field project{type: master_detail, reference: qro_project, required: true}; qro_doc public_read, qro_project private unchanged.GET /api/v1/security/suggested-bindings?packageId=com.qa.rso→ statuspending(anchor everyone); pillar reads "This package suggests granting "qro_default_set" to all signed-in users (Everyone)." with Grant / Dismiss; MGET /api/v1/data/qro_doc→ 403 PERMISSION_DENIED; Grant →POST …/suggested-bindings/sug_…/confirm200, statusconfirmed, pillar "…was accepted for all signed-in users (Everyone)." + Revoke; M → 200 with rows; Revoke →POST …/dismiss200, statusdismissed, pillar "…is not granted: the suggestion was dismissed or revoked."; M → 403.POST …/dismiss200, toastSuggestion for "qro_default2" dismissed., statusdismissed, pillar"qro_default2" is not granted: the suggestion was dismissed or revoked.(also after reload); MGET /api/v1/data/qro_project→ 403 before and after.studio-authoring.no-entry-property-matrix r4 — FAIL
GET /api/v1/meta/TYPE/NAME?state=draft&package=app.qar8and from liveGET /api/v1/meta/TYPE/NAMEafterPOST /packages/app.qar8/publish-drafts→ outcome "published", carrying the written value (byte-equal compare per key). Rows whose variant requires a refusal were refused as written (unknown datasource 422 / publish outcome refused; split keys 422 "was removed in @objectstack/spec 18.3.0 (ADR-0050)"; indexesunique:true422 ADR-0120 D1, re-authoredunique:'global').A0 — stored as written and served (row · door · observed · verdict)
A1 — runtime effect (row · door/surface · observed · verdict)
A2 — a key the runtime cannot honour is refused, never silently inert (row · door · observed · verdict)
N0 — PD#10 class (declared, not enforced) (row · observed · verdict)
Studio census (step 4, source-checked in ui/packages/app-shell/src/views): controls now exist for field.currencyConfig, field.relatedList, field.relatedListFilter (ObjectFieldInspector), view.list conditionalFormatting (ViewVariantInspector VIEW_CURATED_FIELDS + rule editor) and permission.allowExport (PermissionMatrixEditor "E" toggle). These move out of the matrix, which is not a fail. No control was found for any other key in the matrix. ObjectSettingsPanel's "searchable" is the object-level enable.searchable, not field.searchable.
studio-authoring.uninstalled-package-refused r3 — PASS
Teardown: nothing was stored (every read 404, DB scan 0), nothing to delete.
studio-authoring.expression-editors r8 — FAIL
Fixture: com.qa.rsx, published object qrx_job (number est_hours/quantity/unit_price/a/b, select status, formula f_formula
record.est_hours * 1.1returnType text, formula f_linerecord.quantity * record.unit_pricereturnType text), published set qrx_job_editor with RLS [qrx_probe_policy select USINGrecord.status == 'open', qrx_upd_policy update USING same + CHECKrecord.est_hours >= 0]. Engine truth: MCPvalidate_expression(POST /api/v1/mcp tools/call) for formula/conditional probes;POST /api/v1/meta/permission/qrx_job_editor/publishfor RLS probes. Browser: Playwright chromium, form sign-in, Studio /_console/studio/com.qa.rsx/{data,access}.record.est_hours * 1.1→ "Valid CEL" + "Result type: Number";record.a + record.b→ "Result type: Unknown — the engine cannot prove a single type. Wrap operands in double() / int() / string() to pin it…" (engine validate_expression formula: inferredType number / unknown — identical).record.est_hours * 1.2→ autosave PUT /api/v1/meta/object/qrx_job 200; draft and (after publish-drafts) live read f_formula returnTypenumber(wastext); f_line, opened in the inspector but not edited, keeps returnTypetext; an edit to the unprovablerecord.a + record.bcleared returnType (null) as the spec says.record.est_hourz*1.1error "unknown fieldest_hourzonqrx_job— did you meanest_hours?" = engine unknown-field; bareest_hours * 1.1error "…Writerecord.est_hours." = engine bare-reference; parse faults error = engine invalid-cel / rls-predicate-unparseable; non-pushdownable USINGrecord.est_hours * 2 > 10error "RLS using is not lowerable (unsupported operand "*")…" = publish 422 rls-predicate-unenforceable; CHECKrecord.f_formula > 5"Valid CEL" = publish 200), but the formula-field USINGrecord.f_formula > 5reads "Valid CEL" (aria-invalid null) for ~1.7 s while the engine refuses it (publish → 422rls-predicate-unenforceable"RLS using (INVALID_FIELD / 400)… a virtual formula field"); the error tier appears only after the autosave's re-read (~2.0 s). Scored fail per knownGaps[3] (Studio: an expression the engine refuses (RLS USING on a formula field, a null-unsafe validation predicate) keeps autosaving — the server verdict is shown on the field but never holds the save #27482 open); disposition stale spec (F-1).previous.andrecord.→ listbox of 8 field options (organization_id … queue, the system fields first, cap 8);record.est→ [est_hours];previous.s→ [status];current_user.→ no options;parent.→ no options; bareest→ no options (withheld); accepting therecord.estoption insertedrecord.est_hours; bareest_hours > 5→ aria-invalid=true "bare referenceest_hours… Writerecord.est_hours." (engine validate_expression validation: bare-reference error).perm-autosave-held"Fix the CEL syntax errors before saving.", 0 PUTs in 7 s; but the formula-field USINGrecord.f_formula > 5(three fresh loads) shows no hold and issuesPUT /api/v1/meta/permission/qrx_job_editor?mode=draft&package=com.qa.rsx&surface=studio→ 200 carrying"using":"record.f_formula > 5"; each newly refused text (> 7) autosaves once more (200); only a sibling edit (CHECK of policy 1) while the judged text stands is held (0 PUTs) — the Studio: an expression the engine refuses (RLS USING on a formula field, a null-unsafe validation predicate) keeps autosaving — the server verdict is shown on the field but never holds the save #27482 residual. Disposition stale spec (F-1).record.status == "open"+ {status:"open"} → "Allowed"; + {status:"closed"} → "Denied";record.est_hours * 2+ {est_hours:4} → "Non-boolean result 8 — a row filter should evaluate to true or false."; direct @objectstack/formula ExpressionEngine.evaluate of the same pairs → {ok:true,value:true} / false / 8.record.status ==→ held, 0 PUTs (2 fresh loads); parse-fault formularecord.est_hours *→ aria-invalid, banner "Not saved yet: an expression on the field “F Formula” has an error to fix…", 0 PUTs, draft unchanged.current_user./parent.list none of qrx_job's fields; bareest_hourswithheld from bare completion and flagged as an error.objectstack-fleet commentedon Nov 2, 2026
domain:devx#1 · os-warren · session_01JQwxbX9avDwM2goWd7zimE
Close-out for W4-C3b1. Every
failhas a disposition; product defects are extracted, stale clauses are owned by a card.Extracted: #29006 · studio-authoring.dashboard-widget-and-filter-types · acceptance[4]
Extracted: #29007 · studio-authoring.no-entry-property-matrix · acceptance[1]
Extracted: #29008 · studio-authoring.no-entry-property-matrix · acceptance[1]
Extracted: #29009 · studio-authoring.no-entry-property-matrix · acceptance[1]
Extracted: #29010 · studio-authoring.no-entry-property-matrix · acceptance[1]
Extracted: #29012 · studio-authoring.no-entry-property-matrix · acceptance[2]
Extracted: #29013 · studio-authoring.draft-publish-lifecycle · acceptance[10]
Extracted: #29015 · studio-authoring.api-written-keys-survive-studio-save · observed beside the clauses
Extracted: #29016 · automation.packaged-flow-clone-contract · observed beside the clauses
Extracted: #29019 · studio-authoring.no-entry-property-matrix · observed beside the clauses
Extracted: #29020 · records-forms.calendar-interactions · observed beside the clauses
Extracted: #29021 · records-forms.form-view-gallery · observed beside the clauses
Extracted: #29022 · studio-authoring.report-authoring-options · observed beside the clauses
Extracted: #29023 · dashboards.global-filters-rescope · observed beside the clauses
Extracted: #29024 · studio-authoring.app-and-nav-properties · observed beside the clauses
Extracted: #29025 · studio-authoring.flow-edge-type-authoring · observed beside the clauses
Parked-on: #28579 · automation.packaged-flow-clone-contract · re-point acceptance[1], acceptance[2], acceptance[6] (ADR-0146 D4)
Parked-on: #28579 · studio-authoring.packaged-automation-studio-lock · re-point acceptance[0], acceptance[2] (ADR-0146 D3, D4)
Parked-on: #28579 · studio-authoring.access-permission-set-lifecycle · re-point acceptance[3] (ADR-0146 D3)
Parked-on: #27482 · studio-authoring.expression-editors · re-point acceptance[2], acceptance[4], acceptance[6], negative[0] once #27482 is ruled
Parked-on: #27377 · checklist-accuracy findings and fixture gaps (reported to the wave anchor)
Dispositions: 5 stale spec (R-1 to R-5), 2 product defect (R-6, R-7). No assertion defect, no invalid pairing.