diff --git a/content/docs/releases/v15.mdx b/content/docs/releases/v15.mdx
index b51ffd14a8..f3d54ec816 100644
--- a/content/docs/releases/v15.mdx
+++ b/content/docs/releases/v15.mdx
@@ -1,6 +1,6 @@
---
title: v15.0.0
-description: Authorization kernel chain (ADR-0095) — tenant isolation becomes an independent Layer 0, a monotonic posture ladder derived from capabilities, cross-tenant write hardening, and record-grained access explanation. Breaking changes affect multi-org deployments only.
+description: Authorization kernel chain (ADR-0095) — tenant isolation becomes an independent Layer 0, a monotonic posture ladder, and record-grained access explanation. The 15.1 line adds attachments v1, declarative connectors (ADR-0096/0097), dashboard-level filters, pinyin search, and a broad write-path security hardening sweep.
---
**Authorization kernel hardening (ADR-0095) + supporting features (tracking framework#2920).**
@@ -14,7 +14,7 @@ are unaffected.
Read this before upgrading a multi-org or enterprise-hierarchy deployment.
-## Breaking changes
+## Breaking changes (15.0.0)
### Multi-org deployments only
@@ -61,7 +61,7 @@ Read this before upgrading a multi-org or enterprise-hierarchy deployment.
their position BU; multiple positions union; expired/inactive positions fall back
to membership.
-## Non-breaking hardening (same effective visibility)
+## Non-breaking hardening in 15.0.0 (same effective visibility)
- **RLS safety nets recognize canonical `==` (#2936).** The field-existence /
tenancy-disabled nets were inert for `==`-form policies; now recognized. Only
@@ -74,7 +74,7 @@ Read this before upgrading a multi-org or enterprise-hierarchy deployment.
now filters by organization directly, and private hierarchy-scoped objects that lack
an `organization_id` column fail closed at boot.
-## New features (additive)
+## New features in 15.0.0 (additive)
- **A3** — the `sys_user` record page gains **Permission Sets** (direct grant) and
**Business Units** assignment tabs.
@@ -94,8 +94,340 @@ Read this before upgrading a multi-org or enterprise-hierarchy deployment.
- **C1** — hierarchy RLS enterprise package reaches GA (subtree/reporting-chain caching,
customer docs, license gate).
+---
+
+# What's new in 15.1.0
+
+15.1 is a fast-follow minor with three themes: a **write-path security hardening
+sweep** (every hole below was verified exploitable on 15.0.0 and is now closed),
+**attachments v1** (parent-derived access, lifecycle, authenticated downloads), and
+**declarative connectors** (ADR-0096/0097 — REST/OpenAPI/MCP integrations wired
+purely from metadata). No `major` changesets: strict-semver edges are listed under
+*Behavior changes* below with their migrations.
+
+## Behavior changes (15.1.0)
+
+### Spec: `tenancy.strategy` and `tenancy.crossTenantAccess` removed (#2962)
+
+Strict-semver breaking, shipped in a minor under the launch-window policy: both
+fields had **zero consumers** and no runtime behavior. The `tenancy` block is now
+`.strict()` — unknown keys are a loud parse error with tombstone guidance instead
+of being silently stripped. **Migration:**
+
+- `tenancy: { enabled: false, strategy: 'shared' }` → `tenancy: { enabled: false }`
+- `tenancy: { enabled: true, strategy: '…', tenantField: 'x', crossTenantAccess: false }`
+ → `tenancy: { enabled: true, tenantField: 'x' }`
+- Per-tenant databases are a deployment concern (environment-per-database, ADR-0002),
+ not object metadata. Cross-tenant visibility is expressed via sharing rules / OWD
+ (`externalSharingModel`).
+
+### 'ObjectOS' retired from the spec's public surface (#2963, #2960)
+
+Layer vocabulary converges on **ObjectQL (data) / Kernel (control) / ObjectUI (view)**;
+"ObjectOS" now refers exclusively to the commercial runtime environment. Three
+public identifiers renamed, with deprecated aliases kept for one release:
+`ObjectOSCapabilitiesSchema` → `KernelCapabilitiesSchema`, `ObjectOSCapabilities` →
+`KernelCapabilities`, `ObjectOSKernel` → `IKernel` (`PluginContext.os` is now typed
+`IKernel`). Find/replace is the whole migration; schema shapes and JSON output are
+unchanged. Docs moved from `protocol/objectos` to `protocol/kernel`.
+
+### Anonymous access denied uniformly across HTTP surfaces (#2567)
+
+Under `requireAuth` (the secure default), anonymous `POST /graphql` and the raw
+`/data` routes now return 401 — previously they bypassed the gate and reached
+ObjectQL directly. The dispatcher's `requireAuth` default aligns with the REST
+plugin (`?? true`). **Migration:** deployments that genuinely serve these surfaces
+publicly must opt out explicitly with `requireAuth: false` (boot warns). All
+anonymous-deny seams converge on one shared `shouldDenyAnonymous`, and the
+authz-conformance matrix ratchets newly added `/data`/`/meta`/`/graphql` routes.
+
+### Server-enforced `readonly` / `readonlyWhen` on UPDATE (#2948, #3042, #3003)
+
+`readonly: true` was documented as a server contract but only enforced in the UI —
+#3003 confirmed approval/status/amount columns could be forged via a plain REST
+`PATCH` on 15.0.0. Non-system UPDATEs now **strip** caller-supplied writes to static
+`readonly` fields, and `readonlyWhen` predicates are enforced on the multi-row
+UPDATE path too. Server stamps (audit hooks), write middleware, and `isSystem`
+writes (import/seed/approvals/lifecycle) are unaffected. Multi-row semantics are
+fail-safe: if **any** matched row locks a field, that field is dropped for the whole
+batch — narrow the `where` to unlocked rows to write it.
+
+### Ownership anchor (`owner_id`) is guarded (#3004, #2982)
+
+Unprivileged writers can no longer forge another user's `owner_id` on insert or
+reassign it on update; transfers require a transfer grant (`allowTransfer`, or
+`modifyAllRecords` which implies it). Empty `owner_id` on insert is stamped to the
+current user — now on bulk rows too (previously bulk-inserted rows were NULL-owned
+and invisible even to their creator). Bulk writes (`update({multi:true})`, bulk
+delete) are now scoped by the caller's owner/RLS **write** filter and fail closed if
+the scoping AST is missing. Engine-internal referential cascades (`set_null` on user
+deletion) are exempted via a server-side marker that cannot be forged (#3048).
+**Note:** REST import runs as the importing user — a non-privileged user importing a
+CSV with someone else's `owner_id` column is now correctly rejected.
+
+### MCP action surface gated on `ai.exposed`, fail-closed (#2964)
+
+Action bodies execute as trusted code, yet MCP `run_action` could invoke any
+headless action. Actions that should remain invocable by AI agents must now declare
+`ai: { exposed: true, description: '…' }` (description ≥ 40 chars). Other surfaces
+(UI, REST `/actions/...`) are unchanged. Flow-type actions now receive the caller's
+identity as a real `AutomationContext` (`runAs: 'user'` flows evaluate RLS as the
+caller), and trusted-body invocations are audit-logged on both MCP and REST paths.
+
+### Declarative stdio MCP transports are deny-by-default (#3055)
+
+A declarative `stdio` transport spawns a local child process from metadata — and
+Studio runtime publish can introduce such metadata. Previously-materializing
+declarative stdio instances are now treated as a **configuration error** (boot
+fatal / reload skip), never retried as "upstream unavailable". **Migration:** hosts
+opt in explicitly — `new ConnectorMcpPlugin({ declarativeStdio: ['my-mcp-server'] })`
+(command allowlist) or `{ declarativeStdio: true }` (full trust). `http` transports
+and hand-wired connectors are unaffected.
+
+### OWD posture enforced at metadata-save time (#3050)
+
+A new `registerAuthoringGate` seam in `metadata-protocol` runs pre-persistence for
+draft and publish saves (environment writes only). `plugin-security` registers the
+object posture gate on it: an environment overlay over a packaged object can only
+**tighten** `sharingModel` / `externalSharingModel` (ADR-0086 D1), and
+`externalSharingModel ≤ sharingModel` (ADR-0090 D11) is now rejected at save time
+rather than only flagged by CLI lint. Already-stored metadata loads unchanged.
+
+### Attachments: dead fields removed, downloads authenticated (#2755, #2970)
+
+- **REMOVED: `sys_attachment.share_type` / `sys_attachment.visibility`** — modeled
+ in v1 but with zero runtime consumers. No replacement key: access derives from the
+ parent record. Stop sending these fields (unknown-field validation rejects them);
+ existing DB columns remain as unmanaged leftovers.
+- **Attachment downloads are no longer anonymous capability URLs.** For
+ `scope === 'attachments'` files that are not `public_read`,
+ `GET /storage/files/:fileId` and `/url` require a session (401 `AUTH_REQUIRED`)
+ plus parent-derived read access (403 `ATTACHMENT_DOWNLOAD_DENIED`) and return a
+ short-lived signed URL (default `downloadTtl` 300s). Field files, avatars, and org
+ logos (embedded in `
`) keep stable anonymous URLs.
+- **`sys_attachment.enable.trash` is now `false`** — attachment deletion is a hard
+ delete (declaring the honest state; reap guards reclaim bytes).
+- **Attaching to a record now requires parent EDIT** (was: parent READ), matching
+ Salesforce semantics.
+
+### Other semantics worth knowing
+
+- `ConnectorSchema.authentication` defaults to `{ type: 'none' }` (a relaxation;
+ existing connectors unaffected).
+- Aggregate queries gate FLS on the **input** side: a groupBy/aggregate referencing
+ an FLS-unreadable field is rejected fail-closed with the field named (#2976).
+- `findData` rejects unknown `$`-prefixed query params with 400
+ `UNSUPPORTED_QUERY_PARAM` instead of silently matching zero rows (#2926 ⑩).
+- The FormView `buttons` + `defaults` config (#3014) is **EXPERIMENTAL — declared
+ in the spec but not yet enforced by the Console renderer**.
+
+## Security hardening (15.1.0)
+
+Beyond the behavior changes above, an execution-surface sweep (#2849 class) closed
+three confirmed-exploitable holes (#2975):
+
+- **Reports IDOR + scheduled-report RLS bypass (#2980).** `ReportService` dropped
+ the caller context and read/wrote `sys_saved_report` as system — any authenticated
+ user could read/delete/overwrite any saved report by id, and scheduled dispatch
+ emailed entire tables. Now owner-scoped; scheduled dispatch resolves the owner's
+ real RLS context and **fails closed** when it can't.
+- **Knowledge/RAG fall-open (#2981).** `applyPermissionFilter` returned all hits
+ when the context was missing — an agent omitting `ToolExecutionContext.actor` got
+ unfiltered semantic search over the whole corpus. Missing identity now fails closed.
+- **`$expand` cross-object leak (#2850).** Expanding a reference resolved the
+ target rows directly against the driver, skipping the referenced object's RLS and
+ FLS entirely. Expansion now goes through the engine read path; PUBLIC referenced
+ objects are exempted only from the object-level CRUD gate via a server-side marker.
+- **Public-form anchor forgery (#3022).** Zero-declared-field public forms merged
+ the raw body wholesale, letting anonymous visitors POST `owner_id` /
+ `organization_id` / audit columns / `id`. Server-managed fields are now stripped at
+ the data layer and excluded at the route layer, from a single shared definition.
+- **Seed-not-clobber (#2909).** An admin's `active: false` on an overshared sharing
+ rule — and admin-edited capability `scope` values — are no longer resurrected by
+ boot seeding (`managed_by` / `customized` provenance columns land).
+- **`sys_position` system-row write gate re-armed (#2926 ①)** after the A4
+ `managed_by` vocabulary rename had silently disarmed it.
+- **better-auth pinned to 1.7.0-rc.1** for GHSA-p2fr-6hmx-4528, with the ObjectQL
+ adapter implementing the new `consumeOne`/`incrementOne` contracts and `sys_jwks`
+ gaining `alg`/`crv` columns (#2974).
+
+## New capabilities in 15.1.0
+
+### Attachments v1 (#2755, #2970)
+
+Parent-derived access on the full chain (create/read/list/download/delete —
+`sys_attachment` reads inherit parent-record visibility; aggregate counts don't leak
+hidden rows), authenticated short-lived download URLs, orphan lifecycle for
+`sys_file` (tombstone on last detach, TTL'd reap with a `registerReapGuard` seam
+that re-verifies zero references and deletes storage bytes first), and
+`sys_upload_session` reaping that aborts backend multipart uploads before the row is
+reclaimed (abandoned S3 parts stop accruing). Console ships authenticated uploads
+with friendly denial copy, signed-URL downloads, and file/image upload cells inside
+inline line-item grids; the showcase demonstrates per-line Receipt attachments on
+invoice lines (#3051).
+
+### Declarative connectors (ADR-0096 / ADR-0097)
+
+`connectors:` metadata entries with a `provider` (`openapi` / `mcp` / `rest`)
+materialize at boot into dispatchable connectors — an AI or a package can wire an
+integration end-to-end in pure metadata and call it from flow `connector_action`
+nodes (#2994). Credentials go through `credentialRef` (env-var resolution in the
+open profile; vault/OAuth2 per ADR-0015 are enterprise). Boot fails loudly on
+unknown providers, invalid `providerConfig`, unresolvable credentials, or name
+collisions; descriptor-only entries (actions with no runtime registration) get a
+boot audit warning unless `enabled: false` (#2985). Metadata reloads reconcile
+incrementally (#3001), unreachable upstreams degrade to an action-less husk with
+`state: 'degraded'` and exponential-backoff retry instead of failing boot (#3049),
+OpenAPI specs load from inline objects, package-relative file paths (confined), or
+URLs (#3024), and `GET /api/v1/automation/connectors` reports each descriptor's
+`origin` (`plugin` | `declarative`) — which the flow-designer connector picker
+surfaces (objectui#2563). The showcase runs a live `provider:'mcp'` demo pinned in
+CI (#3062).
+
+### Dashboard-level filters (#2501)
+
+`GlobalFilterSchema.name` gives each dashboard filter a variable key (readable as
+`page.` in widget expressions; `"dateRange"` reserved), and
+`DashboardWidgetSchema.filterBindings` lets each widget retarget a filter to its own
+field or opt out with `false` (#3035). The Console renders a dashboard filter bar,
+AND-merges active values into every bound widget's query, ships a visual
+filterBindings editor in the widget inspector, resolves `optionsFrom` via
+server-side distinct, and localizes the bar (objectui#2576/#2586/#2590). The
+showcase "Revenue Pulse" dashboard demonstrates interactive filters driving multiple
+charts over two objects (#3038).
+
+### Pinyin search (#3027)
+
+Locale-gated (`OS_SEARCH_PINYIN_ENABLED`, auto-on when any `zh-*` locale is
+configured): display/name fields get a hidden `__search` companion column filled
+with full pinyin + initials ("张伟" → `zhangwei zw`) on save, backfilled at boot,
+and OR-ed into `$search` — lookup pickers, list quick search, and ⌘K transparently
+match `zhangwei` / `zw` against CJK values. FLS-restricted/secret/PII fields are
+never indexed.
+
+### Metadata & spec
+
+- **Conditional tabs (#2967):** `page:tabs` items accept a `visibleWhen` CEL
+ predicate over `record` + `current_user` + `page.`; FALSE removes the whole
+ tab, re-evaluated live as page variables change.
+- **View property liveness (#3046):** all 83 view properties classified
+ (68 live / 2 experimental / 5 dead); `os build` warns on dead props with fix-it
+ hints.
+- **Feature-gate registry (#2965):** actions/params declare `requiresFeature`,
+ lowered at parse time to canonical `visible` CEL — 17 capability-dependent actions
+ (platform user management, 2FA, OAuth apps) now hide when their plugin is off
+ instead of rendering 404 buttons.
+- **Page variable `source` renders as a component picker** in Studio (#2968).
+
+### AI / MCP
+
+- **`aggregate_records` MCP tool (#2976):** count/sum/avg/min/max/count_distinct
+ with groupBy (incl. date bucketing), where, and IANA timezones — on the ObjectQL
+ ENGINE read path, so RLS/tenant/delegation intersect exactly like `find`.
+- **Standalone authored actions reach the MCP bridge (#3010):** `list_actions` /
+ `run_action` now union `object.actions` with standalone `action` metadata rows —
+ Studio-authored actions with `ai.exposed: true` are callable by agents without
+ re-declaring them inside the object.
+
+### Deployment
+
+- **Official Docker runtime image (#2952):** `ghcr.io/objectstack-ai/objectstack`
+ (Node 22, pinned CLI, `os start`, non-root, `/api/v1/health` HEALTHCHECK,
+ multi-arch). Deployment is a two-line Dockerfile — or plain `docker run` with a
+ mounted artifact / `OS_ARTIFACT_PATH` URL.
+- **`create-objectstack` scaffolds container-ready projects (#2979):** Dockerfile
+ (two-stage onto the official image), docker-compose (app + Postgres),
+ `.dockerignore`.
+
+### Kernel & protocol
+
+- **`kernel:bootstrapped` lifecycle anchor (#2989):** fires after all `kernel:ready`
+ handlers settle, before `kernel:listening` — the correct anchor for
+ reconcile/backfill work (also fixed: `everyone` auto-bind ordering, so app-level
+ auto-bind happens on fresh deployments).
+- **`app:seeded` event (#3000):** membership backfill re-runs when background
+ seeding settles — seeded users no longer wait for a restart.
+- **ADR-0087 completed (#2972):** the protocol handshake now runs on boot-time
+ durable-package rehydration (incompatible `sys_packages` rows are refused with
+ structured `OS_PROTOCOL_INCOMPATIBLE` diagnostics, boot continues) and on
+ code-defined `AppPlugin` loads (fail-fast); conversion chain backfilled so
+ `migrate meta --from 10` reaches protocol 15; `spec-changes.json` ships with the
+ npm artifact; the upgrade guide is generated and drift-checked.
+- **Honest capability discovery (ADR-0076 slice):** services self-describe
+ stub/degraded/fallback status instead of discovery hardcoding `available`;
+ discovery stops advertising a `/realtime` route and `websockets: true` that
+ always 404'd.
+
+### i18n
+
+- `os i18n extract` emits action-param keys — action dialog forms (e.g. Setup →
+ Create User) are translatable; platform bundles regenerated for
+ en/zh-CN/ja-JP/es-ES (#3030).
+- Audit/feed activity summaries localize to the workspace locale (ADR-0053 verb
+ templates, localized object labels) with English fallback (#3045, #3029).
+
+## Notable fixes (15.1.0)
+
+- **Studio metadata editing under spec 15:** lazy schema proxies stay
+ identity-compatible with `z.toJSONSchema` — Page/View inspectors no longer crash
+ (#3021).
+- **Sharing rules apply to seeded demo data out of the box:** an idempotent boot
+ backfill materializes `sys_record_share` for seed-loaded records (#2926 ③).
+- **Package management REST:** `PATCH /packages/:id` is actually routed (#2995);
+ duplicate `POST /packages` ids return 409 (#2971); `packages.install` passes
+ `overwrite` for upgrade flows (#3007).
+- **Create User:** an explicitly typed password wins over the dialog's default
+ `generatePassword: true` instead of erroring 400 (#3031).
+- **REST status passthrough:** record-level 403s from sharing no longer degrade to
+ a bare 400 (#2926 ⑦).
+- `gen:schema` regression ratchet restores the lost `PageTabsProps` schema and fails
+ CI on future disappearances (#3012).
+
+## New in Console (Studio) — bundled objectui 14.1
+
+The pinned Console picks up the objectui 14.1 line (94 commits). Highlights:
+
+- **Record-level inline edit (#2542, #2549, #2604):** one shared draft across the
+ detail body *and* the highlight strip, one atomic Save
+ (`update` with `ifMatch` optimistic concurrency, only edited keys sent), 409
+ conflict dialog, approval-lock preflight, Esc / Cmd+Enter shortcuts.
+- **CEL editors everywhere (#1582 series):** the formula editor edits the engine's
+ real `expression` key (the old textarea wrote a `formula` key the engine never
+ read) and live-infers the result type; conditional rules
+ (visibleWhen/readonlyWhen/requiredWhen), list-view conditional formatting, RLS
+ policies (with lint, autocomplete, and per-policy test-run against a real record),
+ and condition-builder escape hatches all get lint + field autocomplete instead of
+ bare textareas. Summary fields get a structured roll-up editor.
+- **Dashboard filter bar + visual `filterBindings` editor** (see Dashboard-level
+ filters above).
+- **Studio Access pillar:** package-level OWD overview with inline batch edit and
+ `external ≤ internal` validation (#2508); unsaved-changes guards across matrix
+ edits, SPA header navigation, and OWD rows (#2588/#2606/#2610); read-only packages
+ actually lock the matrix (#2570); record-grained AccessExplainPanel (#2502);
+ three-state provenance badges (#2501).
+- **Flow designer:** searchable add-node palette with keyboard navigation and
+ cloud-synced recents (#2543/#2553); Decision branches pick their target node
+ inline (#2568); the connector picker lists runtime-dispatchable connectors and
+ marks declarative instances (#2563); positions as approver/escalateTo references
+ (#2515).
+- **Kanban:** lanes honor the ADR-0085 `stageField` role, cards default to
+ `highlightFields`, conditional formatting accepts CEL rules (#2596/#2541/#2550).
+- **Record header:** metadata-driven multi-button layout (3 desktop / 1 mobile,
+ `order` + `variant:'primary'` tie-break, ⋯ overflow menu) (#2574).
+- **Fixes:** error-envelope objects no longer crash the page via `toast.error`
+ (React #31, #2580); bare-string sort from spec forms no longer crashes ListView
+ (#2601); CSV import sniffs encoding with a GB18030 fallback — zh-CN Excel CSVs
+ import cleanly (#2557); Studio/metadata-admin follow the in-app locale instead of
+ `navigator.language` (#2602); relative dates localize via `Intl.RelativeTimeFormat`
+ (#2593); action-visibility traps defused — `os.user.*` alias in client predicate
+ scope, `record_more` placement, loud warnings for throwing predicates (#2611);
+ related lists respect the current user's child-object read permission (#2565);
+ FLS fail-open fixed (#2537).
+
## Upgrade checklist
+### 15.0.0
+
- **Multi-org:** review any workflow that relies on members editing each other's records
org-wide (grant an explicit edit permission set) and any integration that supplied a
cross-tenant `organization_id` on user-context writes (route it through system context
@@ -107,7 +439,27 @@ Read this before upgrading a multi-org or enterprise-hierarchy deployment.
end-to-end smoke test in a licensed environment is still owed** — open-core CI cannot
exercise the multi-org path.
+### 15.1.0
+
+- Remove `tenancy.strategy` / `tenancy.crossTenantAccess` from object metadata
+ (parse now fails loudly).
+- Rename `ObjectOSCapabilitiesSchema` / `ObjectOSCapabilities` / `ObjectOSKernel`
+ imports to `KernelCapabilitiesSchema` / `KernelCapabilities` / `IKernel`
+ (deprecated aliases last one release).
+- If you serve `/graphql` or raw `/data` anonymously on purpose, set
+ `requireAuth: false` explicitly.
+- Audit clients that write `readonly` fields or `owner_id` — those writes are now
+ stripped/denied; grant `allowTransfer` where ownership transfer is legitimate.
+- Declare `ai: { exposed: true, description }` on actions AI agents should keep
+ invoking via MCP.
+- Stop sending `sys_attachment.share_type` / `visibility`; expect authenticated,
+ short-lived attachment download URLs; attaching now requires parent EDIT.
+- Hosts using declarative **stdio** MCP connectors must opt in via
+ `ConnectorMcpPlugin({ declarativeStdio: [...] })`.
+
## References
ADR-0095 (kernel chain) · ADR-0090 Addendum (position-anchor) · ADR-0094 (overlay
-preservation) · framework#2920 (tracking).
+preservation) · framework#2920 (tracking) · ADR-0096/0097 (declarative connectors) ·
+ADR-0087 (protocol handshake) · ADR-0086 D1 / ADR-0090 D11 (OWD authoring gate) ·
+#2849 (execution-surface sweep) · #2970/#2755 (attachments v1).
diff --git a/docs/plans/release-15.1-test-plan.md b/docs/plans/release-15.1-test-plan.md
new file mode 100644
index 0000000000..cb98cced3c
--- /dev/null
+++ b/docs/plans/release-15.1-test-plan.md
@@ -0,0 +1,112 @@
+# 15.1 发布测试清单
+
+> 范围:framework `@objectstack/*@15.0.0 → 15.1.0`(87 commits, 65 changesets)+
+> 配套 Console `objectui 14.0.0 → 14.1.0`(94 commits,framework 钉在 `b8967495be73`)。
+> 环境:`examples/app-showcase` + `objectstack dev --ui --seed-admin`(独立端口 + 独立 file DB)。
+> 标注:🖥 = 浏览器验证;🔌 = API/CLI 验证;🤖 = 已有自动化钉死(dogfood/e2e 测试,CI 覆盖)。
+
+## A. 安全加固(本次最大主题)
+
+| # | 项 | 方式 | 验证要点 | 来源 |
+|---|---|---|---|---|
+| A1 | 静态 `readonly` UPDATE 强制 | 🔌🤖 | REST PATCH 一个 `readonly: true` 字段 → 该字段被 strip,响应中值不变;`isSystem` 写不受影响 | #2948/#2957/#3003,`showcase-static-readonly.dogfood.test.ts` |
+| A2 | `readonlyWhen` 多行 UPDATE 强制 | 🔌🤖 | bulk update 命中任一锁定行 → 字段对整批丢弃 | #3042 |
+| A3 | `owner_id` 锚点:insert 伪造/update 转移拒绝、批量 stamp | 🔌🤖 | 非特权用户 insert 他人 owner_id → 拒;bulk insert 空 owner → stamp 为当前用户 | #3004/#3018,`owner-anchor-and-bulk-writes.dogfood.test.ts` |
+| A4 | bulk write 按 owner/RLS write filter 收窄 | 🔌🤖 | member 对 OWD-private 对象 `update({multi:true})` → 只命中自己行 | #2982 |
+| A5 | 引擎级联豁免(删用户 set_null 不被 A3 拦) | 🔌🤖 | 删除 sys_user → 其拥有记录 owner_id 被正常清空,级联不中断 | #3023/#3048 |
+| A6 | `$expand` 强制被引对象 RLS/FLS | 🔌🤖 | 以受限用户 `?expand=` 引用 → RLS 隐藏行不返回、FLS 字段掩码 | #2850/#2961 |
+| A7 | public form 剥离 server-managed 锚点 | 🖥🔌 | 公开表单渲染不出现 owner_id/organization_id 等;POST 携带被剥离 | #3022/#3036 |
+| A8 | 匿名 deny 全 HTTP 面统一 | 🔌🤖 | 未登录 `POST /graphql`、raw `/data` → 401(`requireAuth` 默认) | #2567 系列,`showcase-anonymous-deny-surfaces.dogfood.test.ts` |
+| A9 | MCP `run_action` 需 `ai.exposed`,fail-closed | 🔌 | 未声明 action 经 MCP 调用被拒;声明后可调且入 audit log | #2964 |
+| A10 | reports IDOR / 定时报表 RLS / RAG fall-open | 🔌🤖 | 跨 owner 按 id 读/删 saved report → 拒 | #2980/#2981/#2975 |
+| A11 | OWD 保存门:packaged 对象只能收紧 sharingModel;`external ≤ internal` | 🖥 | Studio 里把 packaged 对象 OWD 往宽改 → 保存被拒;OWD 总览 inline 校验 | #3050 + objectui#2508 |
+| A12 | 附件访问链(读继承 parent、下载 401/403+签名 URL、attach 需 parent EDIT) | 🖥🔌🤖 | 见 C 组;非授权下载 → 401/403 | #2755/#2970 |
+| A13 | sharing rule seed-not-clobber + capability scope seed-once | 🔌 | 管理员改动不被重启 seed 覆盖 | #2909 |
+| A14 | better-auth 1.7.0-rc.1(GHSA 修复)登录链路 | 🖥 | 登录成功且后续请求 200(sys_jwks alg/crv 列) | #2974 |
+
+## B. 声明式 Connector(ADR-0096/0097)
+
+| # | 项 | 方式 | 验证要点 | 来源 |
+|---|---|---|---|---|
+| B1 | provider-bound 实例 boot materialization | 🔌🤖 | showcase live `provider:'mcp'` demo:boot 后 `GET /api/v1/automation/connectors` 有该实例,`origin:'declarative'` | #2994/#3062 |
+| B2 | flow designer connector picker 标注声明式实例 | 🖥 | connector_action 节点下拉列出实例并带标注 | objectui#2563 |
+| B3 | 上游不可达 → degraded husk + 退避重试,恢复原子换入 | 🔌 | 断掉 upstream 重启 → boot 不挂,descriptor `state:'degraded'` | #3049 |
+| B4 | declarative stdio 默认拒绝,host allowlist opt-in | 🔌 | 未 opt-in 的 stdio 实例 → boot fatal/reload skip | #3059 |
+| B5 | openapi spec 文件路径(package 相对,拒绝逃逸) | 🔌 | `providerConfig.spec: './openapi.json'` 可解析;`../` 拒 | #3024 |
+| B6 | descriptor-only 契约 boot 审计 | 🔌 | actions 无运行时注册 → boot warning;`enabled:false` 静音 | #2985 |
+
+## C. 附件 v1
+
+| # | 项 | 方式 | 验证要点 | 来源 |
+|---|---|---|---|---|
+| C1 | 记录详情 RecordAttachmentsPanel 认证上传/下载 | 🖥 | 上传成功;下载经签名 URL;无权限时友好拒绝文案 | objectui#2532 |
+| C2 | 发票行 per-line Receipt(inline grid 内上传单元格) | 🖥 | showcase Invoice → 行内 Receipt 列出现上传控件 + chips/缩略图,非文本框 | #3051 + objectui#2585 |
+| C3 | 附件读继承 parent 可见性 | 🔌🤖 | 受限用户列附件 → 不可见 parent 的行与计数均不出现 | #2970 |
+| C4 | sys_file 孤儿 tombstone/reap + upload_session 报废(abort multipart) | 🔌🤖 | 生命周期测试覆盖 | #2755/#2970 |
+
+## D. Dashboard 全局筛选
+
+| # | 项 | 方式 | 验证要点 | 来源 |
+|---|---|---|---|---|
+| D1 | Revenue Pulse:filter bar 驱动多图表 | 🖥 | 切 filter/日期预设 → 多 widget 同步 re-scope;opt-out widget 不动;dirty 出 Reset | #3038/#2576 |
+| D2 | widget inspector 可视化 filterBindings 编辑 | 🖥 | Studio 选中 widget → "Dashboard filter bindings" 区,开关 Apply/换字段 | objectui#2586 |
+| D3 | spec-form filter options 不再崩 dashboard;select 显示 label | 🖥 | 有 `{value,label}` options 的 dashboard 正常渲染 | objectui#2597 |
+
+## E. Console 核心体验(objectui 14.1)
+
+| # | 项 | 方式 | 验证要点 | 来源 |
+|---|---|---|---|---|
+| E1 | Record 级 inline edit:共享 draft + 单次原子 Save | 🖥 | 双击正文字段 + highlight 各改一处 → 同一 Save 条;Network 仅一次 update(只含改动 key + ifMatch);Esc/Cmd+Enter | objectui#2542/#2549/#2604 |
+| E2 | 多按钮 record header(order + primary tie-break,⋯ 溢出) | 🖥 | ≥3 动作对象详情页头并排按钮;Delete/Share 进 ⋯ | objectui#2574 |
+| E3 | related list 权限门控 | 🖥 | 对子对象无 read 权限用户 → section 整体不渲染 | objectui#2565 |
+| E4 | kanban:stageField 默认 lane + highlightFields 默认卡片 | 🖥 | 未配 lane/cardFields 的看板按语义角色渲染 | objectui#2596/#2541 |
+| E5 | 拼音搜索:列表快搜/lookup picker/⌘K 输 `zw` 命中中文 | 🖥 | showcase 种子开箱可演示 | #3027/#3034 |
+| E6 | conditional tabs(page:tabs item visibleWhen) | 🖥 | 谓词为 FALSE 时整个 tab 消失,变量变化 live 重估 | #2967 + objectui#2516 |
+| E7 | B3 级联选项:client re-filter + server 拒绝 | 🖥🤖 | cascading-select fixture;live e2e 已钉 | #3006 + objectui#2547/#2562 |
+| E8 | GB18030 CSV 导入 | 🖥 | 导入 GBK 编码 CSV → 中文表头正常、可映射 | objectui#2557 |
+| E9 | toast 不再因 error 对象崩页(React #31) | 🖥 | 触发 400 的操作 → 可读 toast,页面不白屏 | objectui#2580 |
+| E10 | Studio/metadata-admin 跟随应用 locale | 🖥 | 切 en/zh → Studio 同步切换,无混语言 | objectui#2602 |
+| E11 | 活动 feed/audit 摘要 workspace locale 本地化 | 🖥 | zh 环境下动词模板 + 对象 label 中文 | #3045/#3029 |
+| E12 | Create User:param 可翻译 + 显式密码胜出 | 🖥 | zh 下对话框字段中文;输显式密码不报 400 | #3030/#3031/#3033 |
+| E13 | action visible 三陷阱(os.user.* 别名/record_more/报错谓词 warn) | 🖥🤖 | `locations:['record_more']` 动作出现在 ⋯ 菜单 | objectui#2611 |
+
+## F. Studio(设计期)
+
+| # | 项 | 方式 | 验证要点 | 来源 |
+|---|---|---|---|---|
+| F1 | CEL formula 编辑器:推断结果类型、summary 结构化 roll-up | 🖥 | `record.amount * 1.1` → Result type: Number;裸字段名报错给修法 | objectui#2609 |
+| F2 | 字段条件规则 CEL 编辑器(visibleWhen/readonlyWhen/requiredWhen) | 🖥 | `previous.` 成员补全 + did-you-mean | objectui#2571 |
+| F3 | list-view 条件格式化编辑器(first-match-wins) | 🖥 | 加 CEL+颜色规则 → 运行时行着色 | objectui#2558/#2544 |
+| F4 | RLS 编辑器 lint/补全/test-run | 🖥 | 每策略选记录试跑命中 | objectui#2533 |
+| F5 | Access:OWD 总览批量编辑 + 深链 + external≤internal 校验 | 🖥 | 见 A11 | objectui#2508 |
+| F6 | Access:unsaved 三连守卫(矩阵/pillar SPA 导航/OWD 行) | 🖥 | 不保存点走 → confirm;取消保留编辑 | objectui#2588/#2606/#2610 |
+| F7 | 只读包锁定权限矩阵 | 🖥 | read-only 包矩阵置灰、无 Save | objectui#2570 |
+| F8 | AccessExplainPanel record 粒度 + 三态溯源徽标 | 🖥 | 选记录 → visible 结论横幅 + 逐层归因 | objectui#2502/#2501 |
+| F9 | flow palette 搜索/键盘导航/云同步 recents | 🖥 | 搜索框 + ↑↓ Enter;recents 分组 | objectui#2543/#2553 |
+| F10 | Decision Branches 行内 Target 选择 | 🖥 | 表内选目标 → 画布连边;清除只断边 | objectui#2568 |
+| F11 | Page/View inspector 不崩(lazySchema × toJSONSchema) | 🖥 | Studio 打开 Page/View 编辑面 → 无 `setting 'ref'` 崩溃 | #3021 |
+| F12 | package spec-form modal + namespace 前缀编辑期强制 | 🖥 | New package modal;"+ new object" 自动 `_` 前缀 | objectui#2535/#2524 |
+| F13 | feature-gated action UI 门控 | 🖥 | 关插件 → 相关按钮消失而非 404 | #2965 + objectui#2536 |
+
+## G. 平台 / 协议 / 部署(API·CLI 面)
+
+| # | 项 | 方式 | 验证要点 | 来源 |
+|---|---|---|---|---|
+| G1 | `aggregate_records` MCP 工具走 ENGINE 读路径 | 🔌 | RLS/tenant 与 find 一致;FLS 输入门 fail-closed | #2976 |
+| G2 | standalone authored action 上 MCP 桥 | 🔌 | Studio 授权独立 action `ai.exposed:true` → list_actions 可见 | #3010/#3020 |
+| G3 | packages REST:PATCH 路由/POST 重复 409/install overwrite | 🔌 | 三条包管理修复 | #2995/#2971/#3007 |
+| G4 | ADR-0087:durable-package rehydration 握手 + `migrate meta --from 10` | 🔌 | 不兼容包结构化拒载,boot 继续 | #2972 |
+| G5 | honest capabilities:discovery 不虚报 realtime/stub 状态 | 🔌 | discovery JSON 无 `/realtime` 虚假路由 | ADR-0076 slice |
+| G6 | `kernel:bootstrapped` + `app:seeded` membership backfill | 🔌 | seed 完成后无需重启即有 membership | #2989/#3000 |
+| G7 | Docker 镜像 + 脚手架 container-ready | 🔌 | `docker run` + `/api/v1/health` 200 | #2952/#2979 |
+| G8 | tenancy strict block:未知 key 响亮 parse error + tombstone | 🔌 | `strategy:` 残留 → 报错含迁移指引 | #2962 |
+| G9 | 拼音 `__search` 伴随列 boot backfill + reconcile | 🔌 | 存量行重启后可拼音搜到 | #3027 |
+| G10 | REST 403 透传 / 未知 `$` 参数 400 | 🔌 | 记录级 403 带 code;`$bogus=1` → 400 UNSUPPORTED_QUERY_PARAM | #2926 ⑦⑩ |
+
+## 回归门(发布前必须全绿)
+
+- [ ] `pnpm build` 全绿(71 包)
+- [ ] `pnpm test` 全绿(含 dogfood 套件:owner-anchor / static-readonly / anonymous-deny / attachments matrix / detail-shapes e2e / MCP connector CI-pinned demo)
+- [ ] objectui `e2e/live/showcase-smoke.spec.ts`(全 showcase surface 白屏/空图表/占位符泄漏扫描)
+- [ ] `pnpm check:release-notes`(release 页 drift guard)
+- [ ] `os validate` 对 examples(schema+表达式+widget 三道门)