Repository navigation
QA run · release-18.2.0-w2-manual-browser (34/34) · 756220c4 · 2026-10-27 · 30 PASS / 3 PARTIAL / 1 FAIL / 0 BLOCKED / 0 NOT-RUN #26456
Description
This is the QA run record for card W2-C3b of the 18.2.0 release sweep, wave 2 (wave anchor #25807; Version Packages PR #25640): the manual set, browser/mixed surface. It was run with the checklist-test skill following docs/qa/platform-checklist/RUNNER.md and the dogfood-verification skill. The maintainer ordered this wave directly as a test run; lane/seat dispatch protocol does not apply.
The scope is the unpinned P1 items whose source[] cites a file changed by a Clause-②: yes changeset in #25640, with P0 excluded (card W2-C1 owns P0). The set was named on the card, not resolved by one selector. Each id was resolved on its own with node scripts/checklist-select.mjs ID --json. All 34 came back runnable: none is blocked, none is planned, and none carries an automated.ref, so every clause was driven live. Every item has a verdict (34/34).
This record supersedes, for the 16 items it shares with it, the wave-1 manual-set record #25817 (old head 6679644a). The other 13 items of #25817 belong to the sibling card W2-C3a. Under the card's supersede protocol, whichever of the two cards finishes second closes #25817. When this record was filed, W2-C3a had filed no record, so #25817 stays open and the close is left to W2-C3a (see the close-out comment).
Environment fingerprint
-
Framework:
756220c4a7adcbb1228864607cb4b6a1ca486dc0, checked out detached. The tree was clean before and after.git statusstayed empty throughout, and no runner wrote to the tree.pnpm install --frozen-lockfilepassed in 34 s.pnpm build --concurrency=2passed 119/119 in about 14 min.node scripts/check-dev-prereqs.mjswas green: 108 package build artifacts present.- The tree is unreleased, so the CLI reports
@objectstack/cli/18.1.0.
-
Console build: built in-repo from
ui/at that same commit.pnpm buildbuilt@object-ui/console(packages/console/dist/index.html01:44Z).pnpm console:build --concurrency=2then ran as a cache hit, 72/72 (71 cached). The.objectui-shapin andpnpm objectui:buildare retired. Every console observation was against this build, which rules outstale-console-bundle. -
App and boot:
examples/app-showcase, booted with the canonical lineOS_PORT=PORT setsid pnpm -C …/examples/app-showcase exec objectstack dev --ui --seed-admin -p PORT -d file:SCRATCH/BOOT.db.- Every boot had its own free port in 43100–43700 and its own file DB.
- Seed:
--seed-admin(admin@objectos.ai) plus the showcase seed (132 rows; banner32 flow(s) 19 bound · 8 draft). - Every server was stopped by process group, and a final
lsofsweep found no listeners.
-
Special boots:
--no-seed-adminwithOS_DISABLE_SIGNUP=trueon a fresh DB: first-run owner bootstrap.OS_DISABLE_SIGNUP=true: the signup-gate toggle leg.OS_METADATA_WRITABLE=capability: probe only.OS_SEARCH_PINYIN_ENABLED=false: the pinyin OFF leg.SHOWCASE_WEBHOOK_SECRETset: the webhook intake leg of the node-type matrix.- A reboot with
OS_LOCALIZATION_TIMEZONE=Europe/ParisandOS_MAIL_SMTP_PASSWORD: the settings env-lock legs and webhook activation survival. - An app-todo
--preset minimalscratch copy: the messaging-absent 501 leg. - Scratch app copies (tar, without
node_modules,.objectstackordist): the capability shadow/malformed apps and the retired-webhook-trigger builds. - A published scaffold:
npx -y create-objectstack@latest, which resolved to 18.1.0 through the proxy. OS_AUTOMATION_SCHEDULED_WORK_ENABLEDwas not needed by any clause.
-
Tree side effects:
examples/app-showcase/.objectstack/installed-packages/never existed.examples/app-showcase/.objectstack/data/appeared, empty, at the first boot (01:46:41Z), although every boot passed-d file:…. It is gitignored, and no runner installed a package. -
Browser: Playwright chromium build 1243 (matching
/opt/pw-browsers/chromium-1243), launched withchromium.launch()and default options. Each persona had its own context and signed in through the form, with both the localStorageauth-session-tokenand thebetter-auth.session_tokencookie confirmed and the identity proven viaGET /auth/get-session. At most three runners used a browser at once, and each held at most one browser instance. -
Execution: six runner subagents, each with its own port range and DBs, ran in two staggered waves, never more than three at once. The batches were:
- A: approvals
- B: automation and formula
- C: identity and access
- D: studio-authoring and the permission matrix
- E: records, search and UX
- F: i18n, integration, settings, cli and audit
Items ran sequentially inside each batch. The model tier was the default (opus):
node scripts/pm/dispatch-gates.mjs --tiergives no path-derived mandate for this surface. The coordinator re-verified each extracted defect independently on a boot of its own (port 43700) or against the built package. -
Wall clock: 01:29Z to 03:00Z (UTC, 2026-10-27).
Scope and verdicts
Labels: A0, A1, … are acceptance[0], acceptance[1], …; N0, … are negative[0], … (zero-based).
| item | priority | surface | rev | verdict |
|---|---|---|---|---|
| access-security.audit-log-browser | P1 | mixed | 7 | PASS |
| access-security.public-form-intake | P1 | mixed | 3 | PASS |
| access-security.permission-matrix-edit-loop | P1 | mixed | 4 | PASS |
| access-security.capability-declaration-lifecycle | P1 | mixed | 7 | PASS |
| api-backend.formula-stdlib-matrix | P1 | mixed | 4 | PASS |
| approvals.per-group-signoff | P1 | browser | 10 | PASS |
| approvals.quorum-m-of-n | P1 | browser | 7 | PASS |
| approvals.inbox-metadata-actions | P1 | browser | 6 | PASS |
| approvals.decision-action-matrix | P1 | mixed | 9 | PARTIAL (A9 blocked(fixture), knownGaps[0]) |
| automation.flow-run-step-nesting | P1 | mixed | 8 | PASS |
| automation.rollup-summary-filter | P1 | mixed | 6 | PASS |
| automation.flow-node-type-matrix | P1 | mixed | 9 | PASS |
| automation.flow-error-handling | P1 | mixed | 6 | PASS |
| cli.scaffold-console-first-paint | P1 | mixed | 6 | PASS |
| i18n.notification-localized-and-clears | P1 | browser | 5 | PASS |
| i18n.strict-translation-key-rejection | P1 | mixed | 3 | PASS |
| identity-auth.api-key-ui-lifecycle | P1 | mixed | 4 | PASS |
| identity-auth.org-membership-team-management | P1 | mixed | 5 | PASS |
| identity-auth.first-run-owner-bootstrap | P1 | mixed | 3 | PASS |
| identity-auth.self-signup-gate | P1 | mixed | 6 | PASS |
| integration-system.webhook-lifecycle | P1 | mixed | 10 | PARTIAL (A2 blocked(dependency) #23166; A4 partial: the cross-org and parked arms come from below-HTTP pins) |
| integration-system.notify-inbox-delivery | P1 | mixed | 6 | PASS |
| platform-core.settings-hub-roundtrip | P1 | mixed | 5 | PASS |
| records-forms.form-view-gallery | P1 | browser | 4 | PASS |
| records-forms.action-location-matrix | P1 | browser | 15 | PARTIAL (A3 fail-closed half and A4 half (a) blocked(fixture), knownGaps[2]) |
| records-forms.field-group-visible-when | P1 | mixed | 7 | PASS |
| search.pinyin-flag-both-sides | P1 | mixed | 6 | PASS |
| search.console-global-search | P1 | mixed | 5 | PASS |
| studio-authoring.view-authoring-live | P1 | mixed | 3 | PASS |
| studio-authoring.draft-publish-lifecycle | P1 | mixed | 8 | PASS |
| studio-authoring.authoring-validation-not-persisted | P1 | mixed | 4 | PASS |
| studio-authoring.packaged-automation-studio-lock | P1 | browser | 4 | PASS |
| studio-authoring.doc-on-app-menu | P1 | mixed | 6 | PASS |
| ux-conventions.write-feedback | P1 | browser | 6 | FAIL (A1, the Mark Done row) |
Derived: 30 PASS / 3 PARTIAL / 1 FAIL / 0 BLOCKED / 0 NOT-RUN. One clause failed: ux-conventions.write-feedback A1. Its reproduction rule is below, and it is a product defect in the showcase fixture's copy, extracted. There is no authentication or authorization finding, so nothing is withheld.
The #25822 retest (i18n.strict-translation-key-rejection, a probe beside the clauses that scores no clause):
- The fix holds for the exact reproduction.
PUT /api/v1/meta/translation/qa_bad_localewithlocale: "not a locale!!"→ 422INVALID_METADATA(pathlocale,invalid_format), and the read-back is 404. - It also holds for
zh_CN, for a value padded on either side, and for multi-word names (Simplified Chinese), at both the save door anddefineTranslation/defineTranslationBundle. The controlzh-CNsaves 200. - One residual: a single-word language name of 2–8 letters (
Chinese,English,Japanese) still saves 200_diagnostics.valid:trueand is accepted by both define functions. The changeset and the refusal text both say "a language name" is refused. This is extracted as its own card (see the close-out); it does not reopen spec: a translation item accepts anylocalestring ("not a locale!!" saves 200, valid) — LocaleSchema is a bare z.string() #25822.
Per-clause evidence (one line per clause; text oracles)
approvals.per-group-signoff (rev 10) · PASS
- A0 pass: EXP-2001 reads
pending.pending_approversholds two distinct ids (the admin andusr_showcase_auditor_demo).pending_approver_groupsmaps admin→[manager] and Ada→[finance].decision_progressper_group reads got 0 need 2, and the run ispausedatdual_signoff.
- A1 pass: the admin approves through the request page's dialog:
POST …/approve→ 200.- The request stays
pending, and the slate is [Ada]. - The groups read manager 1/1
satisfied:trueand finance 0/1.
- The request stays
- A2 pass: the chips match the API at all three stages: "0 of 2 groups" with finance 0/1 and manager 0/1, then "1 of 2 groups" with manager ✓, then Approved with no chips.
- A3 pass: Ada, in her own session, approves → 200
approved.- The run goes paused →
completedwith [start, dual_signoff, mark_approved], and there is no return_to_draft step. - The report reads
approved.
- The run goes paused →
- A4 pass:
/actionsholds submit, approve (admin) and approve (Ada), withvia_override:falseon both approves. - A5 pass: the admin's approve carrying
actorIdset to Ada → 403 FORBIDDEN "cannot act as …", and no row is written. With noactorId→ 200, recorded against the session user. - N0 pass: unanimous INV-1012 (need 1) finalizes on one approve; per-group needed two.
- N1 pass: Mei (in neither group) approving → 403 "not a pending approver", and no row is written.
approvals.quorum-m-of-n (rev 7) · PASS
The verdict rests on the runtime fixture, not the clamp. On a fresh boot:
- Ada was bound to finance and Lena Legal (QA) to legal through
sys_user_position(201 each). Lena was minted by invite then sign-up. showcase_expense_signoffwas toggled off and back on.- Two submitted reports (QA-Q1, QA-Q2) each carry a 6000 line. Each slate holds 3 distinct holders, with quorum got 0 need 2.
Clauses:
- A0 pass (clamp contrast): EXP-DEMO need 1 finalizes on one approve, with exactly one approve row.
- A1 pass: Q1 goes 0/2 → 1/2 (Ada's approve in the UI) →
approved(Lena's approve in the UI).- There are 2 approve rows from 2 distinct actors.
- The run completed with mark_approved.
- A2 pass: on Q2, Ada approves (1/2), then Lena rejects in the UI → 200
rejected.- The slate empties, the run completes with return_to_draft, and the report reads
draft. - A later approve → 409 INVALID_STATE.
- The slate empties, the run completes with return_to_draft, and the report reads
- A3 pass: after Q1 finalizes,
pending_approvers=[]and the third holder is dropped.- The admin's My Pending count goes 4 → 3.
?approverId=…&status=pendingno longer lists Q1.
- A4 pass: the page tally matches the API at each stage: "Approvals — 0 of 2 · 3 eligible approvers", then "1 of 2 · 2 eligible approvers", then Approved.
- N0 pass: Ada's repeat approve and Mei's approve (no position) each → 403, and the tally holds at 1/2.
- N1 pass: the clamp is not scored as M-of-N.
approvals.inbox-metadata-actions (rev 6) · PASS
Fixture: a plain member (sys_member.role = member) was reassigned onto EXP-DEMO.
- A0 pass: the member opens My Pending, then the Request cell, which leads to the
sys_approval_requestrecord page.- The decision bar renders [Approve, Reject, Reassign, Send back, Request info, Reply].
- That is exactly the set the served
GET /meta/object/sys_approval_requestdeclares for can_act on a pending request.
- A1 pass:
- The admin's own INV-1012 adds Send reminder and Recall.
- The member's view of EXP-DEMO has neither.
- The admin (override actor) on EXP-DEMO gets Override Approve/Reject/Reassign/Recall and no Send reminder.
- A2 pass: the viewer flags predict each rendered set exactly from the declared CEL
visibleexpressions:- member/EXP-DEMO: can_act T, is_submitter F, can_override F
- admin/EXP-DEMO: F, F, T
- admin/INV: T, T, T
- A3 pass:
- The member's Approve click →
POST …/approve200, and the request isapproved. - The admin's Send reminder →
POST …/remind200, and the request stays pending.
- The member's Approve click →
- A4 pass: one new
approverow (the member) and one newremindrow (the admin). - N0 pass: no button rendered against a denying flag in any of the three (viewer, request) pairs.
approvals.decision-action-matrix (rev 9) · PARTIAL
- A0 pass: all nine variants → 200, and each appends exactly one
/:id/actionsrow naming its actor and action. The variants are approve, reject, revise, resubmit, recall, remind, reassign, request-info (by the reassigned approver) and comment. The maxRevisions revise appends the designed [revise, reject "Auto-rejected: revision limit (2) exceeded"] pair. - A1 pass:
- EXP-DEMO goes pending → approved, and its run goes paused → completed.
- A budget reject (210000) →
rejected, and its run has no needs_exec step. - A budget approve (160000) →
approved, with aneeds_execstep.
- A2 pass: revise →
returned, and the run pauses atwait_revision. - A3 pass:
- A resubmit by a non-submitter → 403 "only the submitter may resubmit".
- The submitter's resubmit → 200, a new pending id on the same
flow_run_id/flow_node_id=manager_review.
- A4 pass: R1 still reads
returnedafter R2 opens. - A5 pass: the third revise → 200
rejected, and the run completes. - A6 pass:
- A recall by a non-submitter → 403.
- The submitter's recall →
recalled, the slate empties and the run completes. - The request leaves
?status=pending&approverId=….
- A7 pass: reassign moves the slate [admin, admin] → [Ada, Ada] and the request stays pending. After remind, request-info and comment, the status, progress, slate and current-step snapshots are byte-identical, with one new row each.
- A8 pass: a project PATCH under manager_review (lockRecord:false) → 200. Under exec_review (lockRecord:true) → 409 RECORD_LOCKED.
- A9 blocked(fixture, knownGaps[0]): still no stock door mints an org-less request at
756220c4.approval-service.tssetsrequestOrg = subjectOrg ?? ctxOrg.- The REST ledger has no submit door.
- No stock flow pairs an approval node with a schedule, time-relative or api trigger.
- N0 pass: revise at exec_review → 400 VALIDATION_FAILED "no 'revise' out-edge", with no row written.
- N1 pass: a second remind → 429 THROTTLED, with no row written.
- N2 pass: every wrong-relationship call → 403 with no row written (recall/resubmit by a non-submitter; request-info after reassigning away; request-info, comment, approve and reassign by an outsider; remind by a non-submitter).
automation.flow-run-step-nesting (rev 8) · PASS
- A0 pass: a trigger with 3 tasks → 200, and the run
completedwith exactly 3send_remindersteps. - A1 pass:
- The 3
guard_remindersteps carry {parentNodeId: loop_tasks, regionKind: loop-body}, with iteration 0/1/2. - Each
send_remindercarries {parentNodeId: guard_reminder, regionKind: try} with its guard's iteration. - start and loop_tasks have no parent.
- The 3
- A2 pass: the nested fan-out over 2 rows gives (iteration, branch) pairs (0,0), (0,1), (1,0) and (1,1), each once, with
branch:0present. All 18 steps parse under the builtExecutionStepLogSchema. - A3 pass: the Studio Runs tab renders a nested tree, not a flat one: "For each task LOOP" → ITERATION 1/2/3 → TRY_CATCH → TRY → Send Reminder.
- A4 pass: the panel shows 8 step rows, matching the 8 API steps, with the same grouping.
- A5 pass: with
tasks:[]the runcompletedwith [start, loop_tasks] and 0 child steps. - N0 pass: the designer panel is the oracle; the developer Flow Runs page was not used.
automation.rollup-summary-filter (rev 6) · PASS
- A0 pass: EXP-2001's 4 lines give total 1500.5, approved 960, reimbursable 512, lines 4, rejected 0 and over_limit 2. All six equal a recomputation from the child rows.
- A1 pass: a line moved approved → rejected (PATCH 200). approved 960 → 620 and rejected 0 → 1; the other four are unchanged.
- A2 pass: billable true → false moves reimbursable 512 → 0; the other five are unchanged.
- A3 pass: deleting a line (DELETE 200) moves total → 880.5, lines → 3, approved → 0 and over_limit → 1, all equal to recomputation.
- A4 pass:
- A scratch package
com.qa.rollupwas created (201), with a parent summarybig_totalfiltered {kind:'big'}. - In Studio, Data › Rollup Parent › Form › Big Total › "Filter (rows to aggregate)", the value was set to "huge" and published ("Published 1 item").
GET /meta/object/rollup_parent→filter:{kind:'huge'}, and a live recompute gives 15 over huge 10 + huge 5 + big 100.
- A scratch package
- N0 pass: no out-of-filter roll-up moved on any edit.
automation.flow-node-type-matrix (rev 9) · PASS
- A0 pass:
GET /automation/actionsreturns 19 ids (the 21 variants minus start and end). The Add-node palette on a clone lists the same 19 plus End. - A1 pass: every variant was evidenced by a real run step: start, decision, get_record, delete_record, assignment, loop, create_record, update_record, http, notify, script, screen, wait, subflow, map, connector_action, parallel, try_catch, approval, approval_revise, and end.
- create_record came from a signed webhook post (202).
- http came from the knownGaps self-ping recipe.
- connector_action came from the self-ping connector with
OS_PORTexported. - end is evidenced by 25 completed runs.
- A skipped end, and an http to an egress-blocked host, were not cited.
- A2 pass: each suspending variant read paused before and completed after: screen (reassign wizard), wait (60 s hold), approval, approval_revise and map.
- A3 pass: a sweep of 35 flows / 34 runs / 104 steps found 0 steps with an empty or missing nodeType.
- A4 pass: loop-body, parallel-branch, try and catch regions each carry parentNodeId and regionKind.
- A5 pass: an unknown node type
bogus_node:- saves 200;
- logs a WARN with
unknownTypes:["bogus_node"]; - its trigger → 400 FLOW_FAILED "No executor registered for node type 'bogus_node'";
- its run reads
failed, with the step carrying NO_EXECUTOR.
- N0 pass: the unknown type was not silent.
automation.flow-error-handling (rev 6) · PASS
- A0 pass: a resilient_sync run
completed, and theguarded_pushtry_catch step reads success. - A1 pass: 4
pushsteps carry {parentNodeId: guarded_push, regionKind: try}, failure, NODE_FAILURE "http: fetch failed". All of them come before the catch steprecord_failure(success). - A2 pass: retryAttempt 0/1/2/3 (maxRetries 3). The durations of 7091 ms and 7219 ms are consistent with 1+2+4 s backoff.
- A3 pass: both tasks read
sync_status=failedwithsync_errorset. - A4 pass: an unprotected probe flow's run reads
failedwith "Node 'push' failed: http: fetch failed". - A5 pass: the Runs panel shows TRY with 4 FAILURE rows, then CATCH SUCCESS "Flag Sync Failure". The probe panel shows the run-level error.
- A6 pass: the log reads
ERROR Trigger-fired run of flow 'qa_b_unprotected' failed … nothing retries the run. - N0 pass, N1 pass (the catch node never ran on successful runs), N2 pass.
- N3: noted only. The container reading success is by design ([Decision] A failed
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).
api-backend.formula-stdlib-matrix (rev 4) · PASS
- A0 pass (test oracle, built-package layer): the harness resolved
packages/formula/dist/index.js, and no src file is newer than dist.registerBoundStdLibregisters 29 distinct names, and the docs table lists the same 29, with 0 missing either way.CEL_STDLIB_FUNCTIONShas 37 entries: those 29 plus 8 CEL builtins.- 79 known-answer rows cover all 29 functions, with 0 failures.
- A1 pass: today(), daysFromNow, daysAgo and isoDate resolve to the zone's calendar day (Asia/Shanghai, UTC). An unknown zone does not throw.
- A2 pass:
- addMonths clamps 01-31+1 to 02-28.
- floor(-1.2) is -2 and an int; ceil(-1.2) is -1.
- joinNonEmpty skips null and blank values.
- coalesce('','x') = ''.
- min/max return the operand verbatim.
- A3 pass: 250/100 → 2.5 (double), and 7/2 → 3 (int).
- A4 pass: the stock
budget_remaining= 180000 and field_zoo f_formula = [315, 3.5]. A scratchupper(joinNonEmpty(…))field gives 'ALPHA-GAMMA' on both create and read. - A5 pass:
sqrt(4)in an active PUT → 422 INVALID_METADATA expression-invalid (a NAME fault), and the item reads 404.- A draft save → 200, but publish-drafts → outcome "refused" with the same issue, and the item still reads 404.
- Control:
floor(record.amount)saves and evaluates.
- N0 pass, N1 pass, N2 pass.
identity-auth.api-key-ui-lifecycle (rev 4) · PASS
- A0 pass: a member mints a key through the UI (Generate key → confirm).
POST /api/v1/keys→ 201 with the fullosk_…secret, shown once in the dialog. Thesys_api_keyreads and lists afterwards carryprefixonly and nokeyfield. - A1 pass:
x-api-keyonGET /data/sys_session→ 200 with 3 rows, all the owner's, the same as the owner's own session read. An admin read sees 8 rows across 5 users. - A2 pass:
PATCH {revoked:true}→ 200, and the nextx-api-keycalls → 403 API_KEY_REVOKED with no records. Control: an unknown key → 401. - A3 pass: un-revoking → 200, and
x-api-key→ 200. - A4 pass:
- A second member's list holds only their own key, and reading the first member's key id → 404.
- The admin list holds both.
- The UI views are scoped the same way, and the admin
all_keysview carries an Owner column.
- A5 pass: the "Your new API key" dialog carries the copyable secret and the "will not be shown again" warning, and the re-opened grid is prefix-only.
- N0 to N3 pass.
identity-auth.org-membership-team-management (rev 5) · PASS
- A0 pass: the Setup Organization nav points to
sys_organization/record/followed by the idget-active-memberreturns. The page shows Members, Invitations and Teams with the management actions. - A1 pass:
- A member's invite → 403.
- Promoted to admin, the invite → 200.
- Demoted again, the invite → 403.
delegated_admin→ 200.
- A2 pass: undefined roles (
guest,contributor,showcase_ops) → 400 ROLE_NOT_FOUND on both update-member-role and invite-member. Nothing is written. - A3 pass:
organization/update(rename) → 200. The full-organization read and the page title show the new name, with slugdefaultpreserved. - A4 pass: after
remove-member(200), the old session → 401. A fresh sign-in → 403 USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION, and the sessions readrevoke_reason organization_membership_ended. - A5 pass:
- create-team → 200, and add-team-member ×2 → 2
sys_team_memberrows. - A duplicate add returns the existing row, so it is idempotent.
- A generic
POST /data/sys_team_member→ 405.
- create-team → 200, and add-team-member ×2 → 2
- A6 pass: a non-admin member is refused 403 on role change, remove, update, create-team, add-team-member and invite. The membership, org-name and team snapshots are identical before and after, and the UI renders "You don't have access to this app".
- N0 to N3 pass.
identity-auth.first-run-owner-bootstrap (rev 3) · PASS
- A0 pass: on a zero-user boot,
GET /auth/bootstrap-status→{"hasOwner":false}before the wizard andtrueafter. - A1 pass: the wizard sign-up → 200, and
get-sessionshows the owner withisPlatformAdmintrue, positions platform_admin/org_owner, and member roleowner. - A2 pass: the CJK and emoji org name
测试组织🚀is applied viaorganization/update, with no create call. There is exactly 1 org, and slugdefaultis preserved. - A3 pass: the exit is a full document load to
/_console/home, which renders the owner's home with the Showcase app. An authed/api/v1/meta/app→ 200 [showcase_app, setup, account]. - A4 pass:
- After bootstrap, an uninvited forge → 403 SELF_REGISTRATION_CLOSED.
- An invited forge under the disable toggle → 400 EMAIL_PASSWORD_SIGN_UP_DISABLED, twice.
- The stock-boot forge → 403, twice.
- The user count stays at 1.
- A5 pass: anonymous
/_console/setupredirects to login with no wizard fields, and the owner's/setupopens the Setup app. - N0 to N4 pass.
identity-auth.self-signup-gate (rev 6) · PASS
- A0 pass:
/auth/configreadsaudiencePosture invite_onlywithdisableSignUp falseon stock, andtrueon the disabled boot. - A1 pass: an uninvited sign-up → 403 SELF_REGISTRATION_CLOSED twice, and no
sys_userrow is created. - A2 pass: invite-member → 200, then sign-up → 200, and
get-sessionnames the invitee. - A3 pass: on the disabled boot,
/registerand/register?redirect=…bounce to login, which has no sign-up link. - A4 pass: on the disabled boot, an uninvited sign-up → 403 SELF_REGISTRATION_CLOSED and an invited one → 400 EMAIL_PASSWORD_SIGN_UP_DISABLED, twice, with no rows created.
- N0 to N4 pass.
access-security.public-form-intake (rev 3) · PASS
- A0 pass: anonymous
GET /api/v1/forms/contact-us→ 200 with fields [name, email, company, message]. No owner_id, organization_id, status, source or created_by appears anywhere in the body. - A1 pass: an anonymous submit with forged owner_id, organization_id, created_by, status and source → 201. Staff read the row back with owner_id null, the deployment org, and created_by null.
- A2 pass: the landed row has status
newand sourceweb. - A3 pass: all four fields persisted.
- A4 pass:
GET /forms/contact-us/lookup/owner_id→ 404 ENDPOINT_NOT_FOUND, identical to a never-registered path. - A5 pass:
/forms/contact-us→ 302 to/_console/f/contact-us, which renders 4 inputs.- The submit → 201, with no Authorization header on any API request.
- The thank-you text renders.
- A6 pass: a view overlay with
allowAnonymous:falsemakes the anonymous GET and submit → 404 FORM_NOT_FOUND, twice. Republishing restores the form. - N0 pass. N1 pass: a
__proto__-nested forgery lands nothing.
access-security.capability-declaration-lifecycle (rev 7) · PASS
- A0 pass:
GET /meta/capability/showcase.export_data→ 200, matchingcapabilities.ts.GETandPOST /data/sys_capability→ 404 OBJECT_NOT_FOUND. - A1 pass, on the action dispatcher:
- Grant and deny:
- The granted member's
perm_held→ 200. - The ungranted member → 403 PERMISSION_DENIED "…caller is missing [showcase.export_data]".
- The admin → 403.
- The granted member's
- Regrant:
PUT /meta/permission/showcase_ops→ 403 NOT_OVERRIDABLE.- A clone with a label → 200, and granting it gives 200.
- Withdrawing the capability → 403 twice; restoring it → 200.
- Cross-checks: perm_missing and perm_and → 403 for all; perm_empty → 200 for all.
- Grant and deny:
- A2 pass: the shadow scratch declares
manage_users.os validateandos buildexit 0, but the boot refuses with "Security catalog name conflict … capability "manage_users" is already held by the platform". At runtime, the PUT → 403 NOT_OVERRIDABLE. - A3 pass:
- A malformed
scope:'galaxy'→os validateexit 1, "capabilities.2.scope: Invalid value 'galaxy'". - Runtime
PUT /meta/capability/qa.new_cap→ 403 NOT_CREATABLE. - A member's PUT → 403 FORBIDDEN.
- A malformed
- N0 to N4 pass.
studio-authoring.view-authoring-live (rev 3) · PASS
The fixture is the runtime object batchd_repair_asset in the authored package com.qa.batchd. A runtime object grants no non-admin persona, so per the item's persona note the end-user surfaces were scored as the admin.
- A0 pass: a draft view PUT → 200
state:"draft", seq 17. - A1 pass: before publish, the list and form render the old shape. The by-name GET → 404, and
?object=→{items:[]}, so nothing leaked. - A2 pass: publish → 200. After a reload the list shows the authored columns and the New dialog the authored sections. The boot log shows only registry lines, with no restart, rebuild or second "Server is ready".
- A3 pass:
GET /meta/view?object=batchd_repair_assetserves the list and form views. - A4 pass: a reorder plus an added column, drafted and published (200 each), renders the new header order after a reload.
- N0 pass (no restart). N1 pass (no pre-publish leak).
studio-authoring.draft-publish-lifecycle (rev 8) · PASS
- A0 pass: a draft PUT →
state:"draft". Runtime GETs → 404 for the admin and for a member, including?state=draftand?preview=truefor the member. - A1 pass: a per-item publish → 200, and the GET serves Rev1.
- A2 pass: with draft 2 staged, the GET still serves Rev1. After publishing, it serves Rev2.
- A3 pass: a stale
If-Match→ 409METADATA_CONFLICTwithcurrentVersion, and the audit row reads denied / metadata_conflict. - A4 pass: publish-drafts with a namespace-less object draft →
data.success:false, outcome refused, publishedCount 0, code NAMESPACE_PREFIX, and nothing is promoted (all 404). After discarding that draft, it →success:true, publishedCount 2, with a commitId. - A5 pass: a draft with a stray widget key → 422 INVALID_METADATA at the save itself, and the GET reads 404.
- A6 pass: Studio shows "Draft · 2 unpublished" and a Changes pill of 2. After the publish the pill reads 1, and after publish-drafts it is gone.
- A7 pass:
/historylists v1 to v4 (create, publish, create, publish), each with the admin as actor. - A8 pass:
diff?from=2&to=4→changed:[{path:"widgets",…}], with added and removed empty. It is not a whole-body dump. - A9 pass: rollback
{toVersion:1}→ 200 and serves Rev1, which renders in the console.{}and"abc"→ 400; 999 → 404 VERSION_NOT_FOUND. - A10 pass:
/auditholds 8 rows, each naming the admin. - A11 pass: a forged
X-Actorheader is ignored. The history and audit rows name the admin, and the forged string appears 0 times. - N0 to N3 held.
- N4 (open ledger question, studio-authoring run notes: one correction to #7637, two stale
automated.refspecs, and three ledger/route observations #7753 item 2): HTTP 200 withdata.success:falsewas observed and is not scored.
studio-authoring.authoring-validation-not-persisted (rev 4) · PASS
- A0 pass: a draft object with a field missing its type → 422 INVALID_METADATA,
fields.amount.type [invalid_value]. - A1 pass: a container-shaped view (
typeandcolumnsbesidelist) → 422, with an unrecognized_keys "Wrap it" hint. - A2 pass: after each rejection, the GET → 404 and
?state=draft→ 404 NO_DRAFT. - A3 pass: in the Setup Source tab (Monaco), the type was edited to
numbrwith the editor's own find/replace. The autosave → 422, and red banners name the field. The stored draft still holds the valid type. - A4 pass: the corrected object and view both save 200 as drafts.
studio-authoring.packaged-automation-studio-lock (rev 4) · PASS
- A0 pass:
GET /packagesshowscom.example.showcasewith writable:false.- The Automations rail has no New button, and the header reads "Read-only · Customize with an overlay".
- Across select, drag, an edit attempt, a wait past the debounce, and a reload, there were 0 non-GET
/api/v1requests. The console's own preference PATCH is excluded.
- A1 pass: the flow GET before and after is byte-identical (
cmp).- A forged draft PUT with
?package=→ 403 ITEM_LOCKED. - Without it → 403 NOT_OVERRIDABLE.
- A forged draft PUT with
- A2 pass: the header switch is disabled with
aria-describedby"Read-only package — switch to or create a writable package to edit." A forced click fires nothing. - A3 pass: all 13 inspector inputs are disabled. A forced typed edit and a node drag change nothing, including after a reload.
studio-authoring.doc-on-app-menu (rev 6) · PASS
Fixtures, all in com.qa.batchd:
- docs: a gated page and two open pages;
- books: a gated book (permissionSet showcase_auditor), an org book, and an empty book that sorts first;
- six doc entries in the app navigation.
The reader holds showcase_auditor; the outsider does not.
- A0 pass: the reader's
/meta/appholds all 6 doc entries and the outsider's holds the 3 open ones. The admin, who does not hold the set, is pruned too, which matches knownGaps. - A1 pass: the rendered menus equal
/meta/appfor both personas. - A2 pass: a flat doc entry lands on its claiming book, not the empty one that sorts first. Each sidebar equals that persona's
/tree. - A3 pass: the outsider gets "You do not have access to this documentation" on 9 gated direct routes, and the server answers 403 PERMISSION_DENIED. Control: an unknown doc → "Documentation not found" with a 404.
- The fixtures were torn down, and the corpus reads back at baseline.
access-security.permission-matrix-edit-loop (rev 4) · PASS
Fixture: set S, granting showcase_task CRU with delete false, held by member M through sys_user_permission_set. M owns 5 tasks.
- A0 pass: M's DELETE on M's own row → 403 PERMISSION_DENIED.
- A1 pass: the Setup matrix Delete checkbox plus Save → a plain
PUT /meta/permission/…200, active, withallowDelete:true. This is the console path; publish then → 404 NO_DRAFT. - A2 pass: M's DELETE → 200, and the read-back → 404.
- A3 pass: flipping delete off (PUT 200) makes M's DELETE → 403.
- A4 pass: a second set granting delete makes it → 200. After that assignment is removed, it → 403.
- A5 pass: the re-opened editor shows Delete off, matching the server.
- N1 extra: a draft-mode save left the DELETE at 403 until the draft was published, then 200. S was then restored.
i18n.notification-localized-and-clears (rev 5) · PASS
The zh-CN user switched locale through the avatar menu (PATCH sys_user {"locale":"zh-CN"} → 200). The EN control has locale null. The flow's notify node has an inline title.
- A0 pass: the zh-CN bell (消息中心) entry reads "New task assigned: Content backlog", with the placeholder rendered.
- A1 pass: the click lands on the task record page, rendered in zh-CN.
- A2 pass:
POST /api/v1/notifications/read {"ids":[…]}→ 200readCount:1, and the server's unreadCount goes 2 → 1. - A3 pass: after a hard reload, and from an independent client, the row stays
read:truewith unread at 1. - A4 pass: the EN recipient's title is identical to the zh-CN one.
- N0 pass, N1 pass, N2 pass.
i18n.strict-translation-key-rejection (rev 3) · PASS
- A0 pass: all 10 variants → 422 INVALID_METADATA via
PUT /api/v1/meta/translation/qa_strict_zh_cn. Everyissues[]entry names its surface and echoes the key. - A1 pass: the built spec's
defineTranslation(/system) anddefineTranslationBundle(root) both throw for all 10 and accept the valid document. - A2 pass: "Did you mean
helpText→help?" and "label→title?" appear at both doors. - A3 pass: each retired key carries its prescription: o, app, nav, dashboard, errors (rule message home), validationMessages (removed in spec 17.0.0, ADR-0049), and _meta (top-level
locale). - A4 pass: rejected saves on a fresh name read back 404, and over a saved baseline they leave it byte-identical.
- A5 pass: the corrected document saves 200. On a zh-CN session the type badge reads
任务(QA-F修正), the New dialog新建任务(QA-F修正), and the field helpQA-F 帮助. - N0 pass, N1 pass.
integration-system.webhook-lifecycle (rev 10) · PARTIAL
- A0 pass: on a fresh boot,
sys_webhookholds 0 rows.- The admin's
POST /api/v1/security/_activation/webhook/showcase_task_changed {"enabled":true}→ 200, writing onesys_metadata_activationrow (active:true). - An undeclared name → 404.
- Anonymous → 401; a member → 403 (
manage_metadata).
- The admin's
- A1 pass: an in-org scratch
sys_webhook(201). Create, update and delete of a task each give exactly 1 receiver hit, withX-Objectstack-Eventdata.record.created / updated / deleted, andtypeandrecordIdin the body. - A2 blocked(dependency, knownGaps[1] / QA run · surface:mixed (90/90) · 36b2889c · 2026-10-12 · 72 PASS / 10 PARTIAL / 8 FAIL / 0 BLOCKED / 0 NOT-RUN #23166):
- A flow
update_record multi:trueon 2 rows produced no bulk delivery. The log reads[webhook-auto-enqueuer] … names no organization — refusing to deliver it inside an organization wall. - The absence half holds: no fabricated per-record deliveries.
- A flow
- A3 pass:
- The custom header arrives on every hit.
- The signature equals
sha256=HMAC(secret, raw body). timeoutMs2000 is honored ("timeout after 2000ms", retry pending).- The outbox rows carry status, attempts,
response_codeandnext_retry_at. A 410 receiver →dead.
- A4 partial (scored partial under RUNNER rule 6):
- Live: redeliver of a dead row → 200 pending, then success, with a byte-identical, verifying signature. Anonymous → 401, non-JSON → 400,
{}→ 400, an unknown id → 404, a pending row → 409 DELIVERY_NOT_ELIGIBLE. - Not drivable on stock
single: the cross-organization 404 and the parked-row 409 DELIVERY_NEVER_SENT arms. These came from pins:webhook-redeliver-tenant-scope.test.tspluswebhook-redeliver-member.test.ts(22 passed, plugin-unit layer), andhttp-outbox-parked-record.integration.test.ts(8 passed, engine/SQL layer, no HTTP). Both stop below HTTP.
- Live: redeliver of a dead row → 200 pending, then success, with a byte-identical, verifying signature. Anonymous → 401, non-JSON → 400,
- A5 pass: a scratch app with
triggers:['undelete']or['api']failsobjectstack compilewith exit 2 and a named refusal. The control['delete']compiles with exit 0. - A6 pass: after a reboot, the activation row is unchanged, and a task PATCH produces a new
declared:showcase_task_changeddelivery row. - N0 pass: failures surface as pending/dead rows, never dropped silently.
- N1 pass: the data door refuses
undeletewith 400. A planted row logs "declares trigger(s) the engine never emits … it will NEVER fire". - N2 pass (spec-parse layer):
ConnectorSchemarefuseswebhooks/triggerswith the retired-key prescription.
integration-system.notify-inbox-delivery (rev 6) · PASS
- A0 pass: after a reassign, the assignee's
GET /api/v1/notificationsliststask.assigned"New task assigned: SEO migration plan" with actionUrl/showcase_task/followed by the task id. - A1 pass: the third member's list is empty with unread 0, and their
sys_inbox_message/ receipt reads total 0. - A2 pass:
POST /notifications/read→ readCount 1, and unread goes 2 → 1, with the receipt flipping delivered → read.POST /read/all→ unread 0.
- A3 pass: anonymous → 401. On a
--preset minimalboot without service-messaging, all three doors → 501 NOT_IMPLEMENTED "Install @objectstack/service-messaging to enable". - A4 pass: a notify with
channels:['push']logs "channel 'push' is not registered; refused 1 delivery row(s)", and the delivery and inbox counts are unchanged. - N0 pass. N1 pass: a cross-user mark-read → readCount 0.
platform-core.settings-hub-roundtrip (rev 5) · PASS
- A0 pass:
PUT /api/settings/localization {"timezone":"Asia/Tokyo"}→ 200, and the re-GET reads Asia/Tokyo with source tenant. - A1 pass:
GET /api/v1/auth/me/localizationfollows: UTC → Asia/Tokyo, and Europe/Paris under the env pin. - A2 pass: a
sys_setting_auditrow (namespace, key, scope tenant, action set, source api, actor the admin,new_hashset).old_hashis null; see the extraction. - A3 pass: the console Save sends
PUT {"fiscal_year_start":"april"}→ 200, and after a reload the field shows "Overrides Default · Reset to default · Tenant". - A4 pass: with
OS_LOCALIZATION_TIMEZONE=Europe/Paris, the GET reads source env, locked true, lockedReason "Set via env: OS_LOCALIZATION_TIMEZONE", and a PUT → 409 SETTINGS_LOCKED. - A5 pass: the "Locked by env" badge shows, with the input disabled.
- A6 pass:
POST /api/settings/mail/test→ 400 SETTINGS_ACTION_FAILED, with "SMTP host is required — nothing was sent" or "No delivery transport is active". - A7 pass: the secret values land in
sys_secret(aes-256-gcm),sys_platform_setting.value_encholds a handle withvalueNULL, and no mail or ai rows exist insys_setting. - A8 pass: set secrets read as the mask, with masked cascade entries, and unset ones read null. No plaintext appears in any body.
- A9 pass: an env-pinned secret reads mask, env, locked. A real PUT → 409, with no plaintext in the body.
- A10 pass: an echoed mask → 200
{values:{}}.- The ciphertext, handle and
updated_atare unchanged. - A fake SMTP AUTH proved the in-process plaintext was not overwritten.
- A new value rotates the handle.
- The ciphertext, handle and
- A11 pass: anonymous → 401 with no body data.
- A12 pass:
GET /api/settingslists 11 namespaces, and each/:ns→ 200. - Negatives pass:
Mars/Phobos→ 400 SETTINGS_VALIDATION, andbogus_key→ 400 UNKNOWN_KEY.
cli.scaffold-console-first-paint (rev 6) · PASS
This was the published path: npx -y create-objectstack@latest (18.1.0 through the proxy, nothing refused), with the blank template. So this proves the published 18.1.0 console, not the 756220c4 workspace console (knownGaps[2]).
- A0 pass:
npx os validateexits 0. - A1 pass:
npx os dev --ui -p PORTserves/_console/200, which redirects to login. The sign-in card renders, and after sign-in the home page renders. - A2 pass: bare
npm run dev -- -p PORT(no--ui) serves the same console, and the banner prints the console URL and the dev admin. - A3 pass: the newcomer signs in with the dev admin the banner prints. The data POST → 201 and the row reads back. The nav/list half is the stated not-applicable limit, since the blank scaffold ships no app.
- N0 to N4 pass.
access-security.audit-log-browser (rev 7) · PASS
-
A0 pass: the admin's
$filterread returns:- the fresh member's login (sys_session);
- the admin's delete with
record_idandold_value; - the admin's config_change.
The member's filtered and unfiltered reads → 403 PERMISSION_DENIED. The parent-record gate pins
audit-log-read-visibility.test.tsandaudit-log-read-visibility.integration.test.tspassed 38 tests (plugin-unit and engine layers); the live door scores the clause. -
A1 pass:
/_console/system/audit-logredirects into Setup. The table paints the columns Timestamp, Action, Object, Record, Actor and IP Address, with the three events present. -
A2 pass: the Action select
deletere-issuesGET …sys_audit_log?$filter={"action":"delete"}…, and the table shows exactly the 4 delete rows. -
A3 pass: an update row's drawer shows the Before and After JSON panels.
-
A4 pass: all 50 page rows match an API read with the same query field by field, with 0 mismatches.
-
A5 pass: POST, PATCH and DELETE → 405 OBJECT_API_METHOD_NOT_ALLOWED. The total is unchanged.
-
N0 pass, N1 pass.
records-forms.form-view-gallery (rev 4) · PASS
- A0 pass: every form type rendered its own shape. tabbed, wizard and split were applied as
PUT /api/v1/meta/view/showcase_task.form?mode=draftwithisDefault:true(200), then published (200), and the servedconfig.typematched each time.- simple: a "Task" section, 2 columns.
- tabbed: Overview/Schedule/Details tabs.
- wizard: 1 Basics · 2 Assignment · 3 Schedule, "Step 1 of 3".
- split: two panes side by side.
- drawer (task-desk): a 384 px dialog docked right over the list.
- modal (task-desk): a centered dialog.
- A1 pass: the wizard sends exactly one
POST /api/v1/data/showcase_task201 at Save on/new, and none per step. On/editit sends one PATCH at Save. - A2 pass:
notesis absent at medium priority, mounts full-width at urgent, and unmounts again at medium. - A3 pass: after every save, the re-read shows only the edited keys changed, across all six types.
- A4 pass: values survive tab hops and wizard Back.
- N0 pass: Next with an empty title shows "Title is required" / "Project is required" and sends no write.
- N1 pass: drawer and modal were scored only on task-desk.
- The view was reverted with
DELETE …/showcase_task.form(200reset:true), and the re-read is byte-identical to the pre-run snapshot.
records-forms.action-location-matrix (rev 15) · PARTIAL
The served actions match the source: 10 actions, and showcase_new_task has locations: [].
- A0 pass: each location renders exactly its declared set.
- list_toolbar: Reassign…
- list_item: Mark Done or Reopen, Reassign…, Quick View
- record_header: Mark Done, Log Time, Archive
- record_more (⋯): Open Docs, Recalculate Estimate, Recalculate Selection
- record_section: Mark Done, Log Time, Archive
- record_related (Project Detail): Log Time
- A1 pass: there are no extra placements anywhere.
- A2 pass: one dispatch per location.
- list_toolbar, no selection: "select a row first", and no request.
- list_toolbar, row selected:
POST /actions/showcase_task/showcase_bulk_reassign200 opens the screen, and resume → 200 changes the assignee on the server. - list_item: Quick View opens its dialog.
- record_header: Mark Done → 200, and
donegoes false→true. - record_more: Recalculate Selection →
POST /api/v1/apps/showcase/recalc200, andestimate_hoursgoes 7→88. - record_section and record_related: Log Time opens its declared form.
- A3 partial:
- The both-sides half passes. A not-done task shows Mark Done; a done task shows Reopen with no Mark Done.
- The fail-closed half is blocked(fixture, knownGaps[2]), re-judged this run. A runtime-authored action with a declared
record_header(PUT 200) is listed under/meta/actionbut not joined into the object'sactions[], and does not render. A throwingvisibleis now also refused at authoring (422 unknown field).
- A4 partial:
- Half (b) passes: New Task renders nowhere, and Recalculate Selection appears only under ⋯.
- Half (a) is blocked(fixture, knownGaps[2]): the runtime action is not served, as before.
- N0 pass, N1 pass, N2 pass.
records-forms.field-group-visible-when (rev 7) · PASS
The scratch object had to be named groupgate_qa_group_gate because of the namespace prefix.
- A0 pass:
- The bare-string PUT (200) reads back bare.
- The envelope PUT (200) reads back
{"dialect":"cel","source":"record.kind == 'pro'"}. - Both gate identically on the form and the detail page.
- Pins
object-strictness-batch20.test.tsandfield-group-layout.test.ts: 70 passed, spec layer.
- A1 pass: the
visibleOnPUT → 422 INVALID_METADATA namingvisibleWhen("FALSE hides the whole group, header included"). The re-read is unchanged. - A2 pass: the "visibleWhen passthrough" describe is green, 5 of 5.
- A3 pass:
- The objectui pin
record-details.groupVisibleWhen-11630.test.tsxpassed 13/13. - Live form: basic hides the "Pro details" header and members, and flipping kind shows or hides them live.
- Detail page: an inline-edit save of kind toggles the section without a reload.
- The objectui pin
- A4 pass: a form edit sends only
{"name":…}, and the hiddenpro_tier/pro_notesstay byte-identical. - N0 pass, N1 pass.
- N2 pass, direction recorded as fail-closed: an unknown-field or syntax-broken predicate hides the section on both surfaces, with no page or console error.
- N3 pass.
ux-conventions.write-feedback (rev 6) · FAIL (A1, the Mark Done row)
- A0 pass: every landed write showed feedback, each paired with a server read:
- create
- inline edit
- bulk delete ×2
- kanban move
- Mark Done
- invite
- approve
- import
- Studio autosave, publish and discard
- Settings save
- Reassign flow
- member create
- A1 fail, the Mark Done row only.
- The record action's toast reads "Task marked done.", which names only the object. Under the rev 6 clause a record or param action is record-level and must name the record or a count.
- Every other row passes its class test:
- 'Task "WF Create E3" created'
- "1 row saved"
- "Deleted 2 Task records"
- "Moved Evidence collection to To Do."
- "Invitation sent to …"
- 'Approved "EXP-2001"'
- "Imported 2 rows."
- "Published 1 item…"
- "Draft discarded: QA Group Gate"
- "Settings saved"
- 'Flow "Reassign Task" completed'
- The dialog create toast now names the record, so Record create/update success toasts name the record by its title, not only the object (ruling C on #25849) #26172's row is no longer an expected FAIL.
- A2 pass: "Task marked done." is the declared
successMessageverbatim, and "Invitation sent to …" renders the declared template withresult.email. - A3 pass:
- An empty create sends no request and shows the required messages.
- A member's Mark Done → 403 with a permission toast, and
donestays false. - A bad currency in Settings → 400 with the ISO 4217 message.
- The kanban refused-transition leg is no longer drivable (console: a state-machine
statusoffers every option in the edit form and every column on the board, and says "任务状态流转无效" only after submit, without naming the allowed next states #25933 drop gate); see the accuracy findings.
- A4 pass: Escape, or re-picking the same value, sends no PATCH and shows no modified state.
- A5 pass: an import job's Undo works in create mode (
POST …/undo200, count 17→15) and in update mode (priority restored). - N0 to N2 pass.
search.console-global-search (rev 5) · PASS
The persona is a contributor-bound member, minted by invite and sign-up with sys_user_position contributor (201).
- Their list shows 0 of 12 invoices, INV-1003 → 404, and their own INV-QA-E1 is visible. The admin sees 12.
- A0 pass: ⌘K, Ctrl+K, the header button,
?palette=1and?cmdk=1each openoverlay:command-palette. - A1 pass: the palette calls
GET /api/v1/search?q=zhangwei&limit=24&objects=…→ 200 (searchAll). - A2 pass:
zhangweiand张伟return 张伟 under "Contact".huaninggroups under the authored label "Account (Success Overlay)" and "Contact". - A3 pass: the top hit has
aria-selected. Enter and a ref click both open the record. - A4 pass: an empty input shows Recently viewed. A one-character query sends 0 requests.
- A5 pass:
- The member's search for INV-1003 →
hits:[],totalHits:0, and the palette shows "No results found.". - The admin's same search finds the invoice.
- The member's search finds their own row.
- The member's search for INV-1003 →
- A6 pass: the full search page (
limit=60) shows "4 results" grouped by object. A no-hit query shows an empty state with 0 console errors. - N0 to N4 pass.
search.pinyin-flag-both-sides (rev 6) · PASS
- A0 pass (ON):
zhangwei/zw/张→ 张伟,wf/wangfang→ 王芳, andhuaning/hnkj→ 华宁科技, with 1 hit each. - A1 pass: on contacts,
hnkj/huaningkeji→ 0, while华宁科技→ 3 through thecompanysource column. - A2 pass (OFF: a fresh boot, and a restart of the ON DB with the flag off):
- Initials
zw/wf/hnkj→ 0. 张still hits.- Full-spelling queries still hit through email/website. No errors.
- Initials
- A3 pass: "Plugins: 53 loaded" ON vs 52 OFF. The roster diff is exactly
PinyinSearchPlugin, and the OFF debug logs (6313 lines) hold 0 'pinyin'. - A4 pass: "[pinyin-search] companion hooks bound", and the reconcile reports account
updated:1and contactupdated:3. - N0 pass, N1 pass.
Reproduction rule for the one fail
ux-conventions.write-feedback · A1 · the record action's success toast names no record. Reproduced twice on fresh loads (tasks D31UoUvnteeR7MXf and sy4Ntk7PLzSu4UoN). The clicks were ref-targeted (role and name inside [aria-label="Page header actions"]), not coordinates. The coordinator independently confirmed the declared string in source.
- Boot stock showcase at
756220c4and sign in as admin through the form. - Open
/_console/apps/com.example.showcase/showcase_task/record/IDfor any task withdone: false(for example "PII access review"). - Click the record-header button "Mark Done". The request is
POST /api/v1/actions/showcase_task/showcase_mark_donewith{"recordId":"ID","params":{}}→ 200, and the server row readsdone: true. - Read the success toast (
[data-sonner-toast]).
Expected (acceptance[1], record-level class, the maintainer ruling recorded on #25849 letter C): the text names the record, e.g. its title "PII access review", or a count.
Actual: "Task marked done.", which names the object only.
Self-check:
hydration-race: ruled out. The toast was read after the 200 and after the screenshot.automation-input: ruled out. The click was ref-targeted and the request fired.eventual-consistency: ruled out. The server read confirmed the write.- Not the console: acceptance[2] shows the console renders the declared string verbatim.
Disposition (§5): product defect, in the showcase fixture's declared copy. The capability works; the declared message violates the ruled convention. It is extracted (see the close-out).
RUNNER rule 7 (a second independent agent before acting): this is a P1 item and the fail blocks nothing, so a full verify pass is not owed. The coordinator re-derived the verdict from the captured strings and the source (examples/app-showcase/src/ui/actions/index.ts:69, successMessage: 'Task marked done.').
No authentication or authorization finding arose in this run, so nothing is withheld.
Derived item verdicts and gaps
30 PASS / 3 PARTIAL / 1 FAIL / 0 BLOCKED / 0 NOT-RUN.
The three PARTIALs, with the gaps that make them partial:
- approvals.decision-action-matrix A9: blocked(fixture, knownGaps[0]), re-confirmed at
756220c4. There is no org-less request on stock: no REST submit door, and no stock approval flow on a schedule, time-relative or api trigger. - records-forms.action-location-matrix A3 (fail-closed half) and A4 (half (a)): blocked(fixture, knownGaps[2]), re-confirmed. A runtime-authored action is listed under
/meta/actionbut never joined into the object'sactions[], even with a declared location. A throwingvisibleis now refused at authoring (422), so the fail-closed half needs a packaged specimen. - integration-system.webhook-lifecycle:
- A2 is blocked(dependency, knownGaps[1] / QA run · surface:mixed (90/90) · 36b2889c · 2026-10-12 · 72 PASS / 10 PARTIAL / 8 FAIL / 0 BLOCKED / 0 NOT-RUN #23166). The bulk event names no organization, and the enqueuer refuses delivery inside an organization wall.
- A4 is partial. On stock
singlethe cross-organization 404 and parked-row 409 arms are not drivable, and their pins stop below HTTP: the plugin-unit layer, and the engine/SQL layer.
The one FAIL: ux-conventions.write-feedback A1, the Mark Done row (product defect, extracted).
Superseded verdicts from #25817: this run re-judged 16 of #25817's 29 items at 756220c4:
- unchanged:
- access-security.capability-declaration-lifecycle PASS
- api-backend.formula-stdlib-matrix PASS
- approvals.per-group-signoff, quorum-m-of-n, inbox-metadata-actions PASS
- approvals.decision-action-matrix PARTIAL
- automation.flow-run-step-nesting, flow-node-type-matrix, flow-error-handling PASS
- i18n.notification-localized-and-clears, strict-translation-key-rejection PASS
- identity-auth.org-membership-team-management PASS
- integration-system.notify-inbox-delivery PASS
- records-forms.action-location-matrix PARTIAL
- search.pinyin-flag-both-sides PASS
- changed:
- ux-conventions.write-feedback PASS (rev 4) → FAIL (rev 6). The clause was reworded to the maintainer ruling between the two runs, so the change is in the clause, not a regression.
The other 13 items of #25817 are W2-C3a's; this record carries none of them forward.
Checklist-accuracy findings and fixture gaps
These close out through the wave anchor #25807 and are not extracted. To keep this body under GitHub's size cap, they and the not-extracted observations are in the first comment on this record.
Probes (RUNNER rule 8)
About 26 runner-minutes, spread across the six batches on the same boots and personas as the clauses. The personas were admin, plain and contributor members, the seeded demo personas, a key as a machine caller, anonymous, and an outsider or forger. The boundary classes were empty, missing or unknown ids and bodies; the opposite posture (flag OFF, walled vs stock, hatch on, a minimal preset); and readers outside the asserted scope.
The doors probed:
- the approval decision doors and the new
GET /approvals/records/:object/:recordId/lockdoor (feat(plugin-approvals,rest,client): a record-anchored approval lock read,{ locked }for every caller who can read the record #26361) - the automation trigger and run doors
- the formula authoring door
- the API-key, public-form and signup doors
- the capability escape hatch
- rollback, diff and publish-drafts
- the translation locale door (the spec: a translation item accepts any
localestring ("not a locale!!" saves 200, valid) — LocaleSchema is a bare z.string() #25822 retest) - redeliver, notifications read, settings and the audit log
- the field-group predicate door
- the search door and the palette
Four probe findings passed the filing gate and are extracted (see the close-out): the diff missing-version 200, the single-word locale residual of #25822, the old_hash gap, and the page-route update toast. The one clause fail is extracted too. The rest are listed under the observations above.
Timings
- Environment prep: about 15 min (01:29Z to 01:44Z): checkout, install 34 s,
pnpm build14 min, and aconsole:buildcache hit. In-run prep (boots, personas, scratch apps) was about 33 runner-minutes across the six batches. - Testing: about 114 runner-minutes for the clauses, plus about 26 for probes. Wave 1 (A, B, C) ran 01:46Z to 02:06Z. Wave 2 (D, E, F) ran 02:01Z to 02:56Z. E was the longest batch, at 55 min wall clock.
- Coordinator verification: the reproductions and source checks for the extractions took about 10 min (02:22Z to 03:05Z, interleaved).
- Wall clock: 01:29Z to 03:00Z.
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 27, 2026
Checklist-accuracy findings, fixture gaps and not-extracted observations for this record (card W2-C3b, wave anchor #25807, at
756220c4). They were moved out of the body for size.sys_approval_requestrecord page, where the tally reads "Approvals — x of 2 · N eligible approvers". The fixture's admin-created reports make the admin both the submitter and the third holder; this is harmless and deserves a line in requires[1].skippedexec_review step after needs_exec. The discriminator holds.t=UNIX,v1=HEXover "t.rawbody". A body-only HMAC → 401 INVALID_SIGNATURE.nameandlabel.itemsmust be record-shaped[{id,title}].expense_report.outcome:"refused"entry inside publish-drafts' batch envelope, not as an HTTP 422./_console/apps/com.objectstack.account/sys_api_key/view/mineand…/com.objectstack.setup/sys_api_key/view/all_keys./login?redirect=/accept-invitation/IDrenders an "Accept your invitation" card with a "Create an account and join" link. It does not forward to/registerautomatically./_console/landing on/_console/home.--no-seed-adminboot seeds no demo personas.mixed, but no clause has a browser oracle.examples/app-showcase/src/security/capabilities.tsstill says the declarations are "seeded intosys_capabilityat boot", which contradicts ADR-0131 D3 and the live 404.NAMESPACE_PREFIX).sharingModeltakesprivate|public_read|public_read_write|controlled_by_parent.METADATA_CONFLICT; the lower-case spelling is the audit row's.widgets), so "isolates the widget/dimension key" overstates it.DELETE /meta/TYPE/NAME?state=draft./apps/PACKAGE_ID/docs/…, and the app-name form redirects there.response_code(the item sayslastStatusCode), and the activation row carriesactive(the item saysenabled).$filtermust be JSON.requires: ['webhooks','realtime'].isActive:false(see the observations below). The working oracle is a post-reboot delivery row.packages/spec/distby absolute path from a scratch dir.content/docs/getting-started/quick-start.mdxcarries nocreate-objectstackcommand.pnpm run devwhere the item saysnpm run dev; both work.invalid_optionforMars/Phobos; the live refusal is 400 SETTINGS_VALIDATION withcode: "value_domain"./newor/editwith a page load makes save'snavigate(-1)leave the document and drop the toast. That is a driver artifact; steps could say to reach the route by in-app navigation.visible.groupgate_qa_group_gate).statusoffers every option in the edit form and every column on the board, and says "任务状态流转无效" only after submit, without naming the allowed next states #25933's drop gate dims the blocked lanes (data-kanban-drop-blocked) and a drop sends nothing, so the validation-rule refusal needs another surface. The dimmed lane also carries no reason text (no title, no aria-describedby), which may need a ruling against "every disabled control explains why".window.confirm, while Studio discard uses an in-app dialog.search:qa-contributor-bound-memberstep 5: the first accountGET /api/v1/data/showcase_accountreturns is churned, so the invoice POST → 400 "This account has churned". Say to pick a non-churned account.examples/app-showcase/.objectstack/data/is created, empty, by a boot that passes-d file:…. It is gitignored and harmless, and it is not theinstalled-packagestrap.Observations seen beside the clauses, no declared contract violated, not extracted (for the anchor's judgment):
sys_audit_logkeeps the fullsys_secretrow, ciphertext included, inold_value/new_valueon create and delete. A rotated secret's AES-GCM ciphertext therefore stays readable by audit-log readers. No plaintext was found in any of 157 rows.GET /api/v1/meta/webhook/showcase_task_changedanswersisActive:falsewhile the activation ledger has it on and it delivers.POST /api/v1/keyswithx-api-key→ 201), so a leaked key can mint replacements that outlive its own revocation.USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL, an account-existence signal limited to invited addresses.fill) leaves no itemaria-selected, so Enter does nothing (2/2 each). Typing key by key always works (3/3). This is driver-sensitive (automation-input), so it is not confirmed as a user-facing defect.limit=0andlimit=-1are coerced to 1 (200,truncated:true) rather than refused, andtotalHitsreports the returned count.GET /approvals/requests/ID/actionsanswers 200[]for a request the caller cannot see, whereGET /approvals/requests/IDanswers 404. No data leaks.visibleWhenwith an unknown field saves 200 and fails closed at render, while the same predicate on an actionvisibleis refused 422 at authoring. The two predicate slots are inconsistent at authoring time.Generated by Claude Code
objectstack-fleet commentedon Oct 27, 2026
Close-out for this record (card W2-C3b, RUNNER extraction obligation and checklist-test §5).
#26456in the extracted cards stands for this record's number. Each card number below is the one its filing returns.Extracted (product defects, each its own card, deduped open AND closed):
Parked on the wave anchor (partial coverage and fixture gaps):
Four-way disposition of the one
fail:ux-conventions.write-feedbackA1 is a product defect in the showcase fixture's declaredsuccessMessage. It is not an assertion defect: the clause matches the maintainer ruling on #25849.Supersede: this record re-judges 16 of #25817's 29 items (listed in the body). The other 13 belong to the sibling card W2-C3a, which had not filed a record when this one was filed. So under the card's rule ("whichever of the two cards finishes second") #25817 is NOT closed here. W2-C3a closes it with
Superseded-by:naming both records, and carries forward any verdict neither record re-judged.The #25822 retest: the fix holds for every reproduced shape except single-word language names (carded above as #26463, not a reopen).
Generated by Claude Code
objectstack-fleet commentedon Oct 28, 2026
Close-out (checklist-test §5) · 2026-10-28T15:34Z. The one FAIL,
ux-conventions.write-feedbackA1, is a product defect extracted as #26459, now fixed and closed. The other defects found in the run (#26461, #26462, #26463, #26465) are fixed and closed too. The 3 PARTIAL items are parked on #25807. Every verdict has a disposition: closingcompleted.Generated by Claude Code