You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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
A(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 surface
pass
screenshot: /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 token
pass
POST /auth/revoke-session {token:<device-2>} → {"status":true}; device-2 GET /data/showcase_task → 401; device-1 → 200
c3 revoke-other-sessions
pass
called 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 surface
pass
screenshot: 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 API
pass
PATCH and DELETE /data/sys_session/{id} → OBJECT_API_METHOD_NOT_ALLOWED, allowed:["get","list","aggregate","export"] — as bravo and as admin
POST /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 owner
pass
x-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 immediate
pass
PATCH /data/sys_api_key/{id} {revoked:true} → 200; next x-api-key call → 401
c3 restore reversible
pass
{revoked:false} → 200; next x-api-key call → 200
c4 mine-view scoping
pass
charlie's sys_api_key read = 0 rows, key id absent; admin all_keys read contains it
c5 dialog + list render
pass
screenshot 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
POST /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 write
pass
network 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 remove
pass
profile-avatar-remove-btn → POST /auth/update-user → 200; get-session.image = null; zero <img> on the card, initials "DA" fallback returns
c3 password rotation
pass
change-password (correct current) → 200; sign-in with new → 200; sign-in with old → 401
c4 wrong current refused server-side
pass
change-password with a wrong current → 400{"code":"INVALID_PASSWORD"}; the old password still signs in (200) → credential unchanged
c5 passwordless set-initial
blocked(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.
the 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 bites
pass
update-member-role bravo→admin → membership read shows admin; bravo's organization/update → 200. Demote to member → same call → 403YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_ORGANIZATION
c2 closed vocabulary
pass
role guest → 400{"code":"ROLE_NOT_FOUND","message":"ROLE_NOT_FOUND: guest"}; stack-declared position contributor → same; delegated_admin → accepted. Follow-up list-members shows no stray row
c3 rename persists
pass
organization/update {name:"QA Renamed Org"} → 200; get-full-organization.name = "QA Renamed Org"; the org record page header follows
c4 remove shrinks access
pass
charlie 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 rows
pass
create-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 ways
pass
as 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
interval ✓ flow-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). once ✓ flow-wait:run_e97bd614…:holdrun_count=1. cron ✗ showcase_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
needs a deliberately-throwing scratch job; runtime authoring closed
c3 timeout status
blocked(fixture)
same
c4 two loud skip doors
blocked(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 loudly
pass
runtime 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 → cron→schedule, interval→schedule, fn→handler, each with a did-you-mean. Valid control accepted
after 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"}
POST /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 gate
pass
anonymous GET /notifications → 401{"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-letters
pass
scratch 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 surface
pass
as 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
6/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 works
pass
the external/credentialsRef-shaped datasource published {success:true} with status:"ok" on the connect probe
c2 both read doors redact
pass
full-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-preserving
pass
credential SET → hasSecret:false, redactedConfigKeys:["encryptionKey"]; credential UNSET → redactedConfigKeys:[]. Distinguishable
c4 legacy alias spellings
pass
planted 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 name
pass
driver 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 guard
pass
read 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 artifact
fail
access-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.
object, categoryField, categoryGranularity, valueField, aggregate, aggregation, rowField, columnField, xAxisField, yAxisFields, measures — 11/11 rejected, each error containing the literal key name
c1 error prescribes the ADR-0021 shape
pass
"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 key
pass
chartFlavour → "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 chartConfig
pass
chartConfig.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 persisted
pass
after 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 renders
fail
the 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
Ground truth first (/api/v1/data): 11 tasks · 5 distinct statuses · 4 distinct priorities · 2 month buckets · 5 projects across 3 accounts.
clause
verdict
oracle evidence
c0 each of 20 variants renders
blocked(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 buckets
pass
bar 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 axes
pass
Task 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 geometry
pass
treemap 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 synonyms
blocked(product-bug → R-1)
qa_single_value_gallery (kpi/gauge/solid-gauge/bullet) published {success:true, seq:4} → "Dashboard Not Found"
c5 pivot cross-tab
pass
Tasks 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
qa_compareto_probe published {success:true, seq:5} → "Dashboard Not Found"
c8 converged compareTo pinned at the parse
pass
packages/spec/src/ui/dashboard-compareto.test.ts → 1 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.
"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 truth
pass
under 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 scope
pass
14 accounts, 3 with signed_on = null; a wide signed_on window returns 11 = 14 − 3; unfiltered returns 14
c5 composition on Delivery Operations
pass
with 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 filterBindings
fail
see R-1 — the authored binding does not persist and does not drive the widget
c7 Reset is dirty-gated
pass
pristine 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 BY
blocked(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
flow 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-CN
fail
see R-3 — the click-through never reaches the record
c2 mark-as-read clears
pass
ref-targeted click inside the bell popover fired POST /api/v1/notifications/read {"ids":["enFXX2Z6ZecLsZtH"]} → 200{"readCount":1}
c3 cleared state is authoritative
pass
after a full page reload the re-opened panel reads 已读完所有通知 with an empty 未读 tab — the server persisted it
c4 title is recipient-INDEPENDENT
pass
the 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).
clause
verdict
oracle evidence
c0 every variant renders its known-translated key
blocked(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 truth
pass
the translation read was captured first and every per-variant expectation cites its key path
c2 fallback is honest
pass
untranslated 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 everywhere
blocked(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 locales
pass
the showcase_task list header row is 100% zh-CN: 标题 | 项目 | 负责人 | 状态 | 优先级 | 截止日期 | 进度 | 操作
c5 action dialog copy
blocked(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 envelope
pass
{"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 vocabulary
pass
non-empty; all 9 top-level keys sit inside the declared translationDataShape vocabulary — no fourth dialect
c8 /labels/:object/:locale agrees with the UI
pass
the 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 honestly
pass
/translations/zz-ZZ → 200{"locale":"zz-ZZ","translations":{}}; /labels/showcase_task/zz-ZZ → 200{"object":"showcase_task","locale":"zz-ZZ","labels":{}}. Empty maps, no 500, no dotted-key dump
c10 feed/audit verb localizes
pass
and 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
9 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 door
pass
scratch 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 misses
pass
field helpText → "Unrecognized key(s) on this field translation: helpText. Did you mean helpText → help?"; dashboard widget label → "Unrecognized key(s) on this widget translation: label. Did you mean label → title?"
c3 prescriptions on legacy + retired keys
pass
o → "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 nothing
pass
after every rejection GET /meta/translation/qa_t2c_tr → RESOURCE_NOT_FOUND — the valid sibling key was not half-saved
c5 corrected document saves and resolves
pass
corrected 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
clean 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 correctly
pass
after OS_SEARCH_PINYIN_ENABLED=false restart: initials zw → 0, hnkj → 0, wf → 0 (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-end
pass
plugin 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 boot
pass
every ON-side query above succeeded on a fresh boot with no manual rebuild
15 · search.console-global-search rev 3 — PASS
clause
verdict
oracle evidence
c0 one idempotent open path
pass
the 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 endpoint
pass
wire trace GET /api/v1/search?q=INV-1003&limit=24&objects=… → 200 (searchAll, not a per-object fanout)
c2 CJK/pinyin recall reaches grouped hits
pass
zhangwei → GET /api/v1/search?q=zhangwei… → 200 with the 张伟 hit; the palette renders it under the "Contact" object heading (screenshot before DOM)
c3 Enter navigates
pass
ref-targeted select → /_console/apps/com.example.showcase/showcase_contact/record/XiTyx8y4Db0CtnoA, and the record page renders 张伟 / 华宁科技 / zhangwei@huaning.example
c4 empty input shows recents
pass
with a blank query the overlay renders the "Recently viewed" group and 0/api/v1/search requests fire
c5 RLS parity, both personas
pass
restricted member (signed-up, bound to the contributor position): GET /api/v1/search?q=INV-1003&objects=showcase_invoice → 200{"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 state
pass
/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.
POST /automation/showcase_batch_reminders/trigger with 3 tasks → run run_b6640d78…status=completed; steps with nodeId=send_reminder = 3
c1 region tags
pass
the 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 tree
pass
the 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 API
pass
5 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 contrast
pass
trigger 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
clause
verdict
oracle evidence
c0 handled run completes, container reads success
pass
run 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 catch
pass
steps 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 countable
pass
retryAttempt 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 data
pass
the task after the run: sync_status="failed", sync_error="Node 'push' failed: http: fetch failed"
c4 unhandled run fails loudly
pass
the 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 panel
pass
the 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… / ↳ CATCH → SUCCESS r UPDATE_RECORD 101ms. The unhandled probe's run row shows Failed
c6 unhandled failure is loud in the server log
pass
ERROR-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
npx 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 agrees
pass
package.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 --ui → 200, Plugins: 35 loaded, identical painted shell. The two invocations agree
c3 the newcomer can complete the visual check
pass
an 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/dashboard → 4 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.
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)
translation has the mirror-image asymmetry of R-1, and it is benign: a runtime-authored translation is served by the i18n resolver (/i18n/translations/:locale and /i18n/labels/:object/:locale both returned the authored values) but not by GET /meta/translation/<name> or the /meta/translation listing, which return RESOURCE_NOT_FOUND / 0 items. Worth a look alongside R-1 since both are org-scoped runtime metadata read paths.
Tier-2C — the final round of the platform-checklist QA wave (#9296). Scope: the last 18 unpinned P0/P1 items on the
browser/mixedsurfaces. All 18 consulted; every unpinned P0/P1 item in the ledger now has a verdict.Environment fingerprint
e4e5c6e3c608b1b807c83a0d5b734f213eb1a1dd— unmoved throughout.objectui-sha665661ab093263f39f2e660a295ea615dbcee35a;check:console-sha✓ stamp = pinorigin/mainat round start —check-platform-checklist: OK — 15 areas, 190 items (190 active); 30 kinds mapped, 0 waivedobjectstack-qa-wave, detached at the pin.objectui:build/objectui:refreshnever runfile:/tmp/qa-wave/t2c-A.db(main) · 3192t2c-B.db(pinyin-OFF) · 3193t2c-C.db(fresh-boot repro) · 3194/3195 fresh scaffold. Max 2 concurrent--seed-admin; personas minted withPOST /auth/sign-up/email(bravo@qa.example, charlie@qa.example)executablePath=/opt/pw-browsers/chromium(the RUNNER-prescribed symlink)sys_metadataruntime rows: none);git status= 11docs/qa/**paths, 0 commits, HEAD stille4e5c6e3Scope and per-item revision
identity-auth.session-list-revokeidentity-auth.api-key-ui-lifecycleidentity-auth.self-service-profile-passwordidentity-auth.org-membership-team-managementintegration-system.job-scheduled-runintegration-system.notify-inbox-deliveryintegration-system.datasource-credential-refusal-matrixdashboards.strict-widget-rejects-stray-keysdashboards.chart-type-matrixdashboards.global-filters-rescopei18n.notification-localized-and-clearsi18n.surface-matrixi18n.strict-translation-key-rejectionsearch.pinyin-flag-both-sidessearch.console-global-searchautomation.flow-run-step-nestingautomation.flow-error-handlingcli.scaffold-console-first-paint10 PASS / 4 PARTIAL / 4 FAIL.
Per-clause verdicts
1 ·
identity-auth.session-list-revokerev 1 — PASSbravo)GET /auth/list-sessions+GET /data/sys_session→ 3 rows, alluser_id=fUn5OO…; B(charlie) → 2 rows, alluser_id=pTunKp…. B's row idsGVz0GATJ…,N5G8bwKM…absent from A's read/apps/com.objectstack.account/sys_session/view/mineas bravo renders "1 record" with IP Address / Active Organization / Created At / Expires At columns — count matches bravo's post-revoke API list (exactly 1)POST /auth/revoke-session {token:<device-2>}→{"status":true}; device-2GET /data/showcase_task→ 401; device-1 → 200list-sessionsreturns exactly 1 row (device-1's own token); a previously-other token → 401; caller → 200all_sessionsshows a User column with cross-user rows (Dev Admin / QA bravo / QA Charlie) and aRevokedview; API: admin read = 7 rows across 3 users, 2 withrevoked_atset andrevoke_reason="user_revoked"PATCHandDELETE /data/sys_session/{id}→OBJECT_API_METHOD_NOT_ALLOWED,allowed:["get","list","aggregate","export"]— as bravo and as admin2 ·
identity-auth.api-key-ui-lifecyclerev 1 — PASSPOST /api/v1/keys→ 201{id, name, prefix:"osk_0LHT6VwR", key:"osk_0LHT6VwRXOKC…"}; follow-upGET /data/sys_api_key/{id}returnsprefixonly,grepfor the raw secret = 0x-api-key:<secret>onGET /data/showcase_task→ 200, 10 rows — identical to the owner's session read (10) and to admin's (10); no superuser wideningPATCH /data/sys_api_key/{id} {revoked:true}→ 200; nextx-api-keycall → 401{revoked:false}→ 200; nextx-api-keycall → 200sys_api_keyread = 0 rows, key id absent; adminall_keysread contains itx-api-key:header form. Re-openedall_keyslist shows prefix-only rows3 ·
identity-auth.self-service-profile-passwordrev 1 — PARTIALPOST /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"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 adata:URLprofile-avatar-remove-btn→POST /auth/update-user→ 200;get-session.image = null; zero<img>on the card, initials "DA" fallback returnschange-password(correct current) → 200; sign-in with new → 200; sign-in with old → 401change-passwordwith a wrong current → 400{"code":"INVALID_PASSWORD"}; the old password still signs in (200) → credential unchangedhasLocalPassword=falseuser exists — exactly the item's own knownGapAlso observed (c6-adjacent, no clause): the profile card renders "Email cannot be changed." with the email input disabled.
4 ·
identity-auth.org-membership-team-managementrev 2 — PASS{current_org_id}resolves/_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-memberandget-full-organizationboth returnorg_msy0avxsf5eiqdj6update-member-rolebravo→admin→ membership read showsadmin; bravo'sorganization/update→ 200. Demote tomember→ same call → 403YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_ORGANIZATIONguest→ 400{"code":"ROLE_NOT_FOUND","message":"ROLE_NOT_FOUND: guest"}; stack-declared positioncontributor→ same;delegated_admin→ accepted. Follow-uplist-membersshows no stray roworganization/update {name:"QA Renamed Org"}→ 200;get-full-organization.name = "QA Renamed Org"; the org record page header followsget-full-organization200 /list-members200 /get-active-member200. Afterremove-member: 403 / 400 / 400 with{"code":"NO_ACTIVE_ORGANIZATION"}create-team→ teamV1ZCv5iv…; twoadd-team-membercalls →GET /data/sys_team_member= exactly 2 rows, pairs(V1ZCv5iv…, fUn5OO…)and(V1ZCv5iv…, pTunKp…)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 affordances5 ·
integration-system.job-scheduled-runrev 2 — PARTIALflow-schedule:showcase_scheduled_digestrun_count=75,last_status=success, with its ownsys_job_runrows and real side effects (75sys_inbox_messagedigest rows). once ✓flow-wait:run_e97bd614…:holdrun_count=1. cron ✗showcase_health_sweepis registered withschedule_type=cron,schedule_expression="0 1 * * *",timezone=UTCbutrun_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 + redeploysys_jobrow{name:"showcase_health_sweep", id:"job_58a9d9a1-…"}— keyed by name, adapter-minted row id, schedule persisted (schedule_type/schedule_expression/timezone). Counters move:run_count75 / 15,last_run_at,last_status=success.sys_job_runrows carry{job_name, status, started_at, completed_at, duration_ms, attempt, trigger}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 specimensPUT /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 (JobSchemafrom@objectstack/spec/system):id→ "job.idwas removed in @objectstack/spec 17.0.0 (#4667, ADR-0049) …nameIS the job's identity everywhere"; straywibble→ named; aliases →cron→schedule,interval→schedule,fn→handler, each with a did-you-mean. Valid control accepted6 ·
integration-system.notify-inbox-deliveryrev 2 — PASSshowcase_task/5BiNGEhsVjoH0UeBto bravo, bravo'sGET /notifications→{id:"kq18I3z8jXVFhSnD", type:"task.assigned", title:"New task assigned: Warehouse schema", body:"You have been assigned \"Warehouse schema\".", actionUrl:"/showcase_task/5BiNGEhsVjoH0UeB"}{"notifications":[],"unreadCount":0}POST /notifications/read {ids:[…]}→{readCount:1};?read=falserelist → empty;sys_notification_receiptas bravo →{notification_id:"kq18…", channel:"inbox", state:"read"}(wasdelivered)GET /notifications→ 401{"code":"UNAUTHENTICATED"}. (The 501 capability-unavailable half was not exercised — the clause makes that side optional/citable from a minimal boot.)channels:['push']triggered → zerosys_inbox_messagerows for the recipient (bravo still holds only thetask.assignedrow) andsys_notification_deliverygained exactly one rowchannel="push", state="dead"against 13inbox/successrows. No fabricated inbox rowsys_inbox_message1 row{notification_id:"kq18…", topic:"task.assigned"},sys_notification_receipt1 row keyed(notification_id, user_id, channel:"inbox"). RLS-scoped — admin's read of the same tables shows only admin's 11 rows7 ·
integration-system.datasource-credential-refusal-matrixrev 1 — FAIL (P0)external.credentialsRefprescribed: config-key (config.password→ "passwordis a credential and is not accepted inline in driver config (#7990)"), URL userinfo ("thisurlembeds 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 viaauthentication… dropauthentication(or set{ type: 'none' })")credentialsRef-shaped datasource published{success:true}withstatus:"ok"on the connect probegrepofGET /api/v1/datasources,GET /api/v1/datasources/:nameandGET /api/v1/meta/datasourcefor all 10 planted cleartext values → 0 hits on both doors. Shapes agree (both drop the key rather than masking)hasSecret:false, redactedConfigKeys:["encryptionKey"]; credential UNSET →redactedConfigKeys:[]. Distinguishablepasswd/pwd/authtokenthrough the metadata door (structural validation, as the knownGap predicts) andpasswdthrough the datasource-admin door on an unknown driver. Metadata door servesconfig:{host:…}only; admin door reportsredactedConfigKeys:["passwd"]. All three planted values: 0 hits on both doorscockroachdb(no shipped contract) withconfig.password→ admin doorconfig:{host:"h.qa.example"},redactedConfigKeys:["password"]; metadata doorconfig:{host:…}. Value absent from bothPATCHthe response back unmodified → 200; post-round-trip read still reportsredactedConfigKeys:["encryptionKey"]and the storedsys_metadatarow still contains the credential (grep count 1). Live connect probe unavailable —@objectstack/driver-tursois not installed — so the "still works" half used the stored-credential oracleintegration-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-writableencryptionKeyis stored in cleartext insys_metadata, which the ordinary data API serves — but that path is admin-gated (PERMISSION_DENIEDfor a member,UNAUTHENTICATEDfor anonymous) and is exactly the "stays stored" behaviourdatasource-credential-redaction.tsdocuments.8 ·
dashboards.strict-widget-rejects-stray-keysrev 2 — FAILobject,categoryField,categoryGranularity,valueField,aggregate,aggregation,rowField,columnField,xAxisField,yAxisFields,measures— 11/11 rejected, each error containing the literal key nameobject+categoryField+valueField+aggregate, pivotrowField/columnField) was removed — bind adatasetand selectdimensions+valuesby name. Renderer-only settings belong underoptions."chartFlavour→ "Unrecognized key(s) on this dashboard widget:chartFlavour." — key echoed, no did-you-mean offered (honestly absent, no near match). Quarantinedcomponent/dataand drill near-keysdrillDown/drilldown/drillalso rejected with their own prescriptionschartConfigchartConfig.dataset→ "datasetis not part of the chart config — it is the dashboard widget's own key (ADR-0021), a sibling ofchartConfig, not a key inside it.";chartConfig.drillDown→ "it is a REACT-TIER prop, written besideobjectName/aggregate:<ObjectChart objectName=… drillDown={…} />";aggregateandobjectNamelikewise namedPUT …?mode=draft,GET /meta/dashboard/qa_stray_key_probereturns a bare lock descriptor with no widgets — nothing carrying the stray keydataset: 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 below9 ·
dashboards.chart-type-matrixrev 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.kpi/gauge/solid-gauge/bulletneed a scratch dashboard, which R-1 makes unrenderableTasks by Status5recharts-rectanglepaths = 5 statuses · columnTasks by Priority4 = 4 priorities · horizontal-barHours by Status5 bars (screenshot) · lineTasks Created (monthly)recharts-curveover 2 month buckets · area idem (curve + fill) · pieStatus Split5recharts-sector· donutPriority Split4 sectors · funnelStatus Funnel5recharts-trapezoid· radarPriority Radarrecharts-polar-grid+recharts-polygonover 4 prioritiesTask Count vs Avg Progresscarries bothrecharts-rectangleandrecharts-curvein one SVG, with two y-axis tick sets rendered (0 2 4 6 8left,0 25 50 75 100right)Hours Treemap= 6 nested<rect>(no cartesian grid); sankeyStatus Flow (Sankey)=recharts-sankey-linkwith 11 link paths; scatterEstimate vs Progress= its own cartesian point plot. None falls back to a barqa_single_value_gallery(kpi/gauge/solid-gauge/bullet) published{success:true, seq:4}→ "Dashboard Not Found"Tasks by Status × Priorityrenders a real table: headersStatus | 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 row1,5,3,2equals the API priority buckets{low:1, medium:5, high:3, urgent:2}exactlyexamples/app-showcase/test/coverage.test.ts→ 1 file, 13 tests passedqa_compareto_probepublished{success:true, seq:5}→ "Dashboard Not Found"packages/spec/src/ui/dashboard-compareto.test.ts→ 1 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.offsetwas removed in @objectstack/spec 17.0.0 (#5011, ADR-0049 …)"Enum re-read as the item's step 0 requires:
ChartTypeSchemahas exactly 20 members — the variants list is current.10 ·
dashboards.global-filters-rescoperev 2 — FAILPOST /analytics/dataset/query; invoice widgets carry{"region":"emea"}, account widgets{"sales_region":"emea"}— per-widget field mapping in one trace{"issued_on":{"$gte":"2026-05-20","$lte":"2026-08-18"}}; account widgets{"signed_on":{…}}{"measures":["account_count"]}with noruntimeFilteron load and is absent from all re-query sets; its value stays 14 across region and reset changes{"invoice_count":1}withSELECT COUNT(*) … WHERE ((issued_on >= $1 AND issued_on < $2) AND region = $3)signed_on = null; a widesigned_onwindow returns 11 = 14 − 3; unfiltered returns 14task_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}filterBindingsLast 90 days | APAC | Reset; clicking Reset restoresLast 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{value,label}options + server GROUP BY{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 — bothrevenue-pulse.dashboard.tsandops-dashboard.dashboard.tsdeclare static inlineoptionsarrays, and opening the select fired 0 analytics requests. No dataset-sourced option list exists on stock showcase11 ·
i18n.notification-localized-and-clearsrev 3 — FAILshowcase_task_assigned_notifycfg.title = 'New task assigned: {record.title}'; on a zh-CN recipient session the bell entry reads exactlyNew task assigned: Warehouse schema, character for character. Session locale confirmed zh by the surrounding chrome (消息中心 / 全部标记为已读 / 未读 / 38秒钟前 / 查看全部通知). No raw{record.title}POST /api/v1/notifications/read {"ids":["enFXX2Z6ZecLsZtH"]}→ 200{"readCount":1}已读完所有通知with an empty 未读 tab — the server persisted itNew task assigned: Warehouse schema— no per-recipient divergence, which is the correct (retired-capability) contract12 ·
i18n.surface-matrixrev 3 — PARTIALExpectations grounded first in
GET /api/v1/i18n/translations/zh-CN(134,700 bytes; top-level groupsobjects, globalActions, dashboards, pages, messages, apps, metadataForms, settingsCommon, settings)._sections(task form 任务信息; contact form 这是谁), dashboard (widget title 任务数与平均进度), page (姓名/邮箱/公司/留言/提交咨询), Settings namespace (本地化 · 区域/格式/财务 · 默认时区/默认语言/日期格式), Studio metadataForms (数据/自动化/界面/权限/发布/保存草稿/记录/表单/验证/钩子), sys_ object (用户 · 身份 · 名称/邮箱/手机号码), feed verb (c10). 1 blocked: action dialog (c5)apps.showcase_appis legitimately absent from the bundle). A regex scan for dotted key paths across every driven zh surface found zero raw keys_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_actionsentry and so legitimately render EN/i18n/localesenvelope{"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/i18n/translations/zh-CNvocabularytranslationDataShapevocabulary — no fourth dialect/labels/:object/:localeagrees with the UI{title:标题, project:项目, assignee:负责人, status:状态(+options), priority:优先级(+options), due_date:截止日期, progress:进度, …}matches the rendered zh list-header row field-for-field/translations/zz-ZZ→ 200{"locale":"zz-ZZ","translations":{}};/labels/showcase_task/zz-ZZ→ 200{"object":"showcase_task","locale":"zz-ZZ","labels":{}}. Empty maps, no 500, no dotted-key dumplocale=zh-CN, the next write produced the activity row更新了 任务 "Warehouse schema"— themessages.activityUpdatedtemplate resolved, object label included13 ·
i18n.strict-translation-key-rejectionrev 1 — PASSPUT /meta/translation/<name>, each alongside a validobjects.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@objectstack/spec:defineTranslationBundle(root export) anddefineTranslation(via the/systemsubpath — see CF-3) each throw for all 11 variants with equivalent error content; both valid controls acceptedhelpText→ "Unrecognized key(s) on this field translation:helpText. Did you meanhelpText→help?"; dashboard widgetlabel→ "Unrecognized key(s) on this widget translation:label. Did you meanlabel→title?"o→ "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"GET /meta/translation/qa_t2c_tr→RESOURCE_NOT_FOUND— the valid sibling key was not half-saved{success:true, seq:1, state:'active'}, and it resolves:/i18n/translations/zh-CNthen returned the authoredobjects.showcase_task.label = 任务(QA已改)andfields.title.help = 帮助;/i18n/labels/showcase_task/zh-CNserved the newhelptoo. Deleted afterwards, and the shipped values (任务 / 标题) came back14 ·
search.pinyin-flag-both-sidesrev 2 — PASS/data/<obj>/query:zhangwei→张伟 (1),zw→张伟 (1),ZhangWei→张伟 (1),张→张伟 (1),huaning→华宁科技 (1),hnkj→华宁科技 (1),华宁→华宁科技 (1)keji(科技's pinyin) returns 0 onshowcase_contact— whosecompanyfield holds 华宁科技 — and 1 onshowcase_account, where 华宁科技 IS the display name. Control:wangfang→ 王芳 (1)OS_SEARCH_PINYIN_ENABLED=falserestart: initialszw→ 0,hnkj→ 0,wf→ 0 (companion gone);张→ 1 and华宁→ 1 (source column). Full-pinyinzhangwei/huaningstill return 1 — a source-column hit, evidenced by the exact stored valuesemail=zhangwei@huaning.exampleandwebsite=https://huaning.example/billing_email=billing@huaning.example. See CF-415 ·
search.console-global-searchrev 3 — PASSaction:command-palette:open) andCtrl+Keach yield theoverlay:command-paletteoverlay with?palette=1on the URL; calling open while open is a no-opGET /api/v1/search?q=INV-1003&limit=24&objects=…→ 200 (searchAll, not a per-object fanout)zhangwei→GET /api/v1/search?q=zhangwei…→ 200 with the 张伟 hit; the palette renders it under the "Contact" object heading (screenshot before DOM)/_console/apps/com.example.showcase/showcase_contact/record/XiTyx8y4Db0CtnoA, and the record page renders 张伟 / 华宁科技 / zhangwei@huaning.example/api/v1/searchrequests firecontributorposition):GET /api/v1/search?q=INV-1003&objects=showcase_invoice→ 200{"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_invoicereturns 200 with 0 rows (all 12 seeded invoices are owned by ada/linus/grace)/searchpage, same path + empty state/apps/com.example.showcase/search?q=INV-1003firesGET /api/v1/search?q=…&limit=60→ 200 and renders1 result for "INV-1003"grouped under Invoice; a no-hit query renders the designed empty state0 results for "zzzznohitzzz" / No results found / Try adjusting your search terms— no error envelope, no error toastA 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-nestingrev 2 — PASSPOST /automation/showcase_batch_reminders/triggerwith 3 tasks → runrun_b6640d78…status=completed; steps withnodeId=send_reminder= 3{parentNodeId:"loop_tasks", regionKind:"loop-body"}withiteration0, 1, 2 — no two share an iteration;startandloop_taskscarry noparentNodeId/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_tasksat x=950, the threesend_reminder(NOTIFY) rows at x=974 each prefixed by a↳marker at x=912. Screenshot taken before the DOM readloop_tasks= the 3 API steps taggedparentNodeId=loop_tasks, iterations 0/1/2tasks: []→ runrun_45d2a21f…status=completed, steps[start, loop_tasks(success)], zero steps withparentNodeId=loop_tasks17 ·
automation.flow-error-handlingrev 2 — PASSrun_ebd1af1b…status=completed,durationMs=7156; thetry_catchstepguarded_pushcarriesstatus="success"(by design per #7546 — not a finding){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 fourretryAttemptvalues 0, 1, 2, 3 — exactlymaxRetries(3)+1, eachstatus=failure. Duration cross-check:durationMs=7156≥ the 1 s first backoff, and 1+2+4 = 7 s matchesbackoffMs:1000, backoffMultiplier:2$errorbinds into datasync_status="failed",sync_error="Node 'push' failed: http: fetch failed"status="failed"with the run-levelerror: "Node 'push' failed: http: fetch failed"populated; steps[start(success), push(failure)]SUCCESS start START 0ms/SUCCESS guarded_pu… TRY_CATCH 7.1s/↳ TRY→ 4×FAILURE p… HTTP …ms http: fetc…/↳ CATCH→SUCCESS r UPDATE_RECORD 101ms. The unhandled probe's run row shows Failedshowcase_task_completed_rest_ping— structurally identical (an outbound call failing outsidetry_catch). See CF-5 on why an api-triggered probe legitimately produces no such line18 ·
cli.scaffold-console-first-paintrev 1 — PASSnpx os validateon a pristine scaffoldnpx -y create-objectstack@latest qa-console-first-paint→✓ Environment created!;npm install→ 438 packages;npx os validate→ exit 0,✓ Validation passed (79ms),Data: 1 Objects 2 Fields · Runtime: 3 pluginsnpx 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 tracedevscript agreespackage.json scripts.dev = "objectstack dev"(no--ui). Barenpm run dev→/_console/200,Plugins: 35 loaded, banner advertising➜ Console: http://localhost:3194/_console/.npx os dev --ui→ 200,Plugins: 35 loaded, identical painted shell. The two invocations agreeNoteobject 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 shows1 recordwith working nav / filter / group / sort affordancesHonest caveat on c3: the scaffold registers no app of its own (
GET /meta/app=setup, account), so its starter objectqa_console_first_pai_notehas 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
dashboardmetadata is accepted with a 200 receipt and served by no read door (P1)Fails
dashboards.strict-widget-rejects-stray-keysc5 anddashboards.global-filters-rescopec6; blocksdashboards.chart-type-matrixc0/c4/c7. Same shape as the Tier-2Bviewfinding (R-2 in #9453), now evidenced ondashboard.{"success":true,"version":"sha256:15b715a1…","seq":1,"state":"active","message":"Saved dashboard 'qa_repro2' (org=org_msy16t4w8wiz56vr, state=active) [seq=1]"}and thenGET /api/v1/meta/dashboard/qa_repro2→{"type":"dashboard","name":"qa_repro2","lock":"none","editable":true,"deletable":true,"resettable":false}— the envelope without theitemkey;GET /api/v1/meta/dashboard→ 4 items (showcase_chart_gallery, showcase_ops_dashboard, showcase_revenue_pulse, system_overview), the authored name absent;/_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-
PUTthe shippedshowcase_revenue_pulsedocument withwidgets[kpi_new_accounts].filterBindings.dateRangechanged to"created_at"andwidgets[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 nofilterBindingsat all onkpi_invoices), and the reloaded dashboard still queriessigned_on.Controls and trap rulings (all measured):
GET /api/v1/meta/dashboard/showcase_chart_galleryreturns{type, name, item:{…18 widgets…}}— the read door works; it simply does not resolve org-scoped runtime-authored rows.sys_metadataholds 3type=dashboardrows,state=active,organization_id=org_msy0avxsf5eiqdj6.sys_organizationrow and it is the session's active org (get-full-organization.id= the id in the save receipt).automation-input: not applicable — purecurl+ 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-shapin, 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
viewfinding), not objectui — the API read door is already wrong before the console is involved.R-2 — (withheld)
integration-system.datasource-credential-refusal-matrixclause 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-clearsc1./_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./_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 becausePOST /api/v1/notifications/readfired 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 anobjects=allowlist that excludes the offending object, so the palette and/searchpage are unaffected. It bites any caller using the bare documented route.{"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:
INV-1003,Warehouse).OS_SEARCH_PINYIN_ENABLED=falsereturns 200 with hits. The pinyin search-companion ORs{__search:{$contains:term}}for every searchable object;showcase_ext_customer(external datasource) has no__searchcolumn, and the fail-closed filter guard refuses the whole query rather than skipping that object.POST /data/showcase_ext_customer/query {"search":"INV"}reproduces the same 400 alone, whilePOST /data/showcase_invoice/query {"search":"INV-1003"}returns 200 with rows.supportedLocales: ['en','zh-CN'], so pinyin defaults ON.automation-inputnot applicable (purecurl).Checklist-accuracy findings
i18n.surface-matrixrev 3 step s6 and its_views.emptyStateknownGap are stale. They instruct the runner to record the emptyState variant asblocked(fixture)citing showcase: no view authors anemptyState, so theobjects._views.<view>.emptyStatetranslation surface has no fixture to test #7714 because "no view in examples/app-showcase authors an emptyState at all". At this pinexamples/app-showcase/src/ui/views/task.view.tsdoes author one on the filteredurgentview (its comment block explicitly cites showcase: no view authors anemptyState, so theobjects._views.<view>.emptyStatetranslation surface has no fixture to test #7714), and the zh-CN bundle carriesobjects.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 bumprevision.i18n.surface-matrixc10'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:1393renders the summary once at write time viaresolveWriteLocale(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 producedUpdated 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.i18n.strict-translation-key-rejectionstep s3 names both bundle-door functions as if they share an entry point.defineTranslationBundleis on the package root (@objectstack/spec);defineTranslationis not — it resolves only through the@objectstack/spec/systemsubpath. A scratch script importing both from the root getsdefineTranslation is not a functionand reads exactly like a missing guard.search.pinyin-flag-both-sidesc2 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 likewisehuaningon the account viawebsite/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.automation.flow-error-handlingstep 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 usedshowcase_declarative_connector_pingandshowcase_task_completed_rest_ping), or have the probe fire from a record-change trigger.cli.scaffold-console-first-paintfixture note "no seeded admin unless one is requested" is stale. At this pin bareobjectstack devseeds one automatically on an empty DB and prints🔑 Dev admin: admin@objectos.ai / admin123in 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.identity-auth.session-list-revokec4 describesrevoke_reasonas a closed vocabulary. It names{idle_timeout, absolute_max, concurrent_cap, admin}, butsys-session.object.ts:154declares free text whose description lists those only as examples ("…, …"). The observed value afterrevoke-other-sessionsisuser_revoked— legitimate, not a violation. Soften the clause or declare the select.localStorage, not by cookie" is true for the data client but incomplete: a real form sign-in also sets abetter-auth.session_tokencookie, and at least one route family requires it. Driving with a localStorage token alone madePOST /api/v1/storage/upload/presignedanswer 401 AUTH_REQUIRED whileGET /auth/get-sessionon 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
self-service-profile-passwordc5 staysblocked. (Restates the item's own knownGap; unchanged at this pin.)jobisallowRuntimeCreate=false, allowOrgOverride=false, so the interval/once/failing/timing-out/disabled/handler-missing scratch jobsjob-scheduled-runprescribes need source + redeploy. Four of its six clauses (c0 cron arm, c2, c3, c4) are blocked on this alone. Candidate: ship the specimens inexamples/app-showcase/src/automation/jobs/behind an env flag.revenue-pulseandops-dashboarddeclare static inlineoptionsarrays, soglobal-filters-rescopec8's server-GROUP-BY half has nothing to observe (opening the select fires 0 requests). One filter sourced from a dataset would close it._actionsonshowcase_task(showcase_recalc_estimate,showcase_recalc_selection) are not surfaced on the list row menu, the list toolbar, or the record page, soi18n.surface-matrixc5 (action confirm/success copy) has no reachable specimen. Either surface one of them, or add an_actionstranslation for an action that is reachable (Mark Done / Reassign… / Log Time / Archive are all EN with no bundle entry).@objectstack/driver-tursonot installed), sodatasource-credential-refusal-matrixc6's live connect probe after the mask round-trip had to fall back to a stored-row oracle.apps.showcase_apptranslation entry, so thenav labelsvariant ofi18n.surface-matrixis only demonstrable on the platformsetup/accountapps. Correct as fallback behaviour, but it means the app-authored nav surface is untested.showcase_invoicerow, so a member bound tocontributorsees 0 of 12 rather than a partial slice.console-global-searchc5 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)
translationhas the mirror-image asymmetry of R-1, and it is benign: a runtime-authored translation is served by the i18n resolver (/i18n/translations/:localeand/i18n/labels/:object/:localeboth returned the authored values) but not byGET /meta/translation/<name>or the/meta/translationlisting, which returnRESOURCE_NOT_FOUND/ 0 items. Worth a look alongside R-1 since both are org-scoped runtime metadata read paths.showcase_field_zoo.f_autonumber,organizationCount: 0) — WARN level, already filed, fails no clause here.Close-out inputs for the PM
Per this round's brief the run record carries
qa-run+testsonly; defect extraction is the PM's close-out duty. Cards this round warrants:dashboardmetadata 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.GET /api/v1/search400s whenever pinyin recall is on and a federated object is registered.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.