Repository navigation
Description
qa#2 · os-bob · session_012dn2Acf5wtyrf2yKWCThBj
Runner session_012dn2Aej8bK94WiqAMApccX (cloud, effort high), dispatched by qa#2 (#32023) as round 1, slice R1: an early probe of the 18.4.0 release wave, on the maintainer's word 「开跑」. The runner staged the record; the seat posts it through the relay. The seat cross-checked the mechanisms of cards A, D, P-1, P-2 and P-3 against the subject's source.
This record covers the owed retest list release-18.4.0-w1-owed (scripts/qa/retest-owed.mjs): 11 items whose QA-extracted fix cards closed after their last verdict.
Result: 11/11 judged · 5 PASS / 2 PARTIAL / 4 FAIL / 0 BLOCKED / 0 NOT-RUN. No planned item. No security finding.
- The 4 FAIL items are 2 product defects (R-3, R-4) and 2 stale specs (R-1, R-2). Off-clause probes found 3 more product defects.
- All 12 fix cards named by the owed rows HOLD: group posture: a member whose session has no active organization gets 500 ERR_SYSTEM_WRITE_ORGANIZATION_REQUIRED on a tenant-scoped create, where isolated answers 403 naming the missing organization #24784, os start --home (or -d) organization-ownership refusal prints ceremony commands that name no database, so following them verbatim plans a different database #31488, GET /api/v1/meta/_drafts silently ignores ?package= and answers the unscoped draft list, while its sibling metadata doors read ?package= and refuse unknown parameters #31490, Screen-flow resume with a non-string value for a text screen field passes the resume gate, fails the downstream write and consumes the run #31491, Console login card collapses into the single-sign-on view and offers "Back to single sign-on" when email+password is off and phone sign-in is on with no SSO configured #31492, Import wizard: the mapping-template "Save current" button is disabled with no reason on hover, on focus or to assistive technology #31493, Calendar: a failed reschedule toasts the raw server sentence "Record ID not found in OBJECT_NAME", untranslated, with the machine object name and the record id #31494, Studio: the disabled Publish button ("No drafts pending publish") is unreachable by keyboard and shows no hover reason, unlike the read-only Publish state beside it #31495, POST /api/v1/email/send with a JSON array body answers 400 VALIDATION_FAILED instead of the INVALID_REQUEST the route gives every other non-object body #31496, Re-point platform-core.seed-mode-matrix: the seed default mode is ignore since #30152, the item still pins upsert #31497, Re-point platform-core.url-overlay-contract: the record page addresses its tabs with ?related=, not ?tab=, and one Back returns to the record #31498, and 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 (against its ruled acceptance).
Environment fingerprint
- Framework:
ff04ad811da54166a5d1114fade5a1a6ab99138c(main, 2026-11-10), detached.ui/is the in-tree import at the same commit, and the Console was built from it. - Build: one cold cloud container.
pnpm install --frozen-lockfile19 s;pnpm build --concurrency=2119/119 tasks in 10m26s;pnpm console:builda cache hit plus copy;check-dev-prereqs107 artifacts. - Slices: the main session plus three runner sub-agents (effort high) in the same container, in parallel. Each item had its own port and file DB under /tmp. Boots were
objectstack dev --ui --seed-admin --no-compile --no-watch -p PORT -d file:/tmp/...withOS_PORTandOS_STORAGE_LOCAL_ROOTexported, unless an item's fixture says otherwise.- main: access-security, cli, platform-core.seed-mode-matrix, studio-authoring.meta-save (ports 41441–41458)
- s1: automation.screen-flow-roundtrip, platform-core.url-overlay-contract, studio-authoring.expression-editors (41410–41412)
- s2: the two ux-conventions items (41420–41421)
- s3: identity-auth.auth-method-matrix, integration-system.email-template-render (41430–41435, SMTP sink 41439 via plugin-email
startFakeSmtp)
- Seed and personas: the stock showcase seed and seeded admin. Members were made by invite plus sign-up with
invitationId, or by admin create-user on walled boots. Identity was proved server-side before each persona's cells. - Walled boots:
OS_TENANCY_POSTURE=groupandisolated,OS_PLATFORM_OWNER_EMAIL=admin@objectos.ai,OS_AUTH_MEMBERSHIP_POLICY=invite-only, from the tree. - Older databases:
- v17: a
create-objectstack@17.7.0blank scaffold booted once by its own 17.7.0os dev. - 18.0 pre-release: the committed
cli-18-prerelease-dbrestore.
- v17: a
- Hygiene: every port free at the end,
git statusclean,.objectstack/installed-packagesempty. One apply report escaped into the tree and was moved out at once (cli item). - Rule 7: a second, independent agent re-derived the P0 FAIL (access-security acceptance[4] and [6]) from the captured evidence alone. It agreed: FAIL, stale spec.
- Spend: runner session $31.07 (
get_sessionusage.cost_usd, sub-agents included).
Scope
Selector release-18.4.0-w1-owed: 11 ids, each resolved with node scripts/checklist-select.mjs ID --json. access-security was resolved with --include-blocked: its blocked marker (#9334) hides it, but its walled-boot fixture is provisionable on this tree, so it was run.
access-security.no-active-org-session-semantics r9 · automation.screen-flow-roundtrip r7 · cli.organization-ownership-ceremony r9 · identity-auth.auth-method-matrix r10 · integration-system.email-template-render r17 · platform-core.seed-mode-matrix r8 · platform-core.url-overlay-contract r5 · studio-authoring.expression-editors r9 · studio-authoring.meta-save-preconditions-and-draft-rows r3 · ux-conventions.disabled-control-reason r6 · ux-conventions.labels-over-machine-names r4
Derived item verdicts
- access-security.no-active-org-session-semantics: FAIL, acceptance[4] and [6], stale spec (R-1). group posture: a member whose session has no active organization gets 500 ERR_SYSTEM_WRITE_ORGANIZATION_REQUIRED on a tenant-scoped create, where isolated answers 403 naming the missing organization #24784 HOLDS live on a walled group boot.
- automation.screen-flow-roundtrip: PASS. Screen-flow resume with a non-string value for a text screen field passes the resume gate, fails the downstream write and consumes the run #31491 HOLDS.
- cli.organization-ownership-ceremony: PASS. os start --home (or -d) organization-ownership refusal prints ceremony commands that name no database, so following them verbatim plans a different database #31488 HOLDS.
- identity-auth.auth-method-matrix: PARTIAL, acceptance[0]'s SSO and device variants are blocked(environment) (knownGaps[3]). Console login card collapses into the single-sign-on view and offers "Back to single sign-on" when email+password is off and phone sign-in is on with no SSO configured #31492 HOLDS.
- integration-system.email-template-render: PARTIAL, acceptance[7]'s no-provider 501 sub-case is scored from the unit pin only. POST /api/v1/email/send with a JSON array body answers 400 VALIDATION_FAILED instead of the INVALID_REQUEST the route gives every other non-object body #31496 HOLDS.
- platform-core.seed-mode-matrix: PASS. Re-point platform-core.seed-mode-matrix: the seed default mode is ignore since #30152, the item still pins upsert #31497 HOLDS.
- platform-core.url-overlay-contract: PASS. Re-point platform-core.url-overlay-contract: the record page addresses its tabs with ?related=, not ?tab=, and one Back returns to the record #31498 HOLDS.
- studio-authoring.expression-editors: FAIL, acceptance[4] and negative[1], stale spec (R-2). 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 HOLDS against its ruled acceptance.
- studio-authoring.meta-save-preconditions-and-draft-rows: PASS. GET /api/v1/meta/_drafts silently ignores ?package= and answers the unscoped draft list, while its sibling metadata doors read ?package= and refuse unknown parameters #31490 HOLDS.
- ux-conventions.disabled-control-reason: FAIL, acceptance[0]–[2], product defect (R-3). Import wizard: the mapping-template "Save current" button is disabled with no reason on hover, on focus or to assistive technology #31493 and Studio: the disabled Publish button ("No drafts pending publish") is unreachable by keyboard and shows no hover reason, unlike the read-only Publish state beside it #31495 HOLD.
- ux-conventions.labels-over-machine-names: FAIL, acceptance[0] and [1], product defect (R-4). Calendar: a failed reschedule toasts the raw server sentence "Record ID not found in OBJECT_NAME", untranslated, with the machine object name and the record id #31494 HOLDS.
Per-clause verdict tables
access-security.no-active-org-session-semantics r9 (P0 api) · main · 41443 (group), 41444 (isolated)
Fixture: a walled boot of the stock showcase (RUNNER "A walled boot is a fixture of its own"). Both postures booted with no FATAL.
Producer (2) of knownGaps[0]:
- The operator creates org A and org B (
POST /api/v1/auth/organization/create, 200 each). - The operator seeds a
showcase_accountin B as the cross-tenant control. - With A active, the operator mints member M by
POST /api/v1/auth/admin/create-user(200, organizationId A, membershipCreated true). - M changes its password, signs in, sets A active, then
POST /api/v1/auth/organization/set-active {"organizationId": null}(200). GET /api/v1/auth/get-sessionreadsactiveOrganizationIdnull.
Producer (1), a member-less user, has had no door since #25626, so its legs are blocked(fixture) per knownGaps[0].
| clause | verdict | evidence |
|---|---|---|
| acceptance[0] | pass | isolated: M reads showcase_account, showcase_project and showcase_task → 200, 0 rows each. group: 200 with 14, 5 and 10 rows, all organization_id A, none of B. The producer (1) leg is blocked(fixture). |
| acceptance[1] | pass | Both postures: M POST /api/v1/data/showcase_task → 403 PERMISSION_DENIED "… this session has no active organization — so this insert has no organization to place the record in. Join or select an active organization and retry." |
| acceptance[2] | pass | Admin-side SQLite count of NULL or empty organization_id: showcase_task 0 → 0, showcase_account 0 → 0. Task total 30 → 30. |
| acceptance[3] | pass | M (no active org): GET org B's showcase_account row → 404; PATCH → 403; GET /api/v1/sharing/rules → 403 (manage_sharing). Pin sharing-rule-org-less-caller.dogfood.test.ts green. |
| acceptance[4] | fail | M's raw sys_audit_log rows, both postures: login A, login A, logout with tenant_id NULL (sent while the session had no active org), login A. The operator reads it (200); M gets 403 (object permission). → R-1 |
| acceptance[5] | pass | get-session 200. M reads sys_user 200 and meta/object/showcase_task 200. Zero 5xx and zero "Unhandled error". |
| acceptance[6] | fail | Sharing refuses (403) and layer 0 and the write path scope or refuse, but the audit ledger accepts a NULL-attributed logout row for the same state. → R-1 (same root) |
| acceptance[7] | pass | Walled group, producer (2) with membership {A}: POST showcase_task → 403 (the acceptance[1] envelope, not 500 ERR_SYSTEM_WRITE_ORGANIZATION_REQUIRED), again on a second sign-in. NULL count 0 → 0. Control: with A active → 201, organization_id A. #24784 HOLDS live. |
| negatives[0]–[5] | held | No cross-tenant read or write; no NULL-stamped row; the acceptance[4] row is visible to the operator; no 5xx; the refusal names the missing org; the code is PERMISSION_DENIED. |
Pins: plugin-security no-active-organization-write-refusal.test.ts and no-active-organization-insert-union-scope.test.ts (29 passed); dogfood no-active-organization-write-refusal.dogfood.test.ts and sharing-rule-org-less-caller.dogfood.test.ts (22 passed).
cli.organization-ownership-ceremony r9 (P0 cli) · main · 41450–41458
v17 database: a blank create-objectstack@17.7.0 scaffold (npm install --legacy-peer-deps). Its own 17.7.0 os dev --seed-admin booted once over file:/tmp/qa/v17/db/v17.db. Copies c1–c4 and H were taken from it (sha256 3a84e5c9…). The ceremony legs ran as a production os serve with OS_AUTH_SECRET and OS_SECRET_KEY set; the 18.0 leg ran under --dev.
| clause | verdict | evidence |
|---|---|---|
| acceptance[0] | pass | 9 pin files green at ff04ad8 (cli apply-flags 7, organization-ownership-apply 117, occupancy.integration 5, dialect 19, apply-organization-ownership-gate.integration 5, comment-reactions.integration 7, host-supplement.integration 7, apply.generation-1 17; runtime organization-ownership-boot-guard 30). |
| acceptance[1] | pass | First os serve over c1 → exit 1, refusal. Bare plan → exit 0, organization-ownership-plan, ceremony "ADR-0131 D10", 150 table rows ("127 table(s) planned … 74 will receive NOT NULL"). Primary sha256 unchanged. --apply without --plan-file → exit 1 apply_refused / plan-file-required (text: "✗ Refused: --apply executes only a plan you reviewed."); checksum unchanged. |
| acceptance[2] | pass | --apply --plan-file plan.json --backup-taken --out apply.json --json → exit 0, complete, blocking 0, 13 steps done. sys_migration adr-0131-organization-ownership verified, shapeGeneration 1 equals ORGANIZATION_OWNERSHIP_SHAPE_GENERATION. |
| acceptance[3] | pass | os serve over migrated c1 → ready, health 200. Over unmigrated c2 → exit 1 "✗ This database carries no organization-ownership marker …" with "Run 'os migrate organization-ownership --out PLAN' …". 0 hits for "17.x release". The --dev 18.0 boot printed the --dev form of the command. |
| acceptance[4] | pass | Fresh c4: comment-reactions --apply without --yes → exit 1; with --yes twice → exit 0. Then os migrate apply --allow-destructive --yes --json → exit 1 refused_organization_ownership_unmigrated naming 9 tables, "applied":[], sha256 unchanged. |
| acceptance[5] | pass | cli-18-prerelease-db restore (the committed dump), reset token re-dated. The census matched the manifest. serve --dev → exit 1 naming sys_flow_credential and sys_secret; plan → exit 0; apply → exit 0, complete, blocking 0 (sibling-moves moved 10, jobsRewritten 2, mirrorsDeleted 1, column-drops 2). Second boot ready. Identity mails moved to sys_identity_email (24 common columns equal except updated_at and attempt_count); invitation mails stay; 8 global audit rows moved to sys_platform_setting_audit; 2 identity-mail jobs re-pointed. Admin, member A and member B sign in with their 18.0 passwords (wrong password → 401). The reset token from the moved mail works. One showcase_task PATCH → exactly one sys_http_delivery row, ref_id declared:showcase_task_changed. The only unlisted count decrease is telemetry sys_activity__r20261027 (66 rows), which is sys_activity's 14-day rotation, not the apply (A-3). |
| negative[0] | held | The plan run and every refusing arm left the checksum unchanged. |
#31488 HOLDS. os start --no-ui --no-compile --home H over a v17 copy → exit 1, and the printed commands carry --database-url file:H/data/objectstack.db and the auth secret. Run verbatim with OS_HOME and OS_DATABASE_URL unset, the plan writes and the apply completes; os start --home H then boots. A path with a space is quoted correctly.
Hygiene: the verbatim apply (no --out) wrote its report into examples/app-showcase/. It was moved out at once. Not a defect: the ceremony prints where it wrote.
platform-core.seed-mode-matrix r8 (P1 api) · main · 41441
Fixture: writable package com.objectstack.qa.seedmodes (namespace seedmodes) with object seedmodes_probe (name, value, other; private). Seeds authored as drafts and applied by POST /api/v1/packages/com.objectstack.qa.seedmodes/publish-drafts.
| clause | verdict | evidence |
|---|---|---|
| acceptance[0] | pass | upsert {A:a1, B:b1} → inserted 2. A.other edited at runtime. upsert {A:a1, B:b2, C:c1} → inserted 1, updated 1. B keeps its id with value b2. A is unchanged, updated_at identical (a no-op skip despite the runtime edit). |
| acceptance[1] | pass | update {A:a2, D:d1} → updated 1, inserted 0; count 3 → 3; D absent. |
| acceptance[2] | pass | ignore {A:a3, E:e1} → A keeps a2, E inserted. Probe: a seed with no mode → A keeps a2, H inserted, so the default is ignore. |
| acceptance[3] | pass | insert re-declaring A → a second A row on the unconstrained object. |
| acceptance[4] | pass | F hand-inserted; replace {G} → F survives, G inserted. Source: seed-loader.ts case 'replace' routes to insert ("caller should have cleared the table"). |
| acceptance[5] | pass | SeedMode has 5 members, SeedSchema.mode defaults to 'ignore' (seed.zod.ts:207); seam dataset.mode || config.defaultMode (metadata-protocol seed-loader.ts:1303). |
| negative[0] | held | The second idempotence replay: inserted 0, updated 0, every updated_at unchanged. |
| negative[1] | held | update created nothing; ignore rewrote nothing. |
Observation (knownGaps[2], not scored): after the insert duplicate, upsert bound A to the newer duplicate. #31497 HOLDS.
studio-authoring.meta-save-preconditions-and-draft-rows r3 (P1 api) · main · 41442
| clause | verdict | evidence |
|---|---|---|
| acceptance[0] | pass | 5 pins green (rest meta-item-version-token-occ 15, meta-draft-head-package-inheritance 25; runtime meta-save-preconditions-parity 23; objectql sys-metadata-repository-list-drafts 6, protocol-metadata-row-actor 4). |
| acceptance[1] | pass | Draft PUT 200; If-Match with the served version → 200; the stale If-Match → 409 METADATA_CONFLICT with currentVersion; If-None-Match: * while only the draft exists → 200, then 409 once active exists; If-None-Match: "abc" → 400; both headers → 400. The active label is unchanged. |
| acceptance[2] | pass | GET /api/v1/meta/_drafts → the row with type, name, label, packageId null and updatedBy the admin id. |
| acceptance[3] | pass | History → 4 events, every actor the admin id, matching the _drafts row's updatedBy. |
#31490 HOLDS. ?package= unknown → 400; a package filter scopes the list; &type= filters; ?packageId= and ?bogus=1 → 400 VALIDATION_ERROR "Supported parameters: package, type". Probe finding: card P-3.
automation.screen-flow-roundtrip r7 (P1 browser) · s1 · 41410
| clause | verdict | evidence |
|---|---|---|
| acceptance[0] | pass | The row menu → POST /api/v1/actions/showcase_task/showcase_bulk_reassign → 200 paused, screen.nodeId collect. No trigger-route POST. |
| acceptance[1] | pass | GET runs/ID/screen → 200, field new_assignee (text, required). |
| acceptance[2] | pass | Modal "Reassign Task · Step 1 of 1" with a required "New Assignee" input, Cancel and Submit. The DOM has one heading and one #ff-new_assignee. |
| acceptance[3] | pass | Resume with the input → 200; run completed (start, collect, apply). |
| acceptance[4] | pass | The task's assignee is the submitted email. |
| acceptance[5] | pass | Resume with {} inputs → 400 INVALID_SCREEN_INPUT "is required"; the run stays paused; a later valid resume completes. |
| acceptance[6] | pass | Cancel or Escape → no resume POST, run paused; a valid resume then completes. |
| negatives[0]–[1] | held | Not consumed on a bad resume; not cancelled by a dismiss. |
#31491 HOLDS. Non-string values (42, objects, arrays, booleans) → 400 "must be a string"; null and blank → 400 "is required"; the run stays paused and a later valid resume completes. Replayed on a second run.
platform-core.url-overlay-contract r5 (P1 browser) · s1 · 41411
| clause | verdict | evidence |
|---|---|---|
| acceptance[0] | pass | The ?recordId= drawer, ?form=new and edit-from-drawer each push one entry, and Back unwinds them in order. |
| acceptance[1] | pass | Six tab switches: leaving Overview pushes once, every later switch replaces ?related=. Back → bare record, then the list. |
| acceptance[2] | pass | A cold tab on ?related=showcase_contact.account selects Contacts. |
| acceptance[3] | pass | Hooli → Invoices → New gives ?form=new&formObject=showcase_invoice&formLink=account:ID with the account pre-linked; a cold load gives the same. |
| acceptance[4] | pass | Related hrefs carry from=. The two-level record-page-trail breadcrumb links up one level each and survives a reload. |
| acceptance[5] | pass | forms/showcase_task.form?recordId=TASK&recordObject=showcase_project → refused ("This form edits showcase_task, but the link says record … belongs to showcase_project"), with no form and no data read. Case variants are refused the same way. |
| negatives[0]–[3] | held | Covered above. |
Pins: FormPage.recordObject.test.tsx and useUrlOverlay.test.tsx (17 passed). #31498 HOLDS.
studio-authoring.expression-editors r9 (P1 mixed) · s1 · 41412
Fixture: writable package com.qa.s1expr, object s1expr_est (numbers est_hours, a, b; formulas f_total, f_sib), permission set s1expr_rls. Engine side: live MCP validate_expression and the permission publish door.
| clause | verdict | evidence |
|---|---|---|
| acceptance[0] | pass | record.est_hours * 1.1 → "Valid CEL · Result type: Number". record.a + record.b → "Unknown … Wrap operands in double() / int() / string()". MCP agrees. |
| acceptance[1] | pass | Editing f_total only and publishing → f_total returnType number, f_sib still text. |
| acceptance[2] | pass | A misspelt field → "unknown field … did you mean est_hours?"; a bare reference → error with no PUT; a non-pushdownable RLS predicate → editor error, publish 422 rls-predicate-unenforceable. |
| acceptance[3] | pass | previous. and record. list all 7 fields; current_user. and parent. offer none; accepting inserts record.status. |
| acceptance[4] | fail | Non-pushdownable and parse-fault predicates are held with no PUT. The formula-field USING predicate record.f_total > 5 is not held on its first autosave: the client reads "Valid CEL", the draft PUT → 200, and only the re-read shows the server verdict (INVALID_FIELD). Each new guard text PUTs again; edits beside it are then held; publish → 422. → R-2 |
| acceptance[5] | pass | The Test policy dialog's Allowed, Denied and "Non-boolean result: 3" match ExpressionEngine.evaluate. |
| acceptance[6] | pass | The editor tier equals the engine on all 9 probes. |
| acceptance[7] | pass | 6 ui test files, 95 passed. |
| negative[0] | held | No editor-versus-engine divergence. |
| negative[1] | fail | Same root as acceptance[4]: a refused formula-field RLS predicate saves to the draft; publish still refuses it (422), so it never goes live. → R-2 |
| negative[2] | held | No unbound-root members offered; bare references flagged. |
#27482 HOLDS against its ruled acceptance (PR #31549, "Acceptance reading (seat ruling on the step-1 report)").
ux-conventions.disabled-control-reason r6 (P1 browser) · s2 · 41420
Controls V0–V5 and Publish-with-no-drafts were driven on 2 fresh loads each, with a real pointer hover and real Tab presses.
| clause | verdict | evidence |
|---|---|---|
| acceptance[0] | fail | Studio Form tab "Move group up/down" and the Studio New object "Save as draft" with an empty name: hover shows no tooltip and no adjacent text. Every other control shows its reason. → R-3 |
| acceptance[1] | fail | Those three are native disabled with no wrapper, aria-describedby or title, and Tab never reaches them. The All-metadata Save (resource-save) carries "No changes to save" only as title and aria-label on a natively disabled button. Every passing control sits on a tabindex=0 wrapper whose aria-describedby resolves to the reason. → R-3 |
| acceptance[2] | fail | The reason is absent for those controls; every other reason is specific. → R-3 |
| negative[0] | held | No leftover reason on enabled controls. |
| negative[1] | held | For the member, the list toolbar's Reassign… is absent rather than disabled (A-9). |
#31493 HOLDS (import "Save current": tabindex=0 wrapper, aria-describedby "Map at least one column to save a template.", tooltip on hover and focus). #31495 HOLDS (Studio Publish "No drafts pending publish", the same treatment).
ux-conventions.labels-over-machine-names r4 (P1 browser) · s2 · 41421
Fixture: package com.qa.lbl (object "Gadget Thing", permission set "Gadget Editors", app nav "My Gadget Shelf"). A member bound as contributor, interface language 简体中文.
| clause | verdict | evidence |
|---|---|---|
| acceptance[0] | fail | Setup → Audit log, Record column: j7snASPp… → showcase_contributor, the position machine name, although GET /api/v1/meta/position/showcase_contributor has label "Contributor". Every other surface checked renders labels. → R-4 |
| acceptance[1] | fail | The same cell shows the member as a raw user id (sys_user name "QA Member S2"). The same text is the record title in Setup → User Positions (list Title column and record H1). → R-4 |
| acceptance[2] | pass | Every machine name met sits beside its label in smaller mono or muted text. |
| acceptance[3] | pass | No platform-built text carries ADR or issue references. |
| negative[0] | not met | No label-less item was met. |
#31494 HOLDS (PATCH → 404; the toast reads 「这条任务记录已不存在,或者您已无权访问它。」 and the card snaps back).
identity-auth.auth-method-matrix r10 (P1 mixed) · s3 · 41430 (dev; phone and 2FA on), 41431 (stock), 41432 (production serve; phone on, no SMS), 41435 (OS_AUTH_URL issuer)
| clause | verdict | evidence |
|---|---|---|
| acceptance[0] email, phone, OTP, 2FA, magic-link, passkeys | pass | /auth/config features match each boot (phoneNumberOtp false without SMS; all off on stock; emailPassword flips with the setting; magicLink and passkeys absent). |
| acceptance[0] SSO and device variants | blocked(environment) | knownGaps[3]: no device switch and no reachable IdP. |
| acceptance[1] | pass | Email sign-in, session, sign-out, then 401. Sign-up → 403 SELF_REGISTRATION_CLOSED. |
| acceptance[2] | pass | Phone sign-in 200; the session names "Mei Phone (demo)". |
| acceptance[3] | pass | Production boot with no SMS: send-otp → 400 SMS_SERVICE_REQUIRED. Control → 200. |
| acceptance[4] | pass | Phone and OTP controls appear only where advertised. |
| acceptance[5] | pass | All-off boot: every disabled method → 404 (social → PROVIDER_NOT_FOUND); email off → 400 EMAIL_PASSWORD_DISABLED. |
| acceptance[6] | pass | Password → twoFactorRedirect + cookie → session 401 → verify-totp 200 → session holds the user. |
| acceptance[7] | pass | OIDC and two-factor-lockout dogfood pins, 8 passed. |
| acceptance[8] | pass | Both .well-known docs 200 with issuer /api/v1/auth; jwks_uri byte-equal; the issuer follows OS_AUTH_URL. |
| acceptance[9] | pass | Unit pins 25 passed; change-email and delete-user door checks. |
| negatives[0]–[4] | held | No 2xx on a disabled method; loud OTP 400; nothing unmounted advertised. |
#31492 HOLDS (phone-only card, no SSO view, server refuses email sign-in).
integration-system.email-template-render r17 (P2 mixed) · s3 · 41433, 41434 (writable copy), SMTP sink 41439
| clause | verdict | evidence |
|---|---|---|
| acceptance[0] | pass | Delivered at the sink "Subject: ✅ Task done: Ship the QA build", no {{ residue, row sent. |
| acceptance[1] | pass | No title → dead MISSING_VARIABLES; no project → sent with an empty slot. |
| acceptance[2] | pass | An inactive template → dead TEMPLATE_INACTIVE. |
| acceptance[3] | pass | zh-CN and zh recipients get 「✅ 任务完成:…」; ja-JP falls back to English. |
| acceptance[4] | pass | From override, Reply-To and a derived text/plain part on the wire. |
| acceptance[5] | pass | Admin overlay PUT 200, package-less PUT 403; the edit survives a reboot. Declared-source half: an edit on a writable copy plus objectstack compile is served about 4 s later with the PID unchanged. |
| acceptance[6] | pass | content → "Did you mean content → bodyHtml?"; a bad name → refusal; meta door 422 / 400. |
| acceptance[7] | pass (no-provider 501 from the pin only) | Admin send 200; anonymous 401; member 403; forged from, sentBy, headers, organizationId and identityMail → 403 with no row; non-object bodies → 400 with no row. The no-provider 501 is scored from the unit pin (rule 6). |
| negatives[0]–[2] | held |
#31496 HOLDS (array and non-object bodies → 400 INVALID_REQUEST, no row).
Repro rules (every FAIL)
R-1 · access-security · acceptance[4], [6] · stale spec.
- Boot walled as above.
- Create org A and set it active.
- Create member M in A.
- As M: change the password, sign in,
set-active {"organizationId": null}, then sign out. - Read
sys_audit_login the telemetry sibling for M.
The logout row has tenant_id NULL. Mechanism: plugin-auth auth-session-audit.ts logoutEventFor takes the session's activeOrganizationId; plugin-audit auth-event-audit.ts writes tenant_id: event.organizationId ?? null. ADR-0131 D7 makes sys_audit_log deployment-level, "governed by object permission, not by the wall", and its 2026-10-22 note keeps the NULL arm by design. The clause predates D7. The residual question (should such a logout be attributed?) goes to the maintainer through the re-point card.
R-2 · expression-editors · acceptance[4], negative[1] · stale spec.
- In Studio, open permission set
s1expr_rls→ RLS tab → Add policy, objects1expr_est. - Enter the USING predicate
record.f_total > 5.
The draft PUT → 200, and the re-read shows INVALID_FIELD. Later edits are held, and publish → 422. This is #27482's ruled acceptance (PR #31549). Reproduced on 2 loads.
R-3 · disabled-control-reason · acceptance[0]–[2] · product defect. Studio form "Move group up/down", Studio New object "Save as draft" with an empty name, and the All-metadata Save: native disabled, no reason on hover or keyboard focus. Cards below. Reproduced on 2 loads each.
R-4 · labels-over-machine-names · acceptance[0], [1] · product defect. sys_user_position's record title formula record.user_id + ' → ' + record.position is rendered as the title in Setup → User Positions and the audit log. Card below.
§5 dispositions
| fail | disposition |
|---|---|
| R-1 | stale spec (ADR-0131 D7/D9) → re-point card |
| R-2 | stale spec (#27482's ruled acceptance, PR #31549) → re-point card |
| R-3 | product defect → three cards |
| R-4 | product defect → one card |
Off-clause probes (rule 8)
About 25 runner-minutes, after every clause had a verdict, on the same boots.
- main: seed modes and bad input;
_draftsparameters; If-Match edges; no-active-org PATCH and bulk; a path with a space. Found the draft-door wording card. - s1: screen-flow input edges; overlays by URL; formula CHECK; unbound roots. Nothing found.
- s2: Studio controls on read-only and writable packages; import wizard; flow inspector. Found the field "Move up" (folded into the form-designer card) and the User Positions surface.
- s3: login card states; phone versus email with two-factor; forged method calls; send-door body edges. Found the phone two-factor card and the send-door type card.
Checklist-accuracy notes and fixture gaps (not extracted)
- A-1 access-security:
- The
blockedmarker (QA run · priority:P0 · e4e5c6e3 · 2026-08-17 · 7 PASS / 7 PARTIAL / 2 FAIL / 2 BLOCKED #9334) and knownGaps[2] are stale: the showcase declares@objectstack/organizations, and walled boots run from the tree. - Producer (1) remains blocked(fixture).
- The
- A-2 cli: an apply without
--outwrites its report into the working directory; steps[1] could say to pass--out. - A-3 cli-18-prerelease-db recipe: the restored
sys_activity__r20261027shard is dropped by the 14-day rotation once the dump is 14 days old. The recipe needs a knownGaps line. - A-4 auth-method-matrix:
- The phone fixture is the seeded
phone.demo@example.com(+8613800138000). - A production boot needs
objectstack servewith OS_DATABASE_URL, OS_AUTH_SECRET and a 32-byte OS_SECRET_KEY.
- The phone fixture is the seeded
- A-5 email-template-render:
- The declared-source half is drivable from a /tmp copy plus
objectstack compile. - The 403 envelopes are nested
{error:{code,message}}.
- The declared-source half is drivable from a /tmp copy plus
- A-6 url-overlay:
- The "← PARENT link" is now the
record-page-trailbreadcrumb. - Few-children accounts collapse tabs into "+N".
- The "← PARENT link" is now the
- A-7 screen-flow:
automated.refcould addsrc/builtin/screen-string-field-resume.test.ts; the run read has no current-node field. - A-8 expression-editors: the formula textarea id is
field-formula-FIELD; Escape closes the inspector. - A-9 disabled-control-reason:
- variants[5]: the member has no disabled Reassign… (it is absent); variants[4] is now a stock instance.
- Add Import wizard: the mapping-template "Save current" button is disabled with no reason on hover, on focus or to assistive technology #31493 and Studio: the disabled Publish button ("No drafts pending publish") is unreachable by keyboard and shows no hover reason, unlike the read-only Publish state beside it #31495 as regression rows.
- Rule whether decorative disabled FieldStubs count as controls.
- Source-only: import Next with nothing mapped, and
import-run-btnfor upsert with no match fields, would show no reason.
- A-10 labels-over-machine-names: add User Positions and the audit Record column as variants; Node Type options need a writable flow.
- A-11 seed-mode-matrix: a seed draft naming a nonexistent object publishes "published" while
seedApplied.successis false (the declared failures-returned behaviour, recorded only).
Wall clock
- Environment: 06:54–07:06Z, about 12 min. The v17 scaffold ran in parallel, about 3 min.
- Testing: 07:06–07:45Z, about 39 min, four runners in parallel. About 30 runner-minutes setup to 86 testing (about 1 : 3).
Activity
objectstack-fleet commentedon Nov 10, 2026
qa#2 · os-bob · session_012dn2Acf5wtyrf2yKWCThBj
Close-out · 2026-11-10T08:03Z
Every fail has its §5 disposition: R-1 and R-2 are stale specs (re-point cards #32111 and #32112), and R-3 and R-4 are product defects (#32103 to #32106). The two partials name their gaps in the record: SSO and device variants are blocked(environment) by knownGaps[3], and the no-provider 501 rests on the unit pin. The checklist-accuracy notes A-1 to A-11 stay in the record body. Retests follow through
retest-owed.mjswhen the fixes land. The record closes now.docs(qa): expression-editors reads the formula-field RLS hold per obj…
docs(qa): re-point no-active-org-session-semantics audit clause to AD…