Tracking issue for end-to-end browser verification of the v17 (17.0.0-rc) train , per the release plan. A dedicated agent will work through this checklist against a running app (PORT=3000 pnpm dev showcase + :5180 objectui dev server where UI-side, or the dogfood-verification flow), acting as a real admin/user. Check items off as they are verified; file a separate issue for every failure found and link it here.
Sources of truth: the curated release page content/docs/releases/v17.mdx , the 300+ pending changesets in .changeset/, and the objectui window cf2d56e32a11 → 4a4829d0ef39 (incl. the backfilled range 2cb8d78e24ad...c6cfdf1288b6, see PR #3908 ). The docs-sweep log lives at docs/v17-docs-sweep.md.
A. Breaking behavior — verify the new posture actually enforces
Export opt-in (用户级 export 权限轴(接入 P1 预留的 userExportAllowed 槽) #3544 /定时报表的 CSV 附件绕过用户级 export 权限轴(#3544 的第二道出数据的门) #3710 ): as a member_default user, export on any list is denied; after an admin adds allowExport: true to the set, export works; viewAllRecords alone does NOT grant export; a set carrying allowExport cannot bind to everyone/guest anchors; export headers still emit on an empty result (security 服务 getReadableFields 查询面(export 列投影的长期正解) #3547 ); reports covered by the same axis.
ApiMethod shrink (P2:ApiMethod 枚举收缩至 6 原语(breaking,独立版本) #3543 ): an object authored with a legacy value (e.g. apiMethods: ['upsert']) strips to deny-all with a FROM→TO warning; primitives-declared objects still derive search/export/history correctly; Console renders the server's effective operation set (objectui#2823) and detail/form edit & delete are gated on it (objectui#2832), matching grid rows (objectui#2889).
User-less flow runs refused (The #1888 user-less fail-open is wider than the lint that guards it — record-change flows fired by a system write run UNSCOPED, unlinted #3760 ): a runAs:'user' record-change flow fired by a system write (e.g. approvals status mirror) refuses data ops with UnscopedRunDataAccessError naming runAs:'system'; the originating write still succeeds; a runAs:'system' flow works and is audit-attributed.
Alias retirement ([P3] Retire the three deprecated aliases — via the ADR-0087 D2 conversion layer, not by deleting the keys #3855 ): authoring action.execute / field.conditionalRequired / agent.knowledge.topics is rejected with the rename in the error; os migrate meta --from 16 rewrites all three; agent.tools is tombstoned (parse error, not silent strip) (feat(spec,lint)!: accept ADR-0109, remove agent.tools[], lint agent authoring, model AI exposure in tool resolution (#3820) #3894 ).
Approval participant visibility (审批请求的可见性是「按租户」而非「按参与者」——getRequest 用 SYSTEM_CTX 读、只按 organization_id 收窄 #3590 ): a non-participant authenticated user cannot read another user's approval request (list/get/count), incl. payload snapshot, decision history, attachments; participants and admin override still can.
Sharing (fix(sharing): 「完全访问」声明了删除/转移/共享,实现上与「编辑」等价 #3865 /[P0] Metadata property liveness audit: ~half of all spec properties are dead; a cluster of security props is parsed-but-unenforced #1878 ): accessLevel:'full' converts to 'edit' at load; group recipient rejected (renamed team); business_unit recipient enforced; owner-type/guest rules no longer parse; the sharing-rule dialog is usable and permission-aware (objectui#2920); switching a rule off actually withdraws access (fix(sharing,driver-sql): a rule authored in Setup actually applies — and switching it off actually withdraws access #3850 ).
Membership grade closed (ADR-0108/sys_member.role app-role channel reverses ADR-0057 D4 ("never as the authority for RBAC") — pin to the built-ins before the next RC; placement is the governed channel #3723 ): a value in sys_member.role no longer projects into current_user.positions; sys_member writes are governed (ADR-0105 D8: the scope-bounded issuance path has no caller — every principal who may invite is already a tenant admin #3697 ); delegated_admin reachable in Console with narrowed pickers (objectui#2891); scoped invitations place into unit+positions (objectui#2868).
better-auth 1.7 (rc.2): existing password/social/SSO accounts sign in after the issuer backfill; unlink uses the account ROW id; boot log names non-derivable federated rows; 2FA obeys the operator lockout policy (2FA lockout is hardcoded to better-auth's defaults while the password lockout is operator-configurable — the second factor ignores the settings UI #3690 ); SCIM provider creation works when enabled (SCIM: 停在 @better-auth/scim rc.1,等正式版再整体迁移 —— rc.2 换掉了整套模型 #3653 ).
Datasource fail-fast (ObjectQLEngine.init() 吞掉 driver 连接失败:连不上数据库的服务器照常启动,driver 的启动期致命错误被降级成运行时报错 #3741 /显式绑定的 datasource 连不上只降级成一条 warning:绑上去的对象在启动后全废,直到查询时才炸 #3758 /ADR-0062 D1 收尾:default driver 的 connect 与失败判决仍是第二份实现(阻塞点已定位) #3826 ): a bound datasource with a bad URL fails the boot with an operator-readable error; /ready 503s when a driver stops answering; Setup datasource list shows the real connect verdict + reason (objectui#2926); startup banner reads DSN datasources and prints no credentials (启动横幅对 DSN 形式的 datasource 显示 → (unknown):describeRegisteredDriver 不认 connectionString #3793 ).
Per-tenant unique (多租户缺陷:unique 物化为全局唯一索引、无视 tenancy,与按租户分裂的 autonumber 序列自相矛盾(跨租户必然撞号 + 存在性探测泄露) #3696 ): two tenants can hold the same unique value; violation message no longer leaks cross-tenant existence.
Multi-org entitlement (ADR-0105 D12): group posture refuses to activate without @objectstack/organizations; org switcher acts as write context with org attribution (objectui#2858).
Node 22 floor (CI validates every PR gate on Node 20 (EOL 2026-04-30) while release/publish runs Node 22 #3825 ): dev/serve/CLI on Node 22; EBADENGINE warning on Node 20.
B. Removed surfaces — verify they are actually gone (and loud)
/graphql → 404 (was 501); no GraphQL advertisement in discovery.
SDK: client.ai.{nlq,suggest,insights}, client.permissions.*, client.realtime.*, client.workflow.*, client.views CRUD, notifications device/preference helpers, projects.listTemplates() — absent (compile error), not 404s.
CLI: os environments create --template rejected as unknown flag.
Console: API console lists the real AI family, tool preview links no 404 (objectui#2925/test(app-showcase): round-3 permission-model retest — issues register with evidence #2921 ); useClientNotifications no longer exposes device/preference delegates (objectui#2862).
Marketplace publish moved: client.packages.install works (POST /packages no longer collides, POST /api/v1/packages is a publish-vs-install shape collision — REST registers first and shadows the dispatcher (#3587 finding) #3610 ); publish at POST /packages/publish.
C. New capabilities — verify they work end-to-end
Files as records (ADR-0104): upload against a field with accept/maxSize → violations rejected server-side (FileConstraintError, direct API call too, not just the widget); stored value is a sys_file id, read expands; downloads carry real filename+content-type (fix(approvals+storage): 审批决策附件在时间线里没名字、点击无反应、下载名是乱码 token (objectui #2820) #3504 ); governed download denies an unauthorized caller; os migrate files-to-references dry-run reports / --apply converts+verifies; strict mode only after the flag; Console adopts the reference shape incl. localized upload widget (objectui#2828/docs(adr): ADR-0092 标记 Accepted,关联实现 issue #2816 / #2817 #2819 ); image fields render consistently with click-to-zoom (objectui#2837).
Dynamic approver routing (审批流无法在「运行中」动态确定审批人——审批人在提交时即被 $trigger 快照锁定(动态路由 / 动态会签受阻) #3447 ): expression approvers resolve at node entry over current/trigger/vars (bare record rejected with prescriptive error); resolveAs + per_group sign-off; onEmptyApprovers all three policies (incl. auto_approve continuing the approve edge); decision outputs: declared keys accepted → resume as <nodeId>.<key> vars, undeclared rejected, typed outputs render record pickers (objectui#2831/docs+build(releases): whole-platform 12.0→14.x history + a maintenance model that includes the frontend #2827 ); live field/manager resolution lets an earlier step route a later one.
Approvals UX: quorum progress, per-group chips (objectui#2811), admin override for unstaffed approver (Approval routed to an empty position permanently locks the record (no admin override, no recovery) #3424 , objectui#2810), timeline attachment chip opens (objectui#2821), lockRecord honored — "in approval (editable)" vs locked (objectui#2906/feat(cli): surface migration guide when specVersion trails the installed platform #2914 ), decision recorded against the authenticated caller (approvals: the acting identity must be resolved server-side, not taken from the request payload #3800 ), status mirror names the human (Approvals status mirror drops the acting user, forcing every downstream record-change flow to runAs:'system' #3783 ), dead run leaves no RECORD_LOCKED (Approval: a dead approval run can leave the record RECORD_LOCKED (#3424 follow-up, expected-behavior 3) #3456 ).
Write observability: PATCH/POST /data returns droppedFields (single, bulk, cross-object batch 审批场景下记录可写性的反馈全线失真:能改的显示「已锁定」,改不了的提示「更新成功」 #3794 ); Console toasts on silently-dropped read-only fields (objectui#2814) and keeps warnings on detail (objectui#2914); flow step warnings in Runs panel (service-automation: update_record reports success when written fields are silently stripped — no observability for dropped writes (split from #3356) #3407 ).
record-after-write trigger (record_change flow start node binds to a single lifecycle event — no create-OR-update in one flow #3427 ): fires on create OR update with previous null on create; Studio offers the new trigger and hides never-firing record-change.
Screen flows (Console: screen-flow Submit never calls the resume endpoint — every screen flow is un-completable from the UI #3528 ): pause → Console completes it; visibleWhen honored in render+validation; runner doesn't tear down its host.
Analytics: RLS/tenant scope on ObjectQL path (security(analytics): ObjectQLStrategy 不消费 getReadScope — NativeSQL 回落后聚合查询无 RLS/租户谓词(#2852 修复未覆盖的另一半) #3597 ), dateRange applied (analytics: ObjectQLStrategy 静默忽略 timeDimensions[].dateRange —— 恰好是 date-granularity 图表必走的那条路 #3650 ), effective granularity in labels+drills (Analytics query builder ignores widget dateGranularity, sortBy/sortOrder, and funnel stage order #3588 ), FK-expand cross-object grouping / fail-closed (analytics/objectql: 跨对象(点号 lookup)维度聚合在 ObjectQL 路径上静默塌成 (null) 桶 / native SQL 报错 #3654 ), label lookups RLS-scoped (analytics: 让 executeAggregate 桥携带 ExecutionContext(#3597 的纵深防御第二层)+ 两处残留无 scope 调用 #3602 ), widget dateGranularity/sortBy/limit honored (objectui#2864), funnel stage order, {current_user_id} in widget filters (objectui#2857).
Historical import (data import: mid-lifecycle rows rejected by state_machine.initialStates — historical-data migration blocked (#3433 sibling) #3479 /data import: a "historical" import can't preserve original timestamps / audit fields — updated_at is stamped now, readonly fields stripped on upsert (#3479 follow-up) #3493 /data import undo: 撤销一次「historical」导入无法还原被保留的 readonly / 审计字段 — undo 的 writeCtx 缺 preserveAudit (#3493 follow-up) #3549 ): wizard "Import as historical data" (objectui#2815) skips state machine + preserves audit timeline, undo included; row errors human-readable, no raw SQL (Identity import: raw SQL leaks into row errors; DB constraint failures need sanitized, friendly messages #3566 ); email format validated in preview (objectui#2840).
i18n: /i18n/labels emits declared shape with help/options (GetFieldLabelsResponse declares rich label entries; both surfaces emit bare strings #3847 ); product-authored translations render (i18n: two unbridged bundle shapes — file-authored objects.<name> vs the runtime translation metadata type's o.<object>, so translations cannot be authored in the product #3778 ); all ten Console packs at key parity (objectui#2909); system-settings hub localized (objectui#2859); SSO/SCIM/user-position/import-job admin objects translated.
Seeds: boot banner Seeds: X inserted · Y updated · Z skipped + yellow REJECTED line (showcase: 4/5 projects and 9/10 tasks are silently rejected on every boot (FSM initialStates vs seed data) #3415 /os dev: surface seed / sample-data-heal outcomes in the boot summary (info logs are swallowed by the default warn log level) #3430 ); marketplace rehydrate heals sample data.
Grid/detail: eleven summary aggregations (objectui#2890), real match total under server pagination (objectui#2873), Kanban Uncategorized lane (objectui#2804), $or filters AND their contents (driver-sql), datetime grouping not collapsing to (null) on SQLite (SQLite 上 Field.datetime 的 dateGranularity 分桶恒为 NULL —— 趋势图塌成一根柱子 #3773 ), empty group bucket is real null (空分组桶的键两条路不一致:下推 SQL 给 null,内存兜底给 "(null)"(不限日期分桶) #3839 ).
D. Docs verification (site)
E. Known out-of-scope items already logged
Spec comment drift: core-services.zod.ts 'ai' member comment still says "NLQ, Chat, Suggest, Insights" (comment-only, spec-touching PR).
content/docs/releases/implementation-status.mdx predates the v17 window; needs its own regeneration pass.
RC exit: after changeset pre exit, re-check the v17 page's version wording and this checklist's remaining items.
Tracking issue for end-to-end browser verification of the v17 (17.0.0-rc) train, per the release plan. A dedicated agent will work through this checklist against a running app (
PORT=3000 pnpm devshowcase +:5180objectui dev server where UI-side, or the dogfood-verification flow), acting as a real admin/user. Check items off as they are verified; file a separate issue for every failure found and link it here.Sources of truth: the curated release page
content/docs/releases/v17.mdx, the 300+ pending changesets in.changeset/, and the objectui windowcf2d56e32a11 → 4a4829d0ef39(incl. the backfilled range2cb8d78e24ad...c6cfdf1288b6, see PR #3908). The docs-sweep log lives atdocs/v17-docs-sweep.md.A. Breaking behavior — verify the new posture actually enforces
member_defaultuser, export on any list is denied; after an admin addsallowExport: trueto the set, export works;viewAllRecordsalone does NOT grant export; a set carryingallowExportcannot bind toeveryone/guestanchors; export headers still emit on an empty result (security 服务 getReadableFields 查询面(export 列投影的长期正解) #3547); reports covered by the same axis.apiMethods: ['upsert']) strips to deny-all with a FROM→TO warning; primitives-declared objects still derive search/export/history correctly; Console renders the server's effective operation set (objectui#2823) and detail/form edit & delete are gated on it (objectui#2832), matching grid rows (objectui#2889).runAs:'user'record-change flow fired by a system write (e.g. approvals status mirror) refuses data ops withUnscopedRunDataAccessErrornamingrunAs:'system'; the originating write still succeeds; arunAs:'system'flow works and is audit-attributed.action.execute/field.conditionalRequired/agent.knowledge.topicsis rejected with the rename in the error;os migrate meta --from 16rewrites all three;agent.toolsis tombstoned (parse error, not silent strip) (feat(spec,lint)!: accept ADR-0109, remove agent.tools[], lint agent authoring, model AI exposure in tool resolution (#3820) #3894).accessLevel:'full'converts to'edit'at load;grouprecipient rejected (renamedteam);business_unitrecipient enforced; owner-type/guestrules no longer parse; the sharing-rule dialog is usable and permission-aware (objectui#2920); switching a rule off actually withdraws access (fix(sharing,driver-sql): a rule authored in Setup actually applies — and switching it off actually withdraws access #3850).sys_member.roleapp-role channel reverses ADR-0057 D4 ("never as the authority for RBAC") — pin to the built-ins before the next RC; placement is the governed channel #3723): a value insys_member.roleno longer projects intocurrent_user.positions;sys_memberwrites are governed (ADR-0105 D8: the scope-bounded issuance path has no caller — every principal who may invite is already a tenant admin #3697);delegated_adminreachable in Console with narrowed pickers (objectui#2891); scoped invitations place into unit+positions (objectui#2868).defaultdriver 的 connect 与失败判决仍是第二份实现(阻塞点已定位) #3826): a bound datasource with a bad URL fails the boot with an operator-readable error;/ready503s when a driver stops answering; Setup datasource list shows the real connect verdict + reason (objectui#2926); startup banner reads DSN datasources and prints no credentials (启动横幅对 DSN 形式的 datasource 显示→ (unknown):describeRegisteredDriver 不认connectionString#3793).unique(多租户缺陷:unique 物化为全局唯一索引、无视 tenancy,与按租户分裂的 autonumber 序列自相矛盾(跨租户必然撞号 + 存在性探测泄露) #3696): two tenants can hold the same unique value; violation message no longer leaks cross-tenant existence.groupposture refuses to activate without@objectstack/organizations; org switcher acts as write context with org attribution (objectui#2858).EBADENGINEwarning on Node 20.B. Removed surfaces — verify they are actually gone (and loud)
/graphql→ 404 (was 501); no GraphQL advertisement in discovery.client.ai.{nlq,suggest,insights},client.permissions.*,client.realtime.*,client.workflow.*,client.viewsCRUD, notifications device/preference helpers,projects.listTemplates()— absent (compile error), not 404s.os environments create --templaterejected as unknown flag.useClientNotificationsno longer exposes device/preference delegates (objectui#2862).client.packages.installworks (POST /packagesno longer collides, POST /api/v1/packages is a publish-vs-install shape collision — REST registers first and shadows the dispatcher (#3587 finding) #3610); publish atPOST /packages/publish.C. New capabilities — verify they work end-to-end
accept/maxSize→ violations rejected server-side (FileConstraintError, direct API call too, not just the widget); stored value is asys_fileid, read expands; downloads carry real filename+content-type (fix(approvals+storage): 审批决策附件在时间线里没名字、点击无反应、下载名是乱码 token (objectui #2820) #3504); governed download denies an unauthorized caller;os migrate files-to-referencesdry-run reports /--applyconverts+verifies; strict mode only after the flag; Console adopts the reference shape incl. localized upload widget (objectui#2828/docs(adr): ADR-0092 标记 Accepted,关联实现 issue #2816 / #2817 #2819); image fields render consistently with click-to-zoom (objectui#2837).expressionapprovers resolve at node entry overcurrent/trigger/vars(barerecordrejected with prescriptive error);resolveAs+per_groupsign-off;onEmptyApproversall three policies (incl.auto_approvecontinuing the approve edge); decision outputs: declared keys accepted → resume as<nodeId>.<key>vars, undeclared rejected, typed outputs render record pickers (objectui#2831/docs+build(releases): whole-platform 12.0→14.x history + a maintenance model that includes the frontend #2827); livefield/managerresolution lets an earlier step route a later one.lockRecordhonored — "in approval (editable)" vs locked (objectui#2906/feat(cli): surface migration guide when specVersion trails the installed platform #2914), decision recorded against the authenticated caller (approvals: the acting identity must be resolved server-side, not taken from the request payload #3800), status mirror names the human (Approvals status mirror drops the acting user, forcing every downstream record-change flow torunAs:'system'#3783), dead run leaves no RECORD_LOCKED (Approval: a dead approval run can leave the record RECORD_LOCKED (#3424 follow-up, expected-behavior 3) #3456)./datareturnsdroppedFields(single, bulk, cross-object batch 审批场景下记录可写性的反馈全线失真:能改的显示「已锁定」,改不了的提示「更新成功」 #3794); Console toasts on silently-dropped read-only fields (objectui#2814) and keeps warnings on detail (objectui#2914); flow step warnings in Runs panel (service-automation: update_record reportssuccesswhen written fields are silently stripped — no observability for dropped writes (split from #3356) #3407).record-after-writetrigger (record_change flow start node binds to a single lifecycle event — no create-OR-update in one flow #3427): fires on create OR update withpreviousnull on create; Studio offers the new trigger and hides never-firingrecord-change.visibleWhenhonored in render+validation; runner doesn't tear down its host.dateRangeapplied (analytics: ObjectQLStrategy 静默忽略 timeDimensions[].dateRange —— 恰好是 date-granularity 图表必走的那条路 #3650), effective granularity in labels+drills (Analytics query builder ignores widget dateGranularity, sortBy/sortOrder, and funnel stage order #3588), FK-expand cross-object grouping / fail-closed (analytics/objectql: 跨对象(点号 lookup)维度聚合在 ObjectQL 路径上静默塌成 (null) 桶 / native SQL 报错 #3654), label lookups RLS-scoped (analytics: 让 executeAggregate 桥携带 ExecutionContext(#3597 的纵深防御第二层)+ 两处残留无 scope 调用 #3602), widgetdateGranularity/sortBy/limithonored (objectui#2864), funnel stage order,{current_user_id}in widget filters (objectui#2857)./i18n/labelsemits declared shape with help/options (GetFieldLabelsResponsedeclares rich label entries; both surfaces emit bare strings #3847); product-authored translations render (i18n: two unbridged bundle shapes — file-authoredobjects.<name>vs the runtimetranslationmetadata type'so.<object>, so translations cannot be authored in the product #3778); all ten Console packs at key parity (objectui#2909); system-settings hub localized (objectui#2859); SSO/SCIM/user-position/import-job admin objects translated.Seeds: X inserted · Y updated · Z skipped+ yellow REJECTED line (showcase: 4/5 projects and 9/10 tasks are silently rejected on every boot (FSM initialStates vs seed data) #3415/os dev: surface seed / sample-data-heal outcomes in the boot summary (info logs are swallowed by the default warn log level) #3430); marketplace rehydrate heals sample data.$orfilters AND their contents (driver-sql), datetime grouping not collapsing to(null)on SQLite (SQLite 上Field.datetime的 dateGranularity 分桶恒为 NULL —— 趋势图塌成一根柱子 #3773), empty group bucket is realnull(空分组桶的键两条路不一致:下推 SQL 给null,内存兜底给"(null)"(不限日期分桶) #3839).D. Docs verification (site)
/docs/releasesreachable from the left sidebar (restored in docs(releases,nav): v17 follow-up — Console gap backfill, sidebar restore, first docs sweep #3908) and/docs/releases/v17renders with Console section + upgrade checklist.docs/v17-docs-sweep.md(watermark:a641d10· 305 changesets · objectui4a4829d0ef39) — v17 is still iterating; append run 2+ to the log.E. Known out-of-scope items already logged
core-services.zod.ts'ai'member comment still says "NLQ, Chat, Suggest, Insights" (comment-only, spec-touching PR).content/docs/releases/implementation-status.mdxpredates the v17 window; needs its own regeneration pass.changeset pre exit, re-check the v17 page's version wording and this checklist's remaining items.