Skip to content

QA run · tier2c:browser-2 (18/18) · e4e5c6e3 · 2026-08-18 · 10 PASS / 4 PARTIAL / 4 FAIL #9467

Description

@os-zhuang

Tier-2C — the final round of the platform-checklist QA wave (#9296). Scope: the last 18 unpinned P0/P1 items on the browser / mixed surfaces. All 18 consulted; every unpinned P0/P1 item in the ledger now has a verdict.

Environment fingerprint

framework sha (subject)e4e5c6e3c608b1b807c83a0d5b734f213eb1a1dd — unmoved throughout
.objectui-sha665661ab093263f39f2e660a295ea615dbcee35a; check:console-sha ✓ stamp = pin
spec (checklist)re-synced from origin/main at round start — check-platform-checklist: OK — 15 areas, 190 items (190 active); 30 kinds mapped, 0 waived
treeobjectstack-qa-wave, detached at the pin. objectui:build / objectui:refresh never run
ports / DBs3191 file:/tmp/qa-wave/t2c-A.db (main) · 3192 t2c-B.db (pinyin-OFF) · 3193 t2c-C.db (fresh-boot repro) · 3194/3195 fresh scaffold. Max 2 concurrent
seed--seed-admin; personas minted with POST /auth/sign-up/email (bravo@qa.example, charlie@qa.example)
browserplaywright-core 1.62.1 via executablePath=/opt/pw-browsers/chromium (the RUNNER-prescribed symlink)
date2026-08-18
exit stateall instances stopped; every probe datasource / dashboard / flow / overlay removed (sys_metadata runtime rows: none); git status = 11 docs/qa/** paths, 0 commits, HEAD still e4e5c6e3

Scope and per-item revision

#itemrevprisurfaceverdict
1identity-auth.session-list-revoke1P1mixedPASS
2identity-auth.api-key-ui-lifecycle1P1mixedPASS
3identity-auth.self-service-profile-password1P1mixedPARTIAL
4identity-auth.org-membership-team-management2P1mixedPASS
5integration-system.job-scheduled-run2P1mixedPARTIAL
6integration-system.notify-inbox-delivery2P1mixedPASS
7integration-system.datasource-credential-refusal-matrix1P0mixedFAIL
8dashboards.strict-widget-rejects-stray-keys2P1mixedFAIL
9dashboards.chart-type-matrix3P1browserPARTIAL
10dashboards.global-filters-rescope2P1browserFAIL
11i18n.notification-localized-and-clears3P1browserFAIL
12i18n.surface-matrix3P1browserPARTIAL
13i18n.strict-translation-key-rejection1P1mixedPASS
14search.pinyin-flag-both-sides2P1mixedPASS
15search.console-global-search3P1mixedPASS
16automation.flow-run-step-nesting2P1mixedPASS
17automation.flow-error-handling2P1mixedPASS
18cli.scaffold-console-first-paint1P1mixedPASS

10 PASS / 4 PARTIAL / 4 FAIL.


Per-clause verdicts

1 · identity-auth.session-list-revoke rev 1 — PASS

clauseverdictoracle evidence (text)
c0 owner-scoped listpassA(bravo) GET /auth/list-sessions + GET /data/sys_session → 3 rows, all user_id=fUn5OO…; B(charlie) → 2 rows, all user_id=pTunKp…. B's row ids GVz0GATJ…, N5G8bwKM… absent from A's read
c1 Account sessions surfacepassscreenshot: /apps/com.objectstack.account/sys_session/view/mine as bravo renders "1 record" with IP Address / Active Organization / Created At / Expires At columns — count matches bravo's post-revoke API list (exactly 1)
c2 revoke-session by tokenpassPOST /auth/revoke-session {token:<device-2>}{"status":true}; device-2 GET /data/showcase_task401; device-1 → 200
c3 revoke-other-sessionspasscalled from device-1 → list-sessions returns exactly 1 row (device-1's own token); a previously-other token → 401; caller → 200
c4 admin all-users surfacepassscreenshot: Setup → Sessions all_sessions shows a User column with cross-user rows (Dev Admin / QA bravo / QA Charlie) and a Revoked view; API: admin read = 7 rows across 3 users, 2 with revoked_at set and revoke_reason="user_revoked"
c5 read-only over data APIpassPATCH and DELETE /data/sys_session/{id}OBJECT_API_METHOD_NOT_ALLOWED, allowed:["get","list","aggregate","export"] — as bravo and as admin

2 · identity-auth.api-key-ui-lifecycle rev 1 — PASS

clauseverdictoracle evidence
c0 show-oncepassPOST /api/v1/keys → 201 {id, name, prefix:"osk_0LHT6VwR", key:"osk_0LHT6VwRXOKC…"}; follow-up GET /data/sys_api_key/{id} returns prefix only, grep for the raw secret = 0
c1 authenticates as ownerpassx-api-key:<secret> on GET /data/showcase_task → 200, 10 rows — identical to the owner's session read (10) and to admin's (10); no superuser widening
c2 revoke immediatepassPATCH /data/sys_api_key/{id} {revoked:true} → 200; next x-api-key call → 401
c3 restore reversiblepass{revoked:false} → 200; next x-api-key call → 200
c4 mine-view scopingpasscharlie's sys_api_key read = 0 rows, key id absent; admin all_keys read contains it
c5 dialog + list renderpassscreenshot of Setup → Connect an Agent after a ref-targeted "Create key" (native setter + input/change on the name field): panel reads "For CI, scripts, and agents without a browser. The key acts as you — treat it like a password. It is shown ONCE." and "Key "qa-console-mint" created — copy it now, it will not be shown again:" with the copyable secret and the x-api-key: header form. Re-opened all_keys list shows prefix-only rows

3 · identity-auth.self-service-profile-password rev 1 — PARTIAL

clauseverdictoracle evidence
c0 name persistspassPOST /auth/update-user {name}{"status":true}; GET /auth/get-session"name":"Dev Admin QA2C"; after a hard reload the profile card still renders "Dev Admin QA2C"
c1 avatar is a real storage writepassnetwork trace: POST /storage/upload/presigned → 200 → PUT /storage/_local/raw/<token> → 200 → POST /storage/upload/complete → 200 {fileId:"efa8853a-…"}POST /auth/update-user → 200; get-session.image = "/api/v1/storage/files/efa8853a-…"; rendered <img src> equals it. Not a data: URL
c2 avatar removepassprofile-avatar-remove-btnPOST /auth/update-user → 200; get-session.image = null; zero <img> on the card, initials "DA" fallback returns
c3 password rotationpasschange-password (correct current) → 200; sign-in with new → 200; sign-in with old → 401
c4 wrong current refused server-sidepasschange-password with a wrong current → 400{"code":"INVALID_PASSWORD"}; the old password still signs in (200) → credential unchanged
c5 passwordless set-initialblocked(fixture)stock showcase ships no configured IdP, so no hasLocalPassword=false user exists — exactly the item's own knownGap

Also observed (c6-adjacent, no clause): the profile card renders "Email cannot be changed." with the email input disabled.

4 · identity-auth.org-membership-team-management rev 2 — PASS

clauseverdictoracle evidence
c0 {current_org_id} resolvespassthe Setup nav emits /_console/apps/com.objectstack.setup/sys_organization/record/org_msy0avxsf5eiqdj6 — the record page, not the list; screenshot shows Members (3) / Invitations / Teams (1) tabs. Cross-checked: get-active-member and get-full-organization both return org_msy0avxsf5eiqdj6
c1 role writes and bitespassupdate-member-role bravo→admin → membership read shows admin; bravo's organization/update200. Demote to member → same call → 403YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_ORGANIZATION
c2 closed vocabularypassrole guest400{"code":"ROLE_NOT_FOUND","message":"ROLE_NOT_FOUND: guest"}; stack-declared position contributor → same; delegated_adminaccepted. Follow-up list-members shows no stray row
c3 rename persistspassorganization/update {name:"QA Renamed Org"} → 200; get-full-organization.name = "QA Renamed Org"; the org record page header follows
c4 remove shrinks accesspasscharlie before: get-full-organization 200 / list-members 200 / get-active-member 200. After remove-member: 403 / 400 / 400 with {"code":"NO_ACTIVE_ORGANIZATION"}
c5 team join rowspasscreate-team → team V1ZCv5iv…; two add-team-member calls → GET /data/sys_team_member = exactly 2 rows, pairs (V1ZCv5iv…, fUn5OO…) and (V1ZCv5iv…, pTunKp…)
c6 gate both wayspassas a non-admin member, all five forged calls refused server-side — update-member-role403, remove-member403, update403, create-team403, add-team-member403 — and org name + all three roles unchanged after. UI side: the same member loading the org record page gets "App not available" and zero management affordances

5 · integration-system.job-scheduled-run rev 2 — PARTIAL

clauseverdictoracle evidence
c0 all three schedule variants fireblocked(fixture)intervalflow-schedule:showcase_scheduled_digestrun_count=75, last_status=success, with its own sys_job_run rows and real side effects (75 sys_inbox_message digest rows). onceflow-wait:run_e97bd614…:holdrun_count=1. cronshowcase_health_sweep is registered with schedule_type=cron, schedule_expression="0 1 * * *", timezone=UTC but run_count=0 — its next fire is outside the session window. The item prescribes near-term scratch jobs in a writable package; the runtime job door is closed (c5), so that needs source + redeploy
c1 durable name-keyed truthpasssys_job row {name:"showcase_health_sweep", id:"job_58a9d9a1-…"} — keyed by name, adapter-minted row id, schedule persisted (schedule_type/schedule_expression/timezone). Counters move: run_count 75 / 15, last_run_at, last_status=success. sys_job_run rows carry {job_name, status, started_at, completed_at, duration_ms, attempt, trigger}
c2 retry ladderblocked(fixture)needs a deliberately-throwing scratch job; runtime authoring closed
c3 timeout statusblocked(fixture)same
c4 two loud skip doorsblocked(fixture)neither skip line appears in the boot log (grep -ic "job handler not found|skipping" = 0) because the stock showcase has no disabled job and no handler-missing job at this pin — the #4774/#4888 regression is fixed. Observing the lines needs authored specimens
c5 closed doors refuse loudlypassruntime create: PUT /meta/job/qa_runtime_job[not_creatable] Metadata type 'job' is code-only: the metadata-type registry declares allowRuntimeCreate=false and allowOrgOverride=false … Declare it in source (**/*.job.ts) and redeploy. An operator may set OS_METADATA_WRITABLE=job …. Parse door (JobSchema from @objectstack/spec/system): id"job.id was removed in @objectstack/spec 17.0.0 (#4667, ADR-0049) … name IS the job's identity everywhere"; stray wibble → named; aliases → cronschedule, intervalschedule, fnhandler, each with a did-you-mean. Valid control accepted

6 · integration-system.notify-inbox-delivery rev 2 — PASS

clauseverdictoracle evidence
c0 deliverypassafter reassigning showcase_task/5BiNGEhsVjoH0UeB to bravo, bravo's GET /notifications{id:"kq18I3z8jXVFhSnD", type:"task.assigned", title:"New task assigned: Warehouse schema", body:"You have been assigned \"Warehouse schema\".", actionUrl:"/showcase_task/5BiNGEhsVjoH0UeB"}
c1 recipient-scopedpasscharlie's authenticated listing → {"notifications":[],"unreadCount":0}
c2 mark-read persisted per receiptpassPOST /notifications/read {ids:[…]}{readCount:1}; ?read=false relist → empty; sys_notification_receipt as bravo → {notification_id:"kq18…", channel:"inbox", state:"read"} (was delivered)
c3 both sides of the gatepassanonymous GET /notifications401{"code":"UNAUTHENTICATED"}. (The 501 capability-unavailable half was not exercised — the clause makes that side optional/citable from a minimal boot.)
c4 unimplemented channel dead-letterspassscratch flow with channels:['push'] triggered → zerosys_inbox_message rows for the recipient (bravo still holds only the task.assigned row) and sys_notification_delivery gained exactly one row channel="push", state="dead" against 13 inbox/success rows. No fabricated inbox row
c5 backing rows reflect surfacepassas bravo: sys_inbox_message 1 row {notification_id:"kq18…", topic:"task.assigned"}, sys_notification_receipt 1 row keyed (notification_id, user_id, channel:"inbox"). RLS-scoped — admin's read of the same tables shows only admin's 11 rows

7 · integration-system.datasource-credential-refusal-matrix rev 1 — FAIL (P0)

clauseverdictoracle evidence
c0 every inline door refusespass6/6 doors, each refusing with the offending key/position named and external.credentialsRef prescribed: config-key (config.password"password is a credential and is not accepted inline in driver config (#7990)"), URL userinfo ("this url embeds a password in its userinfo … (#8082)"), URL query param ("carries ?password= in its query string … (#8337)"), mongo driver-options (config.options.auth.password"not accepted in the driver-options passthrough (#9040)"), placeholder (${DB_PASSWORD} → same config-key refusal), connector inline authentication (PUT /meta/connector"must not inline secrets via authentication … drop authentication (or set { type: 'none' })")
c1 credentialsRef path workspassthe external/credentialsRef-shaped datasource published {success:true} with status:"ok" on the connect probe
c2 both read doors redactpassfull-body grep of GET /api/v1/datasources, GET /api/v1/datasources/:name and GET /api/v1/meta/datasource for all 10 planted cleartext values → 0 hits on both doors. Shapes agree (both drop the key rather than masking)
c3 presence-preservingpasscredential SET → hasSecret:false, redactedConfigKeys:["encryptionKey"]; credential UNSET → redactedConfigKeys:[]. Distinguishable
c4 legacy alias spellingspassplanted passwd / pwd / authtoken through the metadata door (structural validation, as the knownGap predicts) and passwd through the datasource-admin door on an unknown driver. Metadata door serves config:{host:…} only; admin door reports redactedConfigKeys:["passwd"]. All three planted values: 0 hits on both doors
c5 unknown driver redacted by namepassdriver cockroachdb (no shipped contract) with config.password → admin door config:{host:"h.qa.example"}, redactedConfigKeys:["password"]; metadata door config:{host:…}. Value absent from both
c6 echoed-mask write guardpassread the datasource, PATCH the response back unmodified → 200; post-round-trip read still reports redactedConfigKeys:["encryptionKey"] and the stored sys_metadata row still contains the credential (grep count 1). Live connect probe unavailable — @objectstack/driver-turso is not installed — so the "still works" half used the stored-credential oracle
c7 redaction is not a privilege artifactfailaccess-control finding — detail withheld pending maintainer; held by the runner. Item integration-system.datasource-credential-refusal-matrix, clause c7. Already filed as #9391; independently re-confirmed this round. No endpoint, method, payload or reproduction published here.

Note on encryptionKey (turso): write-accepted + read-redacted on both doors — the correct expectation per #8081 scope item 4, not a write-refusal failure. Separately observed and not a clause failure: the still-writable encryptionKey is stored in cleartext in sys_metadata, which the ordinary data API serves — but that path is admin-gated (PERMISSION_DENIED for a member, UNAUTHENTICATED for anonymous) and is exactly the "stays stored" behaviour datasource-credential-redaction.ts documents.

8 · dashboards.strict-widget-rejects-stray-keys rev 2 — FAIL

clauseverdictoracle evidence
c0 all 11 legacy keys rejected, key namedpassobject, categoryField, categoryGranularity, valueField, aggregate, aggregation, rowField, columnField, xAxisField, yAxisFields, measures11/11 rejected, each error containing the literal key name
c1 error prescribes the ADR-0021 shapepass"The pre-ADR-0021 inline analytics shape (object + categoryField + valueField + aggregate, pivot rowField/columnField) was removed — bind a dataset and select dimensions + values by name. Renderer-only settings belong under options."
c2 hallucinated keypasschartFlavour"Unrecognized key(s) on this dashboard widget: chartFlavour." — key echoed, no did-you-mean offered (honestly absent, no near match). Quarantined component/data and drill near-keys drillDown/drilldown/drill also rejected with their own prescriptions
c3 wrong-layer keys inside chartConfigpasschartConfig.dataset"dataset is not part of the chart config — it is the dashboard widget's own key (ADR-0021), a sibling of chartConfig, not a key inside it."; chartConfig.drillDown"it is a REACT-TIER prop, written beside objectName/aggregate: <ObjectChart objectName=… drillDown={…} />"; aggregate and objectName likewise named
c4 rejected draft not persistedpassafter every rejected PUT …?mode=draft, GET /meta/dashboard/qa_stray_key_probe returns a bare lock descriptor with no widgets — nothing carrying the stray key
c5 corrected widget rendersfailthe corrected widget (dataset: showcase_task_metrics, dimensions:['status'], values:['task_count']) published {success:true, version:"sha256:319d0221…", seq:3} and then renders "Dashboard Not Found". See R-1 below

9 · dashboards.chart-type-matrix rev 3 — PARTIAL (matrix: 16 of 20 variants verified)

Ground truth first (/api/v1/data): 11 tasks · 5 distinct statuses · 4 distinct priorities · 2 month buckets · 5 projects across 3 accounts.

clauseverdictoracle evidence
c0 each of 20 variants rendersblocked(product-bug → R-1)16 verified per-variant in the Chart Gallery (screenshot first, DOM after). 4 unreachable: kpi/gauge/solid-gauge/bullet need a scratch dashboard, which R-1 makes unrenderable
c1 mark counts match bucketspassbar Tasks by Status5recharts-rectangle paths = 5 statuses · column Tasks by Priority4 = 4 priorities · horizontal-bar Hours by Status 5 bars (screenshot) · line Tasks Created (monthly)recharts-curve over 2 month buckets · area idem (curve + fill) · pie Status Split5recharts-sector · donut Priority Split4 sectors · funnel Status Funnel5recharts-trapezoid · radar Priority Radarrecharts-polar-grid + recharts-polygon over 4 priorities
c2 combo = mixed marks, dual axespassTask Count vs Avg Progress carries bothrecharts-rectangle and recharts-curve in one SVG, with two y-axis tick sets rendered (0 2 4 6 8 left, 0 25 50 75 100 right)
c3 distinctive geometrypasstreemap Hours Treemap = 6 nested <rect> (no cartesian grid); sankey Status Flow (Sankey) = recharts-sankey-link with 11 link paths; scatter Estimate vs Progress = its own cartesian point plot. None falls back to a bar
c4 four single-value synonymsblocked(product-bug → R-1)qa_single_value_gallery (kpi/gauge/solid-gauge/bullet) published {success:true, seq:4} → "Dashboard Not Found"
c5 pivot cross-tabpassTasks by Status × Priority renders a real table: headers Status | Low | Medium | High | Urgent | Total, 5 status rows + Total. 3-cell spot check vs the API: Done×Medium=3 (API done=3), Backlog×Low=1, grand Total=11; the Total row 1,5,3,2 equals the API priority buckets {low:1, medium:5, high:3, urgent:2} exactly
c6 coverage ratchetpassexamples/app-showcase/test/coverage.test.ts1 file, 13 tests passed
c7 compareTo rendersblocked(product-bug → R-1)qa_compareto_probe published {success:true, seq:5} → "Dashboard Not Found"
c8 converged compareTo pinned at the parsepasspackages/spec/src/ui/dashboard-compareto.test.ts1 file, 17 tests passed. Confirmed live through the dashboard metadata root too: {kind:'previousPeriod'} accepted; the bare string → "dashboard.widgets[].compareTo: 'previousPeriod' (the bare string form) was removed in @objectstack/spec 17.0.0 (#5011)…"; {offset:'7d'}"dashboard.widgets[].compareTo.offset was removed in @objectstack/spec 17.0.0 (#5011, ADR-0049 …)"

Enum re-read as the item's step 0 requires: ChartTypeSchema has exactly 20 members — the variants list is current.

10 · dashboards.global-filters-rescope rev 2 — FAIL

clauseverdictoracle evidence (network trace)
c0 region re-scopes per widget's own fieldpassselecting EMEA re-issues 9POST /analytics/dataset/query; invoice widgets carry {"region":"emea"}, account widgets {"sales_region":"emea"} — per-widget field mapping in one trace
c1 dateRange through each object's own date fieldpassinvoice widgets {"issued_on":{"$gte":"2026-05-20","$lte":"2026-08-18"}}; account widgets {"signed_on":{…}}
c2 opted-out KPI is realpass"Accounts (all time)" issues {"measures":["account_count"]} with noruntimeFilter on load and is absent from all re-query sets; its value stays 14 across region and reset changes
c3 displayed aggregate = server truthpassunder EMEA + last-90-days the Invoices tile shows 1; the identical direct query returns {"invoice_count":1} with SELECT COUNT(*) … WHERE ((issued_on >= $1 AND issued_on < $2) AND region = $3)
c4 prospects excluded by date scopepass14 accounts, 3 with signed_on = null; a wide signed_on window returns 11 = 14 − 3; unfiltered returns 14
c5 composition on Delivery Operationspasswith global task_status=in_progress, the "Awaiting Review" tile's query is $and[{status:"in_review"}, $and[{created_at:…},{status:"in_progress"}]] and the tile goes 2 → 0; the direct aggregate with both filters returns {"task_count":0} and with the tile filter alone {"task_count":2}
c6 Studio inspector authors filterBindingsfailsee R-1 — the authored binding does not persist and does not drive the widget
c7 Reset is dirty-gatedpasspristine bar exposes only the two filter buttons (no Reset); after selecting APAC the bar reads Last 90 days | APAC | Reset; clicking Reset restores Last 90 days | Region: All, fires 9 re-queries in the default (region-free) shape, and the Invoices tile returns to 7 while the opted-out tile stays 14
c8 {value,label} options + server GROUP BYblocked(fixture){value,label} half proven: the trigger renders the LABEL "EMEA" while the committed value on the wire is "emea". The server-GROUP-BY half has no fixture — both revenue-pulse.dashboard.ts and ops-dashboard.dashboard.ts declare static inline options arrays, and opening the select fired 0 analytics requests. No dataset-sourced option list exists on stock showcase

11 · i18n.notification-localized-and-clears rev 3 — FAIL

clauseverdictoracle evidence
c0 title is the flow-authored literalpassflow showcase_task_assigned_notifycfg.title = 'New task assigned: {record.title}'; on a zh-CN recipient session the bell entry reads exactly New task assigned: Warehouse schema, character for character. Session locale confirmed zh by the surrounding chrome (消息中心 / 全部标记为已读 / 未读 / 38秒钟前 / 查看全部通知). No raw {record.title}
c1 deep-link target renders zh-CNfailsee R-3 — the click-through never reaches the record
c2 mark-as-read clearspassref-targeted click inside the bell popover fired POST /api/v1/notifications/read {"ids":["enFXX2Z6ZecLsZtH"]}200{"readCount":1}
c3 cleared state is authoritativepassafter a full page reload the re-opened panel reads 已读完所有通知 with an empty 未读 tab — the server persisted it
c4 title is recipient-INDEPENDENTpassthe same event notified an EN-session recipient (charlie) with the identical string New task assigned: Warehouse schema — no per-recipient divergence, which is the correct (retired-capability) contract

12 · i18n.surface-matrix rev 3 — PARTIAL

Expectations grounded first in GET /api/v1/i18n/translations/zh-CN (134,700 bytes; top-level groups objects, globalActions, dashboards, pages, messages, apps, metadataForms, settingsCommon, settings).

clauseverdictoracle evidence
c0 every variant renders its known-translated keyblocked(fixture)12 of 13 variants verified — nav (setup/account app nav fully zh: 总览/软件包/API 密钥/审批中心/会话/数据源/连接智能体), list headers (标题/项目/负责人/状态/优先级/截止日期/进度/操作), form labels + placeholders (选择…/请选择), select options (待规划/待办/进行中/评审中/已完成; 低/中/高/紧急), view emptyState (see CF-1), _sections (task form 任务信息; contact form 这是谁), dashboard (widget title 任务数与平均进度), page (姓名/邮箱/公司/留言/提交咨询), Settings namespace (本地化 · 区域/格式/财务 · 默认时区/默认语言/日期格式), Studio metadataForms (数据/自动化/界面/权限/发布/保存草稿/记录/表单/验证/钩子), sys_ object (用户 · 身份 · 名称/邮箱/手机号码), feed verb (c10). 1 blocked: action dialog (c5)
c1 expectations from server truthpassthe translation read was captured first and every per-variant expectation cites its key path
c2 fallback is honestpassuntranslated declared strings render their source label (e.g. showcase_task's EN description; the showcase app's own nav labels — apps.showcase_app is legitimately absent from the bundle). A regex scan for dotted key paths across every driven zh surface found zero raw keys
c3 option labels localize everywhereblocked(environment)proven in 2 of 3 places — list cells (评审中/进行中/高/紧急) and form selects (待规划…已完成; 低/中/高/紧急). The list filter panel did not open under the driver, so the third place is unverified
c4 fully-covered list never mixes localespassthe showcase_task list header row is 100% zh-CN: 标题 | 项目 | 负责人 | 状态 | 优先级 | 截止日期 | 进度 | 操作
c5 action dialog copyblocked(fixture)the only two translated _actions (showcase_recalc_estimate → 重算工时, showcase_recalc_selection → 重算所选) are not reachable from the showcase_task list row menu (编辑/删除/Mark Done/Reassign…/Quick View) or the record page (Mark Done/Log Time/Archive); the actions that are reachable carry no _actions entry and so legitimately render EN
c6 /i18n/locales envelopepass{"success":true,"data":{"locales":[{"code":"en","label":"en","isDefault":true},{"code":"zh-CN","label":"zh-CN","isDefault":false}]}} — the declared envelope, exactly the configured set, no superset, not a bare array
c7 /i18n/translations/zh-CN vocabularypassnon-empty; all 9 top-level keys sit inside the declared translationDataShape vocabulary — no fourth dialect
c8 /labels/:object/:locale agrees with the UIpassthe labels response {title:标题, project:项目, assignee:负责人, status:状态(+options), priority:优先级(+options), due_date:截止日期, progress:进度, …} matches the rendered zh list-header row field-for-field
c9 unknown locale degrades honestlypass/translations/zz-ZZ200{"locale":"zz-ZZ","translations":{}}; /labels/showcase_task/zz-ZZ200{"object":"showcase_task","locale":"zz-ZZ","labels":{}}. Empty maps, no 500, no dotted-key dump
c10 feed/audit verb localizespassand it nearly went the other way — see CF-2. With the tenant localization setting locale=zh-CN, the next write produced the activity row 更新了 任务 "Warehouse schema" — the messages.activityUpdated template resolved, object label included

13 · i18n.strict-translation-key-rejection rev 1 — PASS

clauseverdictoracle evidence
c0 every variant rejected at the item doorpass9 variants via PUT /meta/translation/<name>, each alongside a valid objects.showcase_task.label. All rejected with the surface named ("Unrecognized key(s) on this translation") and the key echoed: wibble, o, app, nav, dashboard, errors, notifications, validationMessages, _meta
c1 same variants rejected at the bundle doorpassscratch script against @objectstack/spec: defineTranslationBundle (root export) and defineTranslation (via the /system subpath — see CF-3) each throw for all 11 variants with equivalent error content; both valid controls accepted
c2 did-you-mean on near missespassfield helpText"Unrecognized key(s) on this field translation: helpText. Did you mean helpTexthelp?"; dashboard widget label"Unrecognized key(s) on this widget translation: label. Did you mean labeltitle?"
c3 prescriptions on legacy + retired keyspasso"use 'objects.<object_name>'"; app"use 'apps.<app_name>'"; nav"use 'apps.<app_name>.navigation.<node_id>.label'"; dashboard"use 'dashboards.<dashboard_name>' (plural)"; errors"has no replacement … Author the message on the rule itself (object.validations[].message)"; notifications"notifications have no translation group, omit them"; validationMessages"removed in @objectstack/spec 17.0.0 (#4667, ADR-0049) … author the message on the rule itself"; _meta"use the top-level 'locale' field"
c4 failed save persists nothingpassafter every rejection GET /meta/translation/qa_t2c_trRESOURCE_NOT_FOUND — the valid sibling key was not half-saved
c5 corrected document saves and resolvespasscorrected save → {success:true, seq:1, state:'active'}, and it resolves: /i18n/translations/zh-CN then returned the authored objects.showcase_task.label = 任务(QA已改) and fields.title.help = 帮助; /i18n/labels/showcase_task/zh-CN served the new help too. Deleted afterwards, and the shipped values (任务 / 标题) came back

14 · search.pinyin-flag-both-sides rev 2 — PASS

clauseverdictoracle evidence
c0 flag ON, every latin variant hitspassper-variant on /data/<obj>/query: zhangwei→张伟 (1), zw→张伟 (1), ZhangWei→张伟 (1), →张伟 (1), huaning→华宁科技 (1), hnkj→华宁科技 (1), 华宁→华宁科技 (1)
c1 additive, display-field scopedpassclean contrast: keji (科技's pinyin) returns 0 on showcase_contact — whose company field holds 华宁科技 — and 1 on showcase_account, where 华宁科技 IS the display name. Control: wangfang → 王芳 (1)
c2 flag OFF splits correctlypassafter OS_SEARCH_PINYIN_ENABLED=false restart: initials zw0, hnkj0, wf0 (companion gone); → 1 and 华宁 → 1 (source column). Full-pinyin zhangwei/huaning still return 1 — a source-column hit, evidenced by the exact stored values email=zhangwei@huaning.example and website=https://huaning.example / billing_email=billing@huaning.example. See CF-4
c3 OFF is inert end-to-endpassplugin roster diff: 48 loaded (ON) vs 47 (OFF); full-text grep of the 6.1 MB OFF debug boot log for "pinyin" → 0 occurrences
c4 seeded rows recallable on first bootpassevery ON-side query above succeeded on a fresh boot with no manual rebuild

15 · search.console-global-search rev 3 — PASS

clauseverdictoracle evidence
c0 one idempotent open pathpassthe header Search button (action:command-palette:open) and Ctrl+K each yield the overlay:command-palette overlay with ?palette=1 on the URL; calling open while open is a no-op
c1 palette calls the platform endpointpasswire trace GET /api/v1/search?q=INV-1003&limit=24&objects=… → 200 (searchAll, not a per-object fanout)
c2 CJK/pinyin recall reaches grouped hitspasszhangweiGET /api/v1/search?q=zhangwei… → 200 with the 张伟 hit; the palette renders it under the "Contact" object heading (screenshot before DOM)
c3 Enter navigatespassref-targeted select → /_console/apps/com.example.showcase/showcase_contact/record/XiTyx8y4Db0CtnoA, and the record page renders 张伟 / 华宁科技 / zhangwei@huaning.example
c4 empty input shows recentspasswith a blank query the overlay renders the "Recently viewed" group and 0/api/v1/search requests fire
c5 RLS parity, both personaspassrestricted member (signed-up, bound to the contributor position): GET /api/v1/search?q=INV-1003&objects=showcase_invoice200{"hits":[],"totalObjects":1,"totalHits":0} — searched, no hit, no leaked count; the identical admin query returns INV-1003. Positive control that the member's read is not a blanket refusal: the same member's /data/showcase_invoice returns 200 with 0 rows (all 12 seeded invoices are owned by ada/linus/grace)
c6 /search page, same path + empty statepass/apps/com.example.showcase/search?q=INV-1003 fires GET /api/v1/search?q=…&limit=60 → 200 and renders 1 result for "INV-1003" grouped under Invoice; a no-hit query renders the designed empty state 0 results for "zzzznohitzzz" / No results found / Try adjusting your search terms — no error envelope, no error toast

A distinct defect surfaced while running this item and fails none of its clauses (the console always scopes its query): the unscoped documented route answers 400 — see R-4.

16 · automation.flow-run-step-nesting rev 2 — PASS

clauseverdictoracle evidence
c0 3-item run completes, body ran 3×passPOST /automation/showcase_batch_reminders/trigger with 3 tasks → run run_b6640d78…status=completed; steps with nodeId=send_reminder = 3
c1 region tagspassthe three body steps each carry {parentNodeId:"loop_tasks", regionKind:"loop-body"} with iteration0, 1, 2 — no two share an iteration; start and loop_tasks carry noparentNodeId
c2 designer Runs panel renders a nested treepassthe Studio flow-designer Runs panel (/studio/com.example.showcase/automations?surface=flow:showcase_batch_reminders, Runs toggle → expand the 17 ms run) — not the developer Flow Runs page. Measured indentation: start/loop_tasks at x=950, the three send_reminder (NOTIFY) rows at x=974 each prefixed by a marker at x=912. Screenshot taken before the DOM read
c3 tree agrees with the APIpass5 rendered steps = 5 API steps; the 3 children grouped under loop_tasks = the 3 API steps tagged parentNodeId=loop_tasks, iterations 0/1/2
c4 empty-collection contrastpasstrigger with tasks: [] → run run_45d2a21f…status=completed, steps [start, loop_tasks(success)], zero steps with parentNodeId=loop_tasks

17 · automation.flow-error-handling rev 2 — PASS

clauseverdictoracle evidence
c0 handled run completes, container reads successpassrun run_ebd1af1b…status=completed, durationMs=7156; the try_catch step guarded_push carries status="success" (by design per #7546 — not a finding)
c1 failed try attempts recorded, ordered before catchpasssteps 2–5 all {nodeId:"push", nodeType:"http", status:"failure", parentNodeId:"guarded_push", regionKind:"try", error:{code:"NODE_FAILURE", message:"http: fetch failed"}}; the catch step {nodeId:"record_failure", regionKind:"catch", status:"success"} sits at index 6, after all four
c2 retry ladder countablepassretryAttempt values 0, 1, 2, 3 — exactly maxRetries(3)+1, each status=failure. Duration cross-check: durationMs=7156 ≥ the 1 s first backoff, and 1+2+4 = 7 s matches backoffMs:1000, backoffMultiplier:2
c3 $error binds into datapassthe task after the run: sync_status="failed", sync_error="Node 'push' failed: http: fetch failed"
c4 unhandled run fails loudlypassthe unwrapped probe flow → status="failed" with the run-level error: "Node 'push' failed: http: fetch failed" populated; steps [start(success), push(failure)]
c5 both surface in the designer Runs panelpassthe Runs panel renders the region-labelled tree: SUCCESS start START 0ms / SUCCESS guarded_pu… TRY_CATCH 7.1s / ↳ TRY → 4× FAILURE p… HTTP …ms http: fetc… / ↳ CATCHSUCCESS r UPDATE_RECORD 101ms. The unhandled probe's run row shows Failed
c6 unhandled failure is loud in the server logpassERROR-level lines for trigger-fired unhandled failures, e.g. "ERROR Trigger-fired run of flow 'showcase_declarative_connector_ping' failed (trigger 'record_change') — no caller holds this result and nothing retries the run…" and the same for showcase_task_completed_rest_ping — structurally identical (an outbound call failing outside try_catch). See CF-5 on why an api-triggered probe legitimately produces no such line

18 · cli.scaffold-console-first-paint rev 1 — PASS

clauseverdictoracle evidence
c0 npx os validate on a pristine scaffoldpassnpx -y create-objectstack@latest qa-console-first-paint✓ Environment created!; npm install → 438 packages; npx os validateexit 0, ✓ Validation passed (79ms), Data: 1 Objects 2 Fields · Runtime: 3 plugins
c1 console PAINTS under the documented commandpassnpx os dev --ui/_console/200 and the shell renders: login page reads "Development instance / Sign in with the seeded dev admin: admin@objectos.ai / admin123"; after sign-in the authed home renders ("Working late, Dev Admin", Build an app, Your apps 1, Setup). Not a 404, not a white screen, not a stack trace
c2 the scaffold's own dev script agreespasspackage.json scripts.dev = "objectstack dev" (no --ui). Bare npm run dev/_console/200, Plugins: 35 loaded, banner advertising ➜ Console: http://localhost:3194/_console/. npx os dev --ui200, Plugins: 35 loaded, identical painted shell. The two invocations agree
c3 the newcomer can complete the visual checkpassan identity does exist out of the box — the seeded dev admin, printed both in the boot banner and on the login page (see CF-6). Driven end to end: the starter Note object list renders its designed empty state ("Nothing here yet / Create your first record to get started"), the create form renders (Title* / Body), Save lands the record, and the list then shows 1 record with working nav / filter / group / sort affordances

Honest caveat on c3: the scaffold registers no app of its own (GET /meta/app = setup, account), so its starter object qa_console_first_pai_note has no dedicated nav entry — a newcomer reaches it through the Setup app's object route or Studio.


Reproduction rules

R-1 — Runtime-authored / org-overlaid dashboard metadata is accepted with a 200 receipt and served by no read door (P1)

Fails dashboards.strict-widget-rejects-stray-keys c5 and dashboards.global-filters-rescope c6; blocks dashboards.chart-type-matrix c0/c4/c7. Same shape as the Tier-2B view finding (R-2 in #9453), now evidenced on dashboard.

pnpm -C examples/app-showcase exec objectstack dev --ui --seed-admin -p 3193 -d file:/tmp/qa-wave/t2c-C.db
POST /api/v1/auth/sign-in/email {"email":"admin@objectos.ai","password":"admin123"}
PUT /api/v1/meta/dashboard/qa_repro2
{"name":"qa_repro2","label":"QA Repro 2",
"widgets":[{"id":"w1","title":"Tasks","type":"metric",
"dataset":"showcase_task_metrics","values":["task_count"]}]}
  • expected: the dashboard is served back and renders.
  • actual: the save returns {"success":true,"version":"sha256:15b715a1…","seq":1,"state":"active","message":"Saved dashboard 'qa_repro2' (org=org_msy16t4w8wiz56vr, state=active) [seq=1]"} and then
    • GET /api/v1/meta/dashboard/qa_repro2{"type":"dashboard","name":"qa_repro2","lock":"none","editable":true,"deletable":true,"resettable":false} — the envelope without the item key;
    • GET /api/v1/meta/dashboard4 items (showcase_chart_gallery, showcase_ops_dashboard, showcase_revenue_pulse, system_overview), the authored name absent;
    • browser /_console/apps/com.example.showcase/dashboard/qa_repro2"Dashboard Not Found — The dashboard "qa_repro2" could not be found. It may have been removed or renamed."

Overlay variant (same defect, drives c6): re-PUT the shipped showcase_revenue_pulse document with widgets[kpi_new_accounts].filterBindings.dateRange changed to "created_at" and widgets[kpi_invoices].filterBindings = {region:false}. The save answers {"success":true,"seq":6,"message":"Saved customization overlay (org=…, state=active) — type=dashboard, name=showcase_revenue_pulse"}, and then the re-read still returns the code values ({dateRange:"signed_on", region:"sales_region"}, and no filterBindings at all on kpi_invoices), and the reloaded dashboard still queries signed_on.

Controls and trap rulings (all measured):

  • Positive control, same door: GET /api/v1/meta/dashboard/showcase_chart_gallery returns {type, name, item:{…18 widgets…}} — the read door works; it simply does not resolve org-scoped runtime-authored rows.
  • Storage control: the rows are stored — sys_metadata holds 3 type=dashboard rows, state=active, organization_id=org_msy0avxsf5eiqdj6.
  • Org confound ruled out: exactly 1sys_organization row and it is the session's active org (get-full-organization.id = the id in the save receipt).
  • Reproduced: 4 saves across 3 names on instance A, plus a 5th on a fresh boot with a fresh DB and a different org id (instance C) — deterministic, schema-driven, not state-dependent.
  • automation-input: not applicable — pure curl + direct navigation; no clicks in the write path.
  • hydration-race: the browser leg settled 6 s and was screenshot-confirmed before any DOM read.
  • stale-console-bundle: the console stamp equals the .objectui-sha pin, and the same bundle renders the shipped dashboards correctly in the same session.

Likely owner: framework metadata read path (the same layer as #9453's view finding), not objectui — the API read door is already wrong before the console is involved.

R-2 — (withheld)

integration-system.datasource-credential-refusal-matrix clause c7 — access-control finding — detail withheld pending maintainer; held by the runner. Already filed as #9391; independently re-confirmed this round. No reproduction published.

R-3 — The notification deep link does not reach the record (P1)

Fails i18n.notification-localized-and-clears c1.

pnpm -C examples/app-showcase exec objectstack dev --ui --seed-admin -p 3191 -d file:/tmp/qa-wave/t2c-A.db
POST /api/v1/auth/sign-up/email {"email":"bravo@qa.example","password":"QaWave!2026","name":"QA bravo"}
as admin: PATCH /api/v1/data/showcase_task/<id> {"assignee":"<bravo user id>"}
→ flow showcase_task_assigned_notify fires; the row's actionUrl is "/showcase_task/<id>"
sign in as bravo in the console → open the bell (button aria-label 打开消息中心 / "Open message center")
→ click the notification entry INSIDE the popover (ref-target the entry's own <button> ancestor)
  • expected: navigation to the task's record page.
  • actual: navigation to /_console/apps/showcase_app/page/showcase_capability_map — the app's landing page. Direct navigation to the authored shape /_console/showcase_task/<id> lands on the same page.
  • Positive control: /_console/apps/showcase_app/showcase_task/record/<id> renders the record page correctly, and in zh-CN (状态 评审中; sections 概览/排期/详细信息; labels 项目/负责人/优先级/截止日期/进度) — so the detail page and its localization are fine; only the deep link is.

Traps ruled out:automation-input — the first attempt was an artifact (a bare text match hit the home page's "Needs your attention" card, not the popover) and was discarded; the recorded run used a ref-targeted click on the popover entry's own <button> ancestor, and that same click provably ran the handler because POST /api/v1/notifications/read fired from it. hydration-race — 7 s settle, screenshot before DOM. shared-browser-tab — own context, absolute origin. wrong-panel — the DOM chain was scoped inside [data-radix-popper-content-wrapper]. Reproduced twice (popover click + direct navigation).

Two candidate homes, for triage not for me to decide: the showcase fixture authors actionUrl: '/showcase_task/{record.id}' (examples/app-showcase/src/automation/flows/index.ts), and the console resolves an app-relative actionUrl to the app root rather than to /apps/<app>/<object>/record/<id>.

R-4 — GET /api/v1/search (unscoped) is 400 whenever pinyin recall is on and a federated object is registered (P1)

Surfaced under search.console-global-search; fails none of its clauses — the console always sends an objects= allowlist that excludes the offending object, so the palette and /search page are unaffected. It bites any caller using the bare documented route.

pnpm -C examples/app-showcase exec objectstack dev --ui --seed-admin -p 3191 -d file:/tmp/qa-wave/t2c-A.db
GET /api/v1/search?q=INV-1003 (admin bearer)
  • expected: 200 with hits.
  • actual: 400{"code":"INVALID_FILTER","error":"Filter on '__search' names a column that object 'showcase_ext_customer' has no column for, so the predicate never ran. A filter on a field that does not exist can only match zero records, so the query was refused instead of answered with an empty list. …"}

Isolation and controls:

  • Reproduced twice on distinct terms (INV-1003, Warehouse).
  • Flag discriminator: the identical request on a boot with OS_SEARCH_PINYIN_ENABLED=false returns 200 with hits. The pinyin search-companion ORs {__search:{$contains:term}} for every searchable object; showcase_ext_customer (external datasource) has no __search column, and the fail-closed filter guard refuses the whole query rather than skipping that object.
  • Culprit isolated: POST /data/showcase_ext_customer/query {"search":"INV"} reproduces the same 400 alone, while POST /data/showcase_invoice/query {"search":"INV-1003"} returns 200 with rows.
  • This is the stock configuration — the showcase declares supportedLocales: ['en','zh-CN'], so pinyin defaults ON.
  • automation-input not applicable (pure curl).

Checklist-accuracy findings

  • CF-1 · i18n.surface-matrix rev 3 step s6 and its _views.emptyState knownGap are stale. They instruct the runner to record the emptyState variant as blocked(fixture) citing showcase: no view authors an emptyState, so the objects._views.<view>.emptyState translation surface has no fixture to test #7714 because "no view in examples/app-showcase authors an emptyState at all". At this pin examples/app-showcase/src/ui/views/task.view.tsdoes author one on the filtered urgent view (its comment block explicitly cites showcase: no view authors an emptyState, so the objects._views.<view>.emptyState translation surface has no fixture to test #7714), and the zh-CN bundle carries objects.showcase_task._views.urgent.emptyState.{title,message}. Driven this round: with the two urgent rows demoted, the view renders 暂无紧急任务 / 当前没有需要立即处理的事项。任务优先级调整为「紧急」后会出现在这里。 The variant is runnable — remove the waiver and bump revision.
  • CF-2 · i18n.surface-matrix c10's premise is wrong, and it nearly produced a false resolver FAIL. The clause asserts the feed/audit verb "localizes on the zh-CN session". It does not, and cannot: packages/plugins/plugin-audit/src/audit-writers.ts:1393 renders the summary once at write time via resolveWriteLocale(tenantId, userId) and stores the string, with an EN hardcoded fallback. Switching only the browser session locale changes nothing — a write performed from the zh session still produced Updated Task "Warehouse schema". Flipping the tenant setting (PUT /api/settings/localization {"values":{"locale":"zh-CN"}}) and writing again produced 更新了 任务 "Warehouse schema". Re-word the clause around the write locale.
  • CF-3 · i18n.strict-translation-key-rejection step s3 names both bundle-door functions as if they share an entry point.defineTranslationBundle is on the package root (@objectstack/spec); defineTranslation is not — it resolves only through the @objectstack/spec/system subpath. A scratch script importing both from the root gets defineTranslation is not a function and reads exactly like a missing guard.
  • CF-4 · search.pinyin-flag-both-sides c2 over-specifies the OFF-side expectation. It says 'zhangwei' must no longer return 张伟 with the flag off. On the stock fixture it still does — via the source columnemail = zhangwei@huaning.example, and likewise huaning on the account via website/billing_email. The initials forms (zw, hnkj, wf) do go to zero, which is the real discriminator. Re-target the clause at the initials, or add a pinyin-only term with no source-column spelling.
  • CF-5 · automation.flow-error-handling step s4 prescribes an api-triggered probe, whose failure c6 then cannot observe. The ERROR line exists precisely because "no caller holds this result" — for a manually/api-triggered run the caller does hold it (the trigger response carries the error), so no ERROR line is written and the log stays clean. Score c6 from a trigger-fired specimen (this round used showcase_declarative_connector_ping and showcase_task_completed_rest_ping), or have the probe fire from a record-change trigger.
  • CF-6 · cli.scaffold-console-first-paint fixture note "no seeded admin unless one is requested" is stale. At this pin bare objectstack dev seeds one automatically on an empty DB and prints 🔑 Dev admin: admin@objectos.ai / admin123 in the banner, and the console login page repeats it. c3's open question ("is there any identity a newcomer can sign in with?") has a definite answer: yes.
  • CF-7 · identity-auth.session-list-revoke c4 describes revoke_reason as a closed vocabulary. It names {idle_timeout, absolute_max, concurrent_cap, admin}, but sys-session.object.ts:154 declares free text whose description lists those only as examples ("…, …"). The observed value after revoke-other-sessions is user_revoked — legitimate, not a violation. Soften the clause or declare the select.
  • CF-8 · Correction to the wave's standing bearer-token fact (tracking: full platform-checklist regression against main @ e4e5c6e3 — five-round orchestration #9296 briefing / QA run · tier2b:browser-1 (21/21) · e4e5c6e3 · 2026-08-18 · 7 PASS / 12 PARTIAL / 2 FAIL #9453 item 1). "The console authenticates by bearer token in localStorage, not by cookie" is true for the data client but incomplete: a real form sign-in also sets a better-auth.session_token cookie, and at least one route family requires it. Driving with a localStorage token alone made POST /api/v1/storage/upload/presigned answer 401 AUTH_REQUIRED while GET /auth/get-session on the same page returned 200 — a convincing fake "avatar upload is broken". With a real form sign-in the whole chain is 200. Runners driving any storage/upload surface must sign in through the form.

Fixture gaps

  • FG-1 — No passwordless/SSO-only user on stock showcase → self-service-profile-password c5 stays blocked. (Restates the item's own knownGap; unchanged at this pin.)
  • FG-2 — Job probes are unauthorable at runtime. job is allowRuntimeCreate=false, allowOrgOverride=false, so the interval/once/failing/timing-out/disabled/handler-missing scratch jobs job-scheduled-run prescribes need source + redeploy. Four of its six clauses (c0 cron arm, c2, c3, c4) are blocked on this alone. Candidate: ship the specimens in examples/app-showcase/src/automation/jobs/ behind an env flag.
  • FG-3 — No dataset-sourced dashboard filter anywhere in the showcase. Both revenue-pulse and ops-dashboard declare static inline options arrays, so global-filters-rescope c8's server-GROUP-BY half has nothing to observe (opening the select fires 0 requests). One filter sourced from a dataset would close it.
  • FG-4 — The two translated _actions on showcase_task (showcase_recalc_estimate, showcase_recalc_selection) are not surfaced on the list row menu, the list toolbar, or the record page, so i18n.surface-matrix c5 (action confirm/success copy) has no reachable specimen. Either surface one of them, or add an _actions translation for an action that is reachable (Mark Done / Reassign… / Log Time / Archive are all EN with no bundle entry).
  • FG-5 — No turso driver in the tree (@objectstack/driver-turso not installed), so datasource-credential-refusal-matrix c6's live connect probe after the mask round-trip had to fall back to a stored-row oracle.
  • FG-6 — The showcase app declares no apps.showcase_app translation entry, so the nav labels variant of i18n.surface-matrix is only demonstrable on the platform setup/account apps. Correct as fallback behaviour, but it means the app-authored nav surface is untested.
  • FG-7 — No seeded persona owns any showcase_invoice row, so a member bound to contributor sees 0 of 12 rather than a partial slice. console-global-search c5 still passes (invisible row absent, no leaked count, object provably searched), but a sharper "sees 11, not INV-1003" contrast needs one seeded invoice owned by a signup-able persona.

Derived observations (no clause failed)

Close-out inputs for the PM

Per this round's brief the run record carries qa-run + tests only; defect extraction is the PM's close-out duty. Cards this round warrants:

  1. R-1 — runtime-authored/org-overlaid dashboard metadata never served. Very likely the same root cause as QA run · tier2b:browser-1 (21/21) · e4e5c6e3 · 2026-08-18 · 7 PASS / 12 PARTIAL / 2 FAIL #9453's R-2 (view); consider one card covering both metadata types.
  2. R-3 — notification deep link does not reach the record.
  3. R-4 — unscoped GET /api/v1/search 400s whenever pinyin recall is on and a federated object is registered.
  4. R-2 — already filed as The datasource-admin HTTP family is reachable unauthenticated, read and write — mounts outside the enforceAuth seam #9391; nothing to extract, nothing published here.

CF-1…CF-8 and FG-1…FG-7 close out through this wave's anchor card (#9296), not as separate cards.

Wave anchor: #9296.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions