| title | v15.0.0 |
|---|---|
| description | Record-level access explanation, a docked AI workspace in the Console, attachments that follow record permissions, no-code third-party connectors, dashboard-wide filters, and Chinese pinyin search — with materially safer multi-tenant defaults. Backend and Console notes for 15.0.0 and 15.1.0. |
The v15 line makes the platform explain itself and lets you build more without code: administrators can ask why a specific user sees a specific record and get a layer-by-layer answer; the Console gains a docked AI workspace; attachments, third-party connectors, dashboard-wide filters, and Chinese pinyin search all become configuration instead of custom work — while multi-tenant isolation and the write path get materially safer by default.
- Answer "why can this user see this record?" in one click. Access explanation now reaches individual records: pick a user, an object, and a record, and the panel walks every layer (permission set → position → sharing → row rules) to a final verdict with the deciding layer named. Permission debugging turns from guesswork into reading the answer.
- Multi-tenant data stays in its lane — by construction. Tenant isolation now always runs first, before any business rule. A permissive sharing policy can no longer accidentally expose another organization's records, forged cross-tenant writes are blocked, and org admins no longer cross the wall on private objects.
- An AI workspace built into the Console. The assistant docks on the right and works side-by-side with your page instead of covering it — ask questions in any app, hand off to the builder with one click (your conversation follows), see your usage at a glance, and get the same experience as a bottom sheet on mobile.
- Gantt charts ready for real project management. Connect external API data sources with full write-back, drag to reschedule with per-scale snapping, dependency lines that refuse cycles and locked rows, and per-task overdue/due-soon warning strokes.
- Configuration mistakes surface immediately. A mistyped or misplaced key in view/page metadata now fails loudly at build time with a pointer to the correct spelling — instead of silently doing nothing in production.
- Sign in with a phone number, and sharing-rule forms become pick-not-type (searchable object pickers, visual condition builders, recipient pickers).
- Attachments that follow your permissions. Files attached to a record are visible exactly to the people who can see the record; downloads use short-lived signed links; and inline line-item grids upload files directly — drop a receipt image onto an invoice line.
- Connect third-party systems with pure configuration. Declare a REST, OpenAPI, or MCP connector in metadata and call it from flows — no plugin code. Unreachable upstreams degrade gracefully and retry themselves back to health.
- One filter bar drives the whole dashboard. Date and field filters at the top of a dashboard re-scope every chart at once; each widget can remap the filter to its own field or opt out (a KPI can stay "all time").
- Search Chinese names by pinyin. Typing
zhangweior justzwfinds 张伟 — in list search, lookup pickers, and ⌘K — with no per-object setup. - Edit a whole record inline. Double-click any field on a detail page, change several (header highlights included), and save once atomically. Formula fields get a real editor that validates as you type and infers the result type; row-security policies can be test-run against a live record before you save them.
- Everyday quality: GBK-encoded CSVs from zh-CN Excel import cleanly, imports run automations per row, exports use localized filenames, and the Studio guards you against losing unsaved permission-matrix edits.
- A3 — the
sys_userrecord page gains Permission Sets (direct grant) and Business Units assignment tabs (#2927). - A4 — record-level provenance vocabulary unifies to platform / package / admin
across
sys_capability/sys_permission_set/sys_position, surfaced as a badge and in list views; legacy values self-heal at boot (no destructive migration) (#2934). - A5 — package-level capability declaration API (
defineCapability): a package's own capabilities flow into the registry with package provenance (#2932). - B2/B4 — an explicit, monotonic posture ladder
(
PLATFORM_ADMIN > TENANT_ADMIN > MEMBER > EXTERNAL) derived from capability grants (never the identity provider's admin designation); an additive, derived, explainable field. Enforcement is behavior-preserving (#2938). - C2 —
security.explainextends to record granularity: explain why a specific record is visible/invisible to a user, layer by layer (permission set → position → sharing → row rule → effective row filter), with Layer 0/1 and posture surfaced in the Studio Access panel (#2928, #2939). - C1 — hierarchy RLS enterprise package reaches GA (subtree/reporting-chain caching, customer docs, license gate).
- Docs — a customer-facing Administrator Guide (onboarding task handbook for system admins) lands in the permissions docs (#2929).
15.0.0 bundles the objectui 14.0 Console major (13.2.0 → 14.0.0, ~73 commits, plus six early-14.1 commits picked up by the final pin). Much of this code was already riding along in the 14.8.0 bundle unversioned — this is its first release-page disclosure. The major itself is a version-line alignment with objectstack; the only code-level breaking change is the ADR-0057 chat cleanup below.
- The docked ChatDock becomes the console's only chat form (ADR-0057 final
cleanup, #2475). The
features.chatDockruntime config key is removed (it had already become a default-on kill-switch);ConsoleFloatingChatbotand itsagentPickerhelper are deleted;ConsoleChatbotFabbecomes a thin launcher with a new{ appLabel, onOpenDock }signature; the legacy left-panelStudioAiCopilotis deleted in favor of the right-dockedStudioChatDock. Migration: dropchatDockfrom runtime config; hosts that mounted the floating chat or old FAB props adopt the ChatDock. The ADR-0080aiSlotinjection seam and end-user SDUItype:'chatbot'bubbles are unaffected. - Chat sessions key on
(user, app, product)(#2414). The Studio copilot and/ai/build?package=Xnow share one thread; the oldstudio:-prefixed scopes are retired.
- Right-docked AI rail:
AppShellgains arightRailthat reflows content instead of overlaying it (VS Code-style); collapsible, drag-resizable, ⌘/Ctrl+Shift+I toggle (#2464). The FAB becomes a dock launcher (#2465);/aiis the dock's maximized form with two-way same-thread round-trips; Studio re-lays-out around the dock with central Canvas | Properties tabs (#2467); default-on (#2469). - Explicit ask→build handoff: an "Open in Builder →" card (decline-and-redirect, #2439) carries the ask-session context (#2444), with auto-send reliability and re-carry on a second handoff (#2449, #2452).
- Package binding: the build surface shows a
📦 <app>binding chip / "New app" (#2461); unbound sessions re-key toapp:X:buildthe moment a package is minted, and the chip upgrades to an app switcher (#2466) that hides the platform's built-in setup/account apps (#2474); edit mode gets its own empty state with change-oriented starters (#2476). - Capability-driven rendering: the console consumes per-agent
capabilitiesfromGET /api/v1/ai/agents(debug drawer ←debug, FAB resume ←resume, "Build with AI" ←authoring), with a name-based fallback for older servers (#2452). - Mobile: the dock renders as a bottom sheet (#2470); the sheet's maximize
bridges to full-page
/ai(#2483); Live Canvas becomes an opt-in full-screen "Preview app" pill instead of blowing out the viewport (#2492). - Polish: canvas auto-maximize easing and per-surface dock widths (#2480);
the Studio dock remembers collapse state per tab, side-by-side
canvas+properties from
xl(#2478, #2470).
- API data sources read and write back (reschedule / dependencies / delete /
inline edit through
read/writeHttpRequests; pure-api views need no objectName) (#2423, #2448). - Dependency-line validation: locked/group-row drop rejection, full-set
cycle detection, an
onBeforeDependencyCreatehost veto hook (#2437); candidates exclude group/locked rows, include summaries, and get a search box (#2459). - Per-task warning strokes via
borderColorField(overdue red / due-soon orange on bars, milestones, and summaries) (#2440); tree-aware quick filters keep ancestor chains (#2438); post-write read-back of server-computed fields plus a silent toolbar refresh preserving scroll/collapse (#2442);summaryExtent:'self'and schema-less tooltip fallbacks (#2455). - Interaction batch (#2463): click-to-locate with flash, double-click / 「→」/ context-menu / Enter to open details, day-snapped dragging across week/month/quarter/year scales, layouts persist collapse+filters, a mobile QR context action (taskUrl), lock tooltips; the 「→」detail button gets its own 24px action slot (#2487); full-height edge bands for duration drags (#2420).
- Row-level lock closes the loop:
RecordDetailDraweredit/delete capabilities are now caller-provided handlers — omitting them yields strict read-only (a generic plugin-detail change, #2441); the drawer fetches the real record + schema, write failures toast the server message, and locked rows disable the dependency menu (#2479).
- Localized export filenames —
<object label>-<view>-<timestamp>.<ext>(e.g.任务-In Progress-20260714.xlsx) via a newbuildExportFileName; import template sample rows use option display labels (#2491). - The toolbar search button shows the active keyword inline — a localStorage-restored invisible filter finally has a visible cue (#2472).
- Row-action column fixes: multiple primary actions stop clipping
(
maxInlineRowActionsconfigurable, #2484); the_actionscolumn auto-pins right and coexists with a frozen lead column (#2486). - Inline-edit hardening: schema-
readonlyfields and system/audit columns (created_at…) refuse the double-click editor (#2408).
- Sharing-rule forms pick, not type: three new widget-hint components —
object-ref(searchable object picker over a newDataSource.getObjects()),filter-condition(visual condition builder with raw-JSON fallback), andrecipient-picker(record picker that re-scopes withrecipient_type) (#2421); sectioned layouts respect fieldwidgethints so grouped forms stop degrading to bare text inputs (#2453). - Action params support
visibleCEL predicates — param dialogs hide fields by features/user/app/data scope (e.g. create-user's phone field disappears when the phoneNumber plugin is off) (#2406, #2419).
- Phone + password sign-in: the password mode recognizes an email or an
E.164 phone and routes by shape (better-auth phoneNumber plugin,
framework#2780;
features.phoneNumber-gated) (#2418). - Silent-failure UX: SSO button pending states,
signInWithProviderthrows without a redirect URL, OAuth callback error banners (?error=<code>) (#2468).
- Four-pillar
?surface=deep links restore and write back to the URL (useSurfaceDeepLink; Automations/Access catch up) (#2451); record pages' "Design" button deep-links to the object's design surface (#2446); the package manager's permission-set delete copy becomes "reset to factory baseline" (ADR-0094 semantics, #2494); form/page renderers read the normalizedvisibleWhenkey (visibleOn/visibilitydeprecated but tolerated — ADR-0089 D2, #2490). - i18n: the Profile page is fully translated;
pickLocalizedupgrades bare base-language codes (zh→zh-CN); managed-by badges and record quick actions localize; gaps filled across 8 languages (#2489). - Security: logout/user-switch clears plaintext AI chat caches from
localStorage (cross-user leak on shared machines, #2485); the console
MetadataClient sends Bearer auth — fixing Studio
/api/v1/meta/*401s on token-based deployments, with a ratchet test (#2410). - Fixes: live thinking indicator during ask-decline waits and instant handoff cards (#2496); refresh/history-restore keeps "Open in Builder →" and confirm-change cards (#2497); build summaries truncate on mobile (#2495).
- The Studio copilot dock's composer no longer vanishes on long sessions (flex height chain), and the properties inspector collapses to a slim rail (#2504).
- ImportWizard "run automations & triggers" defaults ON, matching the server's historical behavior (#2503, pairing #2922).
- Record-grained AccessExplainPanel (ADR-0095 C2-β, #2502) and three-state provenance badges (platform/package/admin, #2501) — the Console side of 15.0.0's C2/A4.
- A proactive AI usage indicator in the ChatDock header (build + dataChat progress rings, amber near-limit + upgrade CTA; hides itself when the usage endpoint is absent) (#2498).
Strict-semver edges and behavior changes in 15.0.0, with migrations — read before upgrading a multi-org or enterprise-hierarchy deployment, and before recompiling metadata authored against spec 14.x.
FormFieldSchema, FormSectionSchema (view) and PageComponentSchema (page)
are now .strict(). A key these schemas do not declare — a visibleWhen typo,
a page-only visibility on a view field (or vice-versa), or a stale key past
its deprecation window — used to be silently dropped by zod's default strip
mode, shipping inert metadata with no diagnostic (the ADR-0049
enforce-or-remove / ADR-0078 no-silently-inert principle). It is now a loud
parse error. A new strictVisibilityError error map names the offending
key(s) and, when one looks like a visibility predicate, points at the canonical
visibleWhen. Migration: recompile your metadata; fix any flagged keys —
deprecated visibleOn / visibility aliases are still declared keys and keep
normalizing to visibleWhen, so correctly-authored metadata parses unchanged.
The companion lint rule (visibility-root-mislayered) is now bidirectional
(#2931): it flags a data.-rooted predicate on a runtime view/page and a
record.-rooted predicate on a metadata-editing form.
- Cross-tenant read leak closed (Layer 0). A permissive business RLS policy
(e.g.
status == 'public') can no longer OR-widen tenant scope. Tenant isolation is now AND-composed as the outermost filter (Layer0(tenant) AND Layer1(business RLS)); a foreign-org row a business policy matched is now invisible. - Member by-id writes narrow to owner-only. The previous OR-merge silently
widened
owner_only_writesback to org-wide, so a member could by-id update/delete any record in their org. Writes are now owner-scoped as authored. Migration: if your deployment intentionally relied on members editing each other's records org-wide, grant an explicit per-object edit permission set (position-distributed) where that is wanted — the baselinemember_defaultno longer permits it. - Cross-tenant writes via
organization_idblocked (insert + update). A user could previously plant a row into another tenant by supplying a forgedorganization_idon insert, or by reassigning it on update. Both are now denied for user contexts (Layer 0 post-image check, symmetric across insert/update/bulk);organization_idis effectively immutable outside system context. Default writes (noorganization_id) and system-context writes (import, migration, per-org seed replay) are unaffected. - Org admins no longer cross the tenant wall on
privateobjects. The Layer 0 cross-tenant exemption is now gated on a genuine platform-admin capability (manage_metadata/manage_platform_settings/studio.access/manage_users), not on theviewAllRecords/modifyAllRecordssuper-bits thatorganization_adminalso holds. Onaccess.default: 'private'tenant objects an org admin is now walled to their own organization (previously unfiltered). Genuine platform admins are unaffected; the super-bits continue to drive only the in-org Layer 1 business-RLS short-circuit (TENANT_ADMIN semantics unchanged). - No-active-org writes fail closed. A write by a principal with no active organization on a tenant object is now denied.
- Seeded
tenant_isolationpolicy retired. The wildcardtenant_isolationRLS policy is removed from theorganization_admin/member_default/viewer_readonlysets (isolation is enforced by Layer 0 instead). The_self/_orgidentity-table carve-outs andowner_only_writes/deletesare unchanged; customized seeded sets keep their overlays (ADR-0094).
unitscope anchors to the position's business unit.unit/unit_and_belownow prefersys_user_position.business_unit_id(the position's BU) oversys_business_unit_membermembership. Deployments that do not populatesys_user_position.business_unit_idsee no change (every user falls back to membership). Once anchors are populated, a user'sunitvisibility narrows to their position BU; multiple positions union; expired/inactive positions fall back to membership.
- Import runs the automation chain per row (#2922).
engine.insertnow triggersbeforeInsert/afterInsertonce per row with single-record hook contexts — flat-input proxies, declarative hook conditions, audit writers, and record-change triggers see real records instead of arrays. A newExecutionContext.skipAutomations(mirrored intoHookContext.session) suppresses metadata-bound automation hooks and impliesskipTriggersfor flow dispatch — making the import wizard's "run automations & triggers" checkbox and import-undo actually effective; code-registered system hooks (audit, security, sharing) still run. REST import defaultsrunAutomationsto true unless the request explicitly opts out. - System-row write guardrail on
sys_position/sys_capability(#2930). Platform/application-published rows (managed_by: system/configpositions,managed_by: platform/packagecapabilities) could previously be deleted or rewritten directly by a customer admin, silently breaking an app's authorization baseline. A newassertSystemRowWriteGatein the data-write hook now guards both — the same two-doors modelsys_permission_setalready had. - Explain posture labels align with enforcement (#2949).
security.explainderives its posture evidence from the same enforcement path, eliminating label drift between what the panel says and what the kernel does.
- RLS safety nets recognize canonical
==(#2936). The field-existence / tenancy-disabled nets were inert for==-form policies; now recognized. Only visible effect: on column-less system tables a member's by-id write is now an explicit fail-closed deny rather than a phantom-column filter — same result. - Self-delegation position anchors are subtree-constrained (#2945). A delegated
position's
business_unit_idmust fall within the delegator's own effective anchor — closing a lateral-visibility escalation the position-anchor change would otherwise have opened. - Hierarchy resolver queries are org-scoped (defense in depth). Owner-id resolution
now filters by organization directly, and private hierarchy-scoped objects that lack
an
organization_idcolumn fail closed at boot.
15.1 is a fast-follow minor (87 commits, 65 changesets — 60 minor + 62 patch, no major) 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). Strict-semver edges are listed under Behavior changes with their migrations. The bundled Console picks up the objectui 14.1 line (94 commits) — see its own section below.
Parent-derived access on the full chain, Salesforce-ContentDocumentLink style:
- Reads inherit parent-record visibility — list/count/aggregate filter
consistently (
totaldoesn't leak hidden-row counts); a record with no visible parent yields a deny-all sentinel; computation errors fail closed. - Authenticated, short-lived downloads — see Behavior changes above.
- Create requires parent EDIT; delete requires uploader-or-parent-editor;
uploaded_byis server-stamped (client value ignored); uploads require a session and stampowner_id. sys_fileorphan lifecycle — tombstone on last detach (deleted_at, TTL 30d; pending uploads 7d), re-attach un-tombstones; a new LifecycleService reap guard seam (registerReapGuard) re-verifies zero references at sweep time, deletes storage bytes before confirming the row reap, and a guard error fails safe (row retained).sys_upload_sessionlifecycle — abandoned/terminal chunked-upload sessions are reaped (TTL 1d pastexpires_at, terminal retention 7d), and the reaper aborts the backend multipart upload first so abandoned S3 parts stop accruing storage charges; an abort failure vetoes the reap for retry.- Console — the RecordAttachmentsPanel uploads with authentication and shows friendly denial copy (objectui#2532), downloads via signed URL, and inline line-item grids get real file/image upload cells (objectui#2585); the showcase demonstrates per-line Receipt attachments on invoice lines (#3051).
@objectstack/verifygainsBootOptions.extraPluginsfor booting attachment flows in harnesses.
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
credentialRefand are never inlined (open profile: env-var resolution; vault/OAuth2/per-tenant are enterprise, ADR-0015). Boot fails loudly on unknown providers, invalidproviderConfig, unresolvable credentials, or name collisions. - Descriptor-only contract + boot audit (#2985). A declarative connector
with actions but no same-name runtime registration gets a loud boot/reload
warning;
enabled: falsemarks a deliberate catalog descriptor and silences the audit. - Runtime re-materialization (#3001).
metadata:reloadedreconciles incrementally (only added/removed/signature-changed instances reconnect); boot errors are fatal, reload errors are soft. - Degrade + retry on unreachable upstreams (#3049).
ConnectorUpstreamUnavailableErrormarks an operational failure: the connector registers as an action-less husk (state: 'degraded'+degradedReasonon the descriptor), retries with exponential backoff (5s → 5min), and swaps in atomically on recovery — configuration errors still fail boot. - OpenAPI specs load three ways (#3024): inline object, package-relative
file path (confinement guard rejects absolute paths and
..escapes — the CLI passes the project dir aspackageRootforserve/dev), or http(s) URL. - Discovery:
GET /api/v1/automation/connectorsreports each descriptor'sorigin('plugin' | 'declarative') and state — which the flow-designer connector picker surfaces (objectui#2563). - The showcase runs a live
provider:'mcp'demo pinned in CI (#3062).
The previously declared-but-inert DashboardSchema.globalFilters + dateRange
are now live end to end:
- Spec pairing (#3035):
GlobalFilterSchema.namegives each filter a variable key (readable aspage.<name>in widget expressions;"dateRange"reserved), andDashboardWidgetSchema.filterBindingslets each widget retarget a filter to its own field (string override), opt out (false), or default to the filter's ownfield. - Console runtime (objectui#2576): a DashboardFilterBar renders above the widgets; active values AND-merge into every bound widget's inline query (dataset widgets via runtimeFilter); date presets send date-macro tokens resolved at query time.
- Visual editor (objectui#2586): the widget inspector gains a "Dashboard filter bindings" section — one row per filter with an Apply toggle and field picker; no more hand-written JSON.
- Enhancements (objectui#2590): nested PageVariablesProviders merge instead
of shadowing; metadata-aware default bindings skip (and warn on) fields the
widget's object doesn't have;
optionsFromresolves via server-side dataset GROUP BY distinct with a client top-200 fallback. - The showcase "Revenue Pulse" dashboard demonstrates interactive date + region filters driving invoice and account charts over two objects, with an opted-out all-time KPI (#3038).
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) by a before-save
hook (@objectstack/plugin-pinyin-search), backfilled at boot with a
rebuildSearchCompanion reconcile, 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. Documented in the
search guide with sys_user/picker coverage and existing-row backfill notes
(#3057, #3034); the showcase seeds make it demonstrable out of the box.
- Conditional tabs (#2967):
page:tabsitems accept avisibleWhenCEL predicate overrecord+current_user+page.<var>; FALSE removes the whole tab (header and panel), re-evaluated live as page variables change. The new surface deliberately accepts no legacyvisibility/visibleOnaliases. - View property liveness (#3046, #2998 Track B): all 75 view properties
classified (68 live / 2 experimental / 5 dead);
os buildwarns at compile time on dead props (e.g. achartlist view that renders nothing) with fix-it hints. - Feature-gate registry +
requiresFeature(#2965, #2874):PUBLIC_AUTH_FEATURESclassifies 13/api/v1/auth/configflags; actions and params declarerequiresFeature, lowered at parse time to canonicalvisibleCEL — 17 capability-dependent actions (6 platform user-management, 6 two-factor, 5 OAuth-application) now hide when their plugin is off instead of rendering 404 buttons, and a spec guard enforces that every feature-gated capability is UI-gated. - Page variable
sourcerenders as a component picker (#2968): the metadata form pins aref:componentwidget, so Studio offers a dropdown of the page's components instead of a free-text id (companion objectui#2523).
aggregate_recordsMCP tool (#2976): count/sum/avg/min/max/count_distinct with groupBy (incl. date bucketing), where filters, and IANA timezones — on the ObjectQL ENGINE read path, so RLS/tenant/delegation intersect exactly likefind. The bridge seam is optional; kernels without it simply don't register the tool.- Standalone authored actions reach the MCP bridge (#3010, #3020):
list_actions/run_actionnow unionobject.actionswith standaloneactionmetadata rows (object-embedded declarations win on key conflicts) — Studio-authored actions withai.exposed: trueare callable by agents without re-declaring them inside the object. All invoke-time gates (fail-closedai.exposed, ADR-0066 D4, fail-closedsys_*) unchanged.
- Official Docker runtime image (#2952):
ghcr.io/objectstack-ai/objectstack(Node 22 + pinned CLI +os start, non-root,/api/v1/healthHEALTHCHECK,OS_PORT=8080, multi-arch amd64/arm64, image tags track the CLI version). Deployment is a two-line Dockerfile — or plaindocker runwith a mounted artifact /OS_ARTIFACT_PATHpointing at an https URL. create-objectstackscaffolds container-ready projects (#2979): the blank template ships a Dockerfile (two-stage build onto the official image), docker-compose.yml (app + Postgres), and.dockerignore—npm create objectstackthendocker buildjust works.
kernel:bootstrappedlifecycle anchor (#2989): fires after allkernel:readyhandlers settle, beforekernel:listening— the correct anchor for reconcile/backfill work, eliminating ready-handler ordering races. Fired by both ObjectKernel and LiteKernel; the sharing-rule boot backfill moves onto it. The same PR fixeseveryoneauto-bind ordering: baseline auto-bind used to run before theeveryoneanchor was seeded, so fresh deployments got an emptyeveryoneand personas silently degraded.app:seededevent + membership backfill re-run (#2996, #3000): when seeding exceeds the inline budget (OS_INLINE_SEED_BUDGET_MS, default 8s) and completes in the background, plugin-auth re-runs its idempotent membership backfill — seeded users no longer wait for a restart to get memberships.- ADR-0087 completed (#2972): the protocol handshake now runs on boot-time
durable-package rehydration (incompatible
sys_packagesrows are refused with structuredOS_PROTOCOL_INCOMPATIBLEdiagnostics while boot continues) and on code-definedAppPluginloads (fail-fast);objectstack lintgainsprotocol/missing-engines-range(warning + fix-it); the blank template stampsengines: { protocol: '^<major>' }; the conversion chain is backfilled for steps 12–15 somigrate meta --from 10reaches protocol 15;spec-changes.jsonis generated from the registry, ships with the npm artifact, and attaches to every spec GitHub Release; the upgrade guide is generated and drift-checked in CI. - Honest capability discovery (ADR-0076 D12 slice): services self-describe
via a
__serviceInfomarker (stub/degraded/fallback) instead of discovery hardcodingstatus: 'available'; discovery stops advertising a/realtimeroute andwebsockets: truethat always 404'd (SDK clients are unaffected — they fall back to regular paths as before); the REST protocol dependency narrows toRestProtocol = DataProtocol & MetadataProtocol(type-level only).
os i18n extractemits action-param keys (o.<object>._actions.<action>.params.<param>.*), so action dialog forms — e.g. Setup → Create User — are translatable; platform-objects bundles regenerated for en/zh-CN/ja-JP/es-ES (#3030, #3033).- Audit activity summaries localize to the workspace locale (ADR-0053 verb templates for en/zh-CN/ja-JP/es-ES, localized object labels, English fallback when the service or key is missing) (#3039, #3045); feed/audit summaries use the object's display label instead of its API name (#3029).
- New lint:
field-group-shadowedwarns when a field group is silently shadowed (#3029).
The pinned Console picks up the objectui 14.1 line (94 commits), plus a
handful of post-14.1 fixes carried by the final pin: cascading option fields
receive the live form record so a dependent select actually unlocks when its
parent is chosen — found during 15.1 release QA on the showcase B3 fixture
(#2620); OIDC provider sign-in works against better-auth ≥ 1.7's
POST /sign-in/social (#2621); inline-edit numeric fields enforce
min/max/step (#2615); the approval-lock band renders from the host signal on
request-tracked backends (#2619); built-in grid row Edit/Delete honor
per-record CEL predicates (#2617); and the AI build canvas cold-starts into
Studio with a primary CTA + artifact deep links (ADR-0080 D5, #2623). By area:
- Record-level inline edit with one atomic save (#2542). The inline-edit
session lifts from per-field to a shared
InlineEditContext+ record-level save bar: the whole draft commits through oneupdate(obj, id, draft, { ifMatch: updated_at })— only edited keys sent, 409 conflicts open a ConcurrentUpdateDialog. Computed/readonly/system fields stay non-editable;canEditgating and optimistic concurrency preserved. - The highlight strip joins the same draft (#2549). Hover-pencil / double-click editing on the header highlights shares the same draft and the same Save as the body — one atomic update for both. Computed highlights get no editor.
- Inline-edit polish (#2604). Lookup/user fields reuse the
$expanded display name (no duplicate findOne); an approval-lock preflight disables editing live (with a save-bar notice) when a submitted approval locks the record; number/currency/percent edit with real form widgets wired to metadata min/max/step; the header Edit button dims while an inline session is active; Esc cancels, Cmd/Ctrl+Enter saves. - File/image upload cells in inline line-item grids (#2585).
Field.filein a master-detail grid used to degrade to a text input; a compact FileCell (pick button + removable chips + image thumbnails) now rides the same UploadProvider pipeline, and auto-derived subform/related-list columns stop silently dropping file/image/avatar fields. - Metadata-driven multi-button record header (#2574). The header renders up
to 3 inline actions on desktop / 1 on mobile (
maxVisible/mobileMaxVisibleto override), ordered byorderwith avariant:'primary'tie-break — consistent with the action:bar contract;component:'action:menu'pins an action into the ⋯ overflow. System actions declare accordingly (sys_edit order:100; sys_share/sys_delete pinned to ⋯ — Delete no longer surfaces as a red inline button on action-poor objects). - ADR-0085 detail follow-ups (#2577). The highlight strip dedupes the
title field; fieldGroups render their icon/description;
currencyConfigflows through field enrichment (spec-declared currency fields show their symbol); RecordMetaFooter gets a proper no-actor fallback (and a zh mistranslation fix); select badges ellipsize with a hover title; approval fetches carry Bearer tokens for split-origin deployments. - Related lists respect the child object's read permission (#2565).
deriveRelatedListstakes acanReadpredicate and therecord:related_listrenderer self-gates — users without read on the child no longer see an empty section with a New button that 403s on save. - Supporting fixes: master-detail saves refresh bound related lists
(batchTransaction/bulk now emit MutationEvents, #2607/#2584); lookup chips
resolve display names via the referenced object's schema instead of an
autonumber-prone key heuristic (#2551); the highlight-strip lookup editor
honors the ObjectStack
referencekey (#2587); the legacy monolith detail renderer and itsrenderViaSchemakill-switch are removed (ADR-0085 PR4, #2546).
All expression surfaces converge on the canonical @objectstack/formula
engine, and Studio's bare textareas are replaced by a shared
CelPredicateField with lint + autocomplete:
- Runtime convergence first (#2544). List/grid/kanban conditional
formatting and row-action visible/disabled all evaluate through the CEL
engine; three historical rule shapes (CEL envelope / bare string / native
{field, operator, value}) route through one resolver with a one-time deprecation warning for legacy syntax. Row-actionvisiblefails closed on a broken expression (hidden + warn);disabledfails soft. - Formula editor with inferred result type (#2609). Fixes a silent trap:
the old formula textarea wrote a
formulakey the engine never read (ObjectQL only evaluatesexpression) — Studio-authored formulas never computed. The editor now writesexpression(migrating legacyformulaon first edit), lints as a value expression, live-infers the result type (Number/Text/Boolean/Date, withdouble()/int()/string()hints on Unknown), writes the inferred type toField.returnType, and summary fields get a structured roll-up editor (child object / aggregate / child field / relationship) instead of raw JSON.validateMetadataDraft('object')lints every formula in the draft. - Field conditional rules (#2571). visibleWhen / readonlyWhen /
requiredWhen in the object designer switch to CEL editors scoped to
record(matching runtime evaluation); bare field references error with therecord.<field>fix-it;record./previous.member autocomplete with did-you-mean; deprecatedconditionalRequiredmigrates torequiredWhenon first edit. - List-view conditional formatting editor (#2558). An ordered,
first-match-wins rule list (move up/down), each rule = CEL predicate +
background/text/border colors; writes the spec-canonical
{ condition, style }shape and upgrades legacy shapes on read. - RLS policy authoring safety (#2533). USING/CHECK editors gain inline lint, field/scope autocomplete, and a per-policy test-run against a chosen record — delegated to the canonical engine via a lazily-loaded, feature-detected bridge.
- Condition-builder escape hatch (#2567). The raw-expression fallback in
ConditionBuilder becomes a CelPredicateField across field-level
*When, action visible/disabled, and condition-widget props — invalid predicates get readable inline errors instead of failing silently at runtime. - Scope fixes: row-predicate editors advertise runtime-bound roots and
kanban binds the host scope (#2603);
current_user.positionsjoins the client predicate scope (#2573); kanban's zod schema accepts the{condition, style}CEL shape (#2550).
Covered under "Dashboard-level filters" above — runtime bar (#2576), visual
filterBindings editor (#2586), nested-variable merging + metadata-aware
defaults + server-side optionsFrom (#2590), and the crash fix for spec-form
{value, label} options with label display (#2597), plus catalog examples, a
guide tutorial with real screenshots, and en/zh filter-bar i18n (#2581).
- Package-level OWD overview with batch edit (#2508). A "Record Sharing
Baseline (OWD)" panel lists every object's
sharingModel/externalSharingModelwith inline selects and a single Save (one package-scoped draft per changed object);controlled_by_parentrows link to the master read-only; row-levelexternal ≤ internalvalidation (ADR-0090 D11) blocks saving inline; the permission matrix's OWD badges deep-link here (?surface=owd:overviewsupported). - Three unsaved-changes guards (#2588, #2606, #2610). Matrix checkbox edits no longer vanish on rail clicks (dirty tracking + confirm + beforeunload); Studio header pillar links / Home / PackageSwitcher are SPA navigations that bypassed beforeunload — pillar dirty state now reports up to StudioDesignSurface for a unified confirm; OWD-row edits join the same guard (owdDirty ∥ matrixDirty).
- Read-only packages actually lock the matrix (#2570). Previously the
Access pillar rendered editable checkboxes and a working Save on read-only
packages;
readOnlynow threads all the way down (checkboxes, bulk buttons, name inputs, capability picker, Save), with badge copy distinguishing the package-level vsOS_METADATA_WRITABLEtype-level gate. - Record-grained AccessExplainPanel (#2502, framework#2920 C2-β). A record
selector + per-layer row-level attribution (permission set → position →
sharing → row rules, outcome badges + tri-state dots), the effective
rowFilter, a
record.visibleverdict banner with the deciding layer, and posture/kernel-tier labels (tenant wall vs business RLS). - Three-state provenance badges (#2501): platform / package / admin
(custom), aligned with the framework's
sys_*.managed_byvocabulary. - Access matrix history opens in an in-place sheet; breadcrumbs stop escaping the pillar (#2599).
- Add-node palette: search + keyboard nav + recents (#2543, #2553). Substring
search over label/hint/type, ↑↓ + Enter selection, a "Recently used" group
cloud-synced through
sys_user_preference(localStorage-first, debounced, cross-tab, offline fallback, one-time legacy-key migration), localized group headings (Data/Logic/Human/Integration/Flow), and an official guide. - Decision branches pick their target node inline (#2568). The Branches editor gains a Target column — label, CEL expression, and downstream node in one table (Salesforce Flow Decision Outcomes style). The column is virtual: derived from the decision's outgoing edges (the routing truth), round-trips with canvas rewiring and the FlowEdgeInspector; clearing a target only disconnects the edge.
- Connector picker reads the runtime registry (#2563). The
connector_actionpicker lists exactly the dispatchable connectors (GET /api/v1/automation/connectors— plugin + materialized declarative instances) and marks declarative instances by origin, instead of a genericclient.list('connector')that missed plugin-registered connectors. - Positions as approvers (#2515). Approval
approvers/escalateToacceptpositionreferences via a new xRef picker kind (combobox over known positions, free-text fallback when empty).
- Lanes honor the
stageFieldsemantic setting (#2596): explicit config → ADR-0085stageField→ strict-false suppression → shared name/type heuristics (no more hardcoded 'status'). - Cards default to
highlightFields(#2541) when the view declares no cardFields, falling back to legacy heuristics. - Conditional formatting accepts CEL
{condition, style}rules (#2550), consistent with list/grid and the runtime.
- Spec-driven package form (#2535). The three hand-written package forms (with two mutually-contradictory id regexes) collapse into one ManifestSchema-generated modal; id/type/namespace/scope lock after creation; duplicate ids get a friendly 409 message. Net −254 lines.
- Namespace prefix enforced at authoring time (#2524, framework#2694). The
package creator derives an editable
namespacefrom the package id, and "+ new object" auto-prefixes identifiers with<namespace>_(no double prefixes,sys_*exempt) — authors stop discovering NAMESPACE_PREFIX rejections at publish time. - AI ↔ Studio bridging: a "Design in Studio" entry on the AI build canvas (#2511); a proactive AI usage indicator in the ChatDock (#2498); the AI edit-app surfaces localize (#2512).
- Package rename refreshes the builder top bar immediately (#2554); empty
packages stop force-opening the new-object dialog (#2569); the copilot
composer and collapsible properties inspector return (#2504); View
create-form Object fields render as ref:object pickers (#2526, #2521);
metadata-admin can create form-family views (#2531);
page:tabshonors item-levelvisibleWhen(#2516, pairing framework#2967).
- Error envelopes no longer crash the page via
toast.error(#2580). Failed api/flow/server actions returned{error:{code,message}}objects straight intotoast.error(), tripping React #31 and white-screening the page (Setup → Create User's 400 was a repro). Fixed at the source (errorDetail()flattens to a string) and at the sink (ActionRunner coerces). - Bare-string sort from spec forms no longer crashes ListView (#2601) (from the #2578 shape-mismatch audit).
- CSV import sniffs encoding with a GB18030 fallback (#2557). zh-CN Excel
saves CSVs as GBK;
file.text()decoded UTF-8 only, garbling Chinese headers so required columns could never map. Detection ladder: BOM → strict UTF-8 → GB18030, native TextDecoder, zero new dependencies. - i18n set: Studio/metadata-admin follow the in-app locale instead of
navigator.language— no more mixed-language sessions (#2602, ~40 call sites); relative dates (Today/Yesterday/In N days) localize viaIntl.RelativeTimeFormat(#2593); userFilters chips keep author labels instead of being overwritten by i18n skeletons (#2509);createSafeTranslationdrops a try/catch-around-hook (#2605). - Action-visibility traps defused (#2611): ① the client predicate scope
gains
os.user.*aliases (predicates written in the server's canonical dialect no longer fail closed and hide for everyone); ② actions withlocations:['record_more']render into the ⋯ overflow (previously not at all); ③ throwing predicates still hide their action but warn once (action name + predicate source + reason) instead of disappearing silently. - Attachment downloads use authenticated signed URLs; uploads authenticate
with friendly denial copy (#2532); an FLS fail-open and lookup
display_fieldfix (#2537); the list view's horizontal scrollbar pins to the viewport bottom (#2506); sticky leading cells pin at measured header widths (#2592); ImportWizard "run automations & triggers" defaults ON (#2503); rules-of-hooks violations in cell renderers cleaned up (#2595);reference/reference_tocanonicalized at schema chokepoints (#2598). - Removals: the zero-consumer
@object-ui/tenantpackage (#2566), the duplicate FormRenderer/FieldFactory form path (#2560), and the legacy monolith detail renderer (#2546).
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).
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.
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, so the same object data an authenticated route protected
could be read anonymously through a different door. 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 in @objectstack/core, the
/security/suggested-bindings admin surface hardcodes requireAuth: true, 401
bodies unify on code: 'unauthenticated', and the authz-conformance matrix
ratchets newly added /data//meta//graphql routes — an unclassified new
route fails CI.
readonly: true was documented as a server contract but only enforced in the
UI — issue #3003 confirmed approval/status/amount columns could be forged via a
plain REST PATCH on 15.0.0 (e.g. self-approving by patching an
approval-status column).
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' updated_by/updated_at), 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. The docs now describe readonly as a server contract, not "read-only
in UI".
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). Non-scalar owner_id payloads are
rejected outright (prototype-pollution safe, own-property semantics). 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; a non-system bulk write with no userId on an owner-scoped object now
affects 0 rows instead of all of them. Engine-internal referential cascades
(set_null on user deletion) are exempted via a server-side
__referentialFieldClear marker that cannot be forged from a client (#3048) —
without it, deleting a user tripped the guard mid-cascade and left partial
state. 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 unless they hold a transfer grant; admins with modifyAllRecords are
unaffected.
Action bodies execute as trusted code (the engine facade carries no
ExecutionContext, so internal reads/writes bypass RLS/FLS/CRUD/tenant scoping),
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 the MCP and REST paths.
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" (a security
refusal must not be resurrected by the retry loop). 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. This gate is the security
precondition for shipping connector-mcp in the default preset (#3056).
A new registerAuthoringGate(type, gate) 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 — closing the
OS_METADATA_WRITABLE=object unvalidated-widening hole), and
externalSharingModel ≤ sharingModel (ADR-0090 D11) is now rejected at save
time rather than only flagged by CLI lint. Already-stored metadata loads
unchanged.
- 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, no data migration needed. - Attachment downloads are no longer anonymous capability URLs. For
scope === 'attachments'files that are notpublic_read,GET /storage/files/:fileIdand/urlrequire a session (401AUTH_REQUIRED) plus parent-derived read access (403ATTACHMENT_DOWNLOAD_DENIED) and return a short-lived signed URL (defaultdownloadTtl300s). Field files, avatars, and org logos (embedded in<img src>) keep stable anonymous URLs; bare kernels that never wired the auth seam remain open for back-compat. sys_attachment.enable.trashis nowfalse— attachment deletion is a hard delete (declaring the honest state; reap guards reclaim bytes, and a restore would dangle).- Attaching to a record now requires parent EDIT (was: parent READ), matching Salesforce semantics (#2970 item 3).
ConnectorSchema.authenticationdefaults 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 — output rows carry only aliases, so the result-masking pass can never
run; the input side is where the leak is stopped (#2976). Aggregates map to
the
listApiMethod (objects excludinglistcan't leak row statistics via GROUP BY), and at least one aggregate is required (no degenerate bare-row scans past the FLS masker). findDatarejects unknown$-prefixed query params with 400UNSUPPORTED_QUERY_PARAM(naming the supported aliases) instead of silently treating them as an implicit equality filter matching zero rows (#2926 ⑩). Bare-key implicit equality is unchanged.getReadFilterreturns a fail-closed deny sentinel for on-behalf-of (delegated) contexts on the analytics/raw-SQL read path — a latent-invariant guard until D10 delegation intersection is implemented there; no production surface reaches it today (#2852, #2988).- The FormView
buttons+defaultsconfig (#3014) is EXPERIMENTAL — declared in the spec but not yet enforced by the Console renderer (pending objectui#2545). Authoring it today is a declaration, not behavior.
Beyond the behavior changes above, an execution-surface sweep (#2849 class) closed three confirmed-exploitable holes (#2975), and a second sweep hardened seeding and expansion:
- Reports IDOR + scheduled-report RLS bypass (#2980).
ReportServicedropped the caller context and read/wrotesys_saved_reportas system — any authenticated user could read/delete/overwrite any saved report by id, and scheduled dispatch emailed entire tables (isSystemreads). Now owner-scoped; scheduled dispatch resolves the owner's real RLS context and fails closed (skip + mark failed) when it can't. - Knowledge/RAG fall-open (#2981).
applyPermissionFilterreturned all hits when the context was missing — an agent omittingToolExecutionContext.actorgot unfiltered semantic search over the whole corpus. Missing identity now fails closed; only an explicit system context passes unfiltered. $expandcross-object leak (#2850, #2961). Expanding a reference resolved the target rows directly against the driver — the referenced object's RLS and FLS never executed, so any caller who could read the base row could pull back RLS-hidden rows and FLS-masked fields of the referenced object. Expansion now batches through the enginefindpath; PUBLIC referenced objects are exempted only from the object-level CRUD gate via a server-side__expandReadmarker; PRIVATE objects keep every gate.- Public-form anchor forgery (#3022, #3036). Zero-declared-field public forms
fell back to merging 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 (publicFormGrantbranch, batch included) and excluded at the route layer (allow-list, resolve route, lookuppublicPickerdeny on managed anchors) — from a single sharedPUBLIC_FORM_SERVER_MANAGED_FIELDSdefinition. Authenticated writes are unaffected. - Bulk-write predicate guard scoped to the caller's filter (#3053). The
anti-oracle predicate guard now inspects the caller's original
options.whererather than an AST a sibling middleware may already have injected an owner filter into — removing a middleware-order-dependent false 403 — and pre-image reads are deduplicated. - Seed-not-clobber (#2909 P0/T1, T3). An admin's
active: falseon an overshared sharing rule is no longer resurrected by boot seeding, and the curated capability seeder no longer overwrites an admin's re-categorizedscopeon every boot —managed_by/customizedprovenance columns land on both. sys_positionsystem-row write gate re-armed (#2926 ①). After the A4managed_byvocabulary rename, the 15.0.0 gate (#2930) still checked the old vocabulary — platform/package-managed positions (including theeveryone/guestanchors) could be physically deleted via the data API once unbound. The gate now recognizes both vocabularies.- GraphQL / realtime identity pre-wiring (#2992, #3013, ADR-0096 D4). Two
surfaces that are not client-reachable today are fixed and CI-pinned before a
real transport lands: the GraphQL entry resolves and threads an
ExecutionContext (conformance-matrix row
graphql-identity-thread), realtime registers its pure fan-out posture as an experimental matrix row plus a transport TRIPWIRE (newly wiring a WebSocket/SSE fails CI), and theservice-realtimeREADME stops advertising APIs (authorizeChannelet al.) that don't exist. - better-auth pinned to 1.7.0-rc.1 for GHSA-p2fr-6hmx-4528
(
@better-auth/oauth-provider), with the ObjectQL adapter implementing the newconsumeOne/incrementOnecontracts andsys_jwksgainingalg/crvcolumns — without which JWKS writes 500'd and every authenticated request after login returned 401 (#2974). - CI: the retired
pnpm auditis replaced by OSV-Scanner in validate-deps (#2986).
- Studio metadata editing under spec 15: lazy schema proxies stay
identity-compatible with
z.toJSONSchema— the Page/View inspector no longer crashes withCannot set properties of undefined (setting 'ref')(objectui#2561, #3021). Parse behavior is unchanged;OS_EAGER_SCHEMAS=1remains a bypass. - Sharing rules apply to seeded demo data out of the box (#2926 ③):
seed-loader records never produced
sys_record_sharerows (the write hook skipsisSystem), so demo data with sharing rules was born broken. An idempotent boot backfill onkernel:listeningnow materializes them; a single bad rule doesn't block startup. - Package management REST:
PATCH /packages/:idis actually routed — edit-manifest requests used to never reach the handler (#2995); duplicatePOST /packagesids return 409 (#2971); the client SDK'spackages.installpassesoverwritefor upgrade flows (#3007). - Create User: an explicitly typed password wins over the dialog's default
generatePassword: trueinstead of erroring 400 "Provide either … not both" (#3031). - REST status passthrough (#2926 ⑦): record-level 403 FORBIDDEN from plugin-sharing no longer degrades to a bare code-less 400; structured 409 envelopes (CONCURRENT_UPDATE et al.) keep their dedicated branch.
gen:schemadisappearance ratchet (#3012): anio:'input'fallback restores the lostPageTabsPropsgenerated schema, and CI now fails if a generated schema disappears.isDefaultdual-track documented (#2926 ②): app-levelisDefaultauto-bindseveryone; package-level only emits a suggestion — fixing the previously self-contradictory "never auto-bound" wording (doc-only).
- Strict schemas: recompile metadata; fix any keys flagged by the new
strict view/page parse errors (typos, mis-layered
visibility/visibleWhen, stale keys). Deprecated aliases still parse. - 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_idon user-context writes (route it through system context or a dedicated endpoint). - Import integrations: REST import now defaults
runAutomations: true— opt out explicitly if your integration depended on import skipping hooks. - Enterprise hierarchy: the upgrade itself is inert; plan BU-anchor data before you
begin populating
sys_user_position.business_unit_id, sinceunitvisibility then narrows to the position BU. - Before production: a multi-org / hierarchy / tenant-write-guard enterprise end-to-end smoke test in a licensed environment is still owed — open-core CI cannot exercise the multi-org path.
- Remove
tenancy.strategy/tenancy.crossTenantAccessfrom object metadata (parse now fails loudly). - Rename
ObjectOSCapabilitiesSchema/ObjectOSCapabilities/ObjectOSKernelimports toKernelCapabilitiesSchema/KernelCapabilities/IKernel(deprecated aliases last one release). - If you serve
/graphqlor raw/dataanonymously on purpose, setrequireAuth: falseexplicitly. - Audit clients that write
readonlyfields orowner_id— those writes are now stripped/denied; grantallowTransferwhere 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: [...] }).
ADR-0095 (kernel chain) · ADR-0089 D3 (strict visibility schemas + lint) · ADR-0090 Addendum (position-anchor) · ADR-0094 (overlay 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) · ADR-0085 (detail shapes / Console) · #1582 (CEL editors).