Releases: objectstack-ai/objectstack
Release list
@objectstack/plugin-security@16.0.0
Minor Changes
-
22013aa: Split the overloaded
managedBy: 'system'bucket into engine-owned vs. admin-writable, and enforce engine-owned writes (ADR-0103, #3220). Thesystembucket conflated two incompatible write policies: rows a platform service owns end to end (never user-written), and platform-defined schema whose rows are legitimately admin/user-writable. It carried the same all-false affordance row asbetter-auth/append-onlybut, unlikebetter-auth, had no engine enforcement — a wildcard admin could raw-write these rows through the generic data API (ADR-0049 gap).Rather than add a new
managedByenum value (which would fall through to fully-editableplatformdefaults on already-deployed Console clients), the write policy is now the resolved affordance (resolveCrudAffordances= bucket default +userActions), and engine-owned is defined as asystem/append-onlyobject that grants no write:- Writable set declares
userActions— the RBAC link tables (sys_user_position,sys_user_permission_set,sys_position_permission_set),sys_user_preference,sys_approval_delegation, and the messaging config grids (sys_notification_preference/…_subscription/…_template) now declareuserActions: { create, edit, delete: true }. The affordance is a declaration only — theDelegatedAdminGate/ RLS / permission sets remain the authz. - Engine-owned objects locked to reads —
apiMethods: ['get','list']added where absent (jobs, notifications, approval request/approver/token/action,sys_record_share,sys_automation_run, mail/settings/secret audit, the messaging delivery pipeline).sys_secretis explicitly read-locked (an emptyapiMethodsarray fails open). sys_import_jobstays engine-owned: the REST import route now writes its job rowsisSystem-elevated (attribution preserved via the explicitcreated_bystamp) and the object is locked to['get','list'].- New engine write guard (
assertEngineOwnedWriteAllowed, plugin-security) fail-closed rejects user-context generic writes to engine-ownedsystem/append-onlyobjects, keyed off the resolved affordance;isSystemand context-less engine/service writes bypass by construction. Wired into the security middleware alongside the other data-layer gates. reconcileManagedApiMethods(objectql registry) now runs for every managed bucket, not justbetter-auth: any advertised write verb an object's resolved affordances forbid is stripped at registration with a warning (the drift backstop, ADR-0049)./me/permissionsclamp (plugin-hono-server) now clampssystem/append-onlyas well asbetter-auth, so the client hint reflectspermission ∩ guard.
Potentially breaking: a downstream/third-party
systemobject that advertised generic write verbs relying on today's fail-open behaviour will have those verbs stripped (with a warning) and user-context generic writes to it rejected. DeclareuserActionsopening the verbs the object legitimately takes from a user context.better-authkeeps plugin-auth's identity write guard unchanged; the row-levelmanaged_byprovenance vocabulary (ADR-0066) is a different axis and is untouched. - Writable set declares
-
62a2117: Split the overloaded
managedBy: 'system'bucket with an explicitengine-ownedvalue (ADR-0103 addendum, #3343). ADR-0103 deferred the enum split ("revisitable later as a rename") because a newmanagedByvalue would fall through to the fully-editableplatformdefault on deployed Console clients. Both reasons against it are now retired — the server-side write guard /apiMethodsreconciliation //me/permissionsclamp make that fallthrough cosmetic (the write is rejected regardless of what the client renders), and objectui#2712 closed the UI union — so v16 lands it, additively.- New enum value
engine-ownedwith the same all-locked default affordance row assystem(create/import/edit/delete: false,exportCsv: true). It joinsENGINE_OWNED_BUCKETS(the engine write guard) andGUARDED_WRITE_BUCKETS(the/me/permissionsclamp); the guard,reconcileManagedApiMethods, and the clamp mechanisms are unchanged —engine-ownedis an explicit member of the set they already covered by resolved affordance. - 20 objects relabelled
system → engine-owned— the ones the engine owns end to end and that declared no write-openinguserActions(the metadata store, jobs, approval runtime rows, sharing rows,sys_automation_run, the messaging delivery/receipt pipeline,sys_secret, settings). One-line, behaviour-identical per object. - 8 admin/user-writable objects keep
managedBy: 'system'(the RBAC link tables,sys_user_preference,sys_approval_delegation, the messaging config grids) —systemnow reads as "engine-managed schema, writable viauserActions".
Behaviour-, enforcement- and wire-identical: resolved affordances, the guard verdict, the 405
apiMethodsreconciliation, and the permissions clamp are the same before and after — this is a self-documenting relabel, not a policy change. No data migration (managedByis schema metadata) and no code branches on the'system'literal. Retiring the overloadedsystementirely (moving the 8 writable objects to a dedicated bucket) is a breaking rename deferred to v17. - New enum value
Patch Changes
-
2f3c641: ADR-0099 P0: land the probe-vs-carried-rung equivalence gate in the authz matrix (
authz-matrix-gate.test.ts) — seeded-shape equivalence cells, two adversarialKNOWN DIVERGENCEpins (scopedadmin_full_accessgrant; piecemeal platform-exclusive capability), the I2 nesting and I3 narrowing invariant cells, posture-blindness staging pins for the P1 flip, and the EXTERNAL dead-branch cell. Extracts the platform-admin capability probe as the exported purehasPlatformAdminCapability(mechanical, behavior unchanged). Test-only gate; the ADR-0099 P1 flip lands behind it (#3211). -
e38da5b: ADR-0099 P1 (#3211 M2): the Layer 0 cross-tenant exemption gate now reads the carried
ctx.posturerung (#2956) as authoritative, with the platform-admin capability probe demoted to a fallback for resolver-less contexts (delegated-admin bridge, sharing service,getReadFilter). The read and write (insert/update post-image) tenant checks share one decision (computeLayeredRlsFilter), so they cannot drift. A probe↔rung disagreement logs a defect breadcrumb and enforces the narrower rung verdict.Behavior change (security narrowing, multi-org /
@objectstack/organizationsonly): a principal whose carried rung is notPLATFORM_ADMINno longer crosses the tenant wall on private / platform-global / better-auth-managed objects, even when its resolved permission sets carry a platform-exclusive capability. Two shapes are affected: (a) a scopedadmin_full_accessgrant (sys_user_permission_set.organization_idnon-null), and (b) a custom set granting a platform capability (e.g.studio.access) piecemeal alongside a superuser bit. Both are now walled to their own org — the fail-safe direction (the carried rung is a strict subset of the probe). Single-org / env-per-database deployments are unaffected (Layer 0 is inert).Upgrade check: before upgrading, scan
sys_user_permission_setforadmin_full_accessrows with a non-nullorganization_id, and custom permission sets whosesystemPermissionsintersect{manage_metadata, manage_platform_settings, studio.access, manage_users}. To restore cross-tenant operator access for such a principal, grant the unscopedadmin_full_accessinstead. The[authz/ADR-0099]warn log names any principal hitting the divergence at runtime. -
f9b118d: ADR-0099 P2′ (#3211 M3′): pin the two-axis Amendment in the authz matrix. The original P2 (collapse the Layer 1 tier onto posture) was rejected — Layer 1's tier input is the per-object super-bit, a per-principal × per-object delegation primitive posture cannot represent. New cells pin: seeded-face agreement (seeded super-bit holders are already ≥ TENANT_ADMIN), the load-bearing delegation cell (a MEMBER with a delegated per-object
viewAllRecords/modifyAllRecordsshort-circuits Layer 1 yet stays walled by Layer 0 — the auditor pattern), invariant I7 (the scope axis never crosses a boundary posture has not opened), and the contrast that the bit is a real grantable capability, not conditionally inert. Test-only; zero behavior change. -
9d897b3: Derive the better-auth managed-object write denies from the live registry (#3325, follow-through of ADR-0092 / ADR-0103). The default permission sets deny generic writes on better-auth identity tables via a hand-maintained
BETTER_AUTH_MANAGED_OBJECTSlist — exactly the drift ADR-0092 forbids, and it had already drifted (the list carried 17 names while 22 schemas declaremanagedBy: 'better-auth', leavingsys_scim_provider,sys_sso_provider, and threesys_oauth_*tables wildcard-granted for writes at the permission-evaluator layer; the identity write guard still 403'd the actual write, so this was a defense-in-depth gap, not a live hole).- New
applyManagedWriteDenies(managed-object-write-denies.ts) injects a read-only-write deny for every registeredmanagedBy: 'better-auth'object into the four write-granting default sets (organization_admin,member_default,viewer_readonly, MCP write) atkernel:ready, mutating the shared in-memorybootstrapPermissionSetsin place (the array the evaluator resolves and the seeder serializes — a DB-row-only fix would be dead code). Never touchesadmin_full_access, never overrides an existing explicit entry, ignoresuserActions(the better-auth bucket is hard-denied —sys_user'suserActions.editopens only a field-level whitelist the identity guard enforces). - The static
BETTER_AUTH_MANAGED_OBJECTSlist is completed to 22 and kept as a compile-time baseline (covers the pre-kernel:readywindow), now pinned bidirectionally...
- New
@objectstack/plugin-reports@16.0.0
Patch Changes
- Updated dependencies [f972574]
- Updated dependencies [6289ec3]
- Updated dependencies [22013aa]
- Updated dependencies [3ad3dd5]
- Updated dependencies [8efa395]
- Updated dependencies [3a18b60]
- Updated dependencies [a8aa34c]
- Updated dependencies [e057f42]
- Updated dependencies [a3823b2]
- Updated dependencies [bc65105]
- Updated dependencies [43a3efb]
- Updated dependencies [524696a]
- Updated dependencies [bfa3c3f]
- Updated dependencies [5e3301d]
- Updated dependencies [dd9f223]
- Updated dependencies [46e876c]
- Updated dependencies [5f05de2]
- Updated dependencies [021ba4c]
- Updated dependencies [158aa14]
- Updated dependencies [62a2117]
- Updated dependencies [d2723e2]
- Updated dependencies [fefcd54]
- Updated dependencies [beaf2de]
- Updated dependencies [369eb6e]
- Updated dependencies [06ff734]
- Updated dependencies [b659111]
- Updated dependencies [5754a23]
- Updated dependencies [6c270a6]
- Updated dependencies [290e2f0]
- Updated dependencies [668dd17]
- Updated dependencies [8abf133]
- Updated dependencies [e0859b1]
- Updated dependencies [04ecd4e]
- Updated dependencies [4d5a892]
- Updated dependencies [16cebeb]
- Updated dependencies [86d30af]
- Updated dependencies [8923843]
- Updated dependencies [a2795f6]
- Updated dependencies [f16b492]
- Updated dependencies [4b6fde8]
- Updated dependencies [2018df9]
- Updated dependencies [fc5a3a2]
- Updated dependencies [8ff9210]
- @objectstack/spec@16.0.0
- @objectstack/platform-objects@16.0.0
- @objectstack/core@16.0.0
@objectstack/plugin-pinyin-search@16.0.0
Patch Changes
- Updated dependencies [22013aa]
- Updated dependencies [a8aa34c]
- Updated dependencies [e057f42]
- Updated dependencies [a3823b2]
- Updated dependencies [fdc244e]
- Updated dependencies [dd9f223]
- Updated dependencies [2ea08ee]
- Updated dependencies [5f05de2]
- Updated dependencies [021ba4c]
- Updated dependencies [83e8f7d]
- Updated dependencies [d2723e2]
- Updated dependencies [674457a]
- Updated dependencies [6c270a6]
- Updated dependencies [290e2f0]
- Updated dependencies [668dd17]
- Updated dependencies [92f5f19]
- Updated dependencies [32899e6]
- Updated dependencies [04ecd4e]
- Updated dependencies [4d5a892]
- Updated dependencies [86d30af]
- Updated dependencies [2018df9]
- @objectstack/objectql@16.0.0
- @objectstack/core@16.0.0
- @objectstack/types@16.0.0
@objectstack/plugin-hono-server@16.0.0
Minor Changes
-
22013aa: Split the overloaded
managedBy: 'system'bucket into engine-owned vs. admin-writable, and enforce engine-owned writes (ADR-0103, #3220). Thesystembucket conflated two incompatible write policies: rows a platform service owns end to end (never user-written), and platform-defined schema whose rows are legitimately admin/user-writable. It carried the same all-false affordance row asbetter-auth/append-onlybut, unlikebetter-auth, had no engine enforcement — a wildcard admin could raw-write these rows through the generic data API (ADR-0049 gap).Rather than add a new
managedByenum value (which would fall through to fully-editableplatformdefaults on already-deployed Console clients), the write policy is now the resolved affordance (resolveCrudAffordances= bucket default +userActions), and engine-owned is defined as asystem/append-onlyobject that grants no write:- Writable set declares
userActions— the RBAC link tables (sys_user_position,sys_user_permission_set,sys_position_permission_set),sys_user_preference,sys_approval_delegation, and the messaging config grids (sys_notification_preference/…_subscription/…_template) now declareuserActions: { create, edit, delete: true }. The affordance is a declaration only — theDelegatedAdminGate/ RLS / permission sets remain the authz. - Engine-owned objects locked to reads —
apiMethods: ['get','list']added where absent (jobs, notifications, approval request/approver/token/action,sys_record_share,sys_automation_run, mail/settings/secret audit, the messaging delivery pipeline).sys_secretis explicitly read-locked (an emptyapiMethodsarray fails open). sys_import_jobstays engine-owned: the REST import route now writes its job rowsisSystem-elevated (attribution preserved via the explicitcreated_bystamp) and the object is locked to['get','list'].- New engine write guard (
assertEngineOwnedWriteAllowed, plugin-security) fail-closed rejects user-context generic writes to engine-ownedsystem/append-onlyobjects, keyed off the resolved affordance;isSystemand context-less engine/service writes bypass by construction. Wired into the security middleware alongside the other data-layer gates. reconcileManagedApiMethods(objectql registry) now runs for every managed bucket, not justbetter-auth: any advertised write verb an object's resolved affordances forbid is stripped at registration with a warning (the drift backstop, ADR-0049)./me/permissionsclamp (plugin-hono-server) now clampssystem/append-onlyas well asbetter-auth, so the client hint reflectspermission ∩ guard.
Potentially breaking: a downstream/third-party
systemobject that advertised generic write verbs relying on today's fail-open behaviour will have those verbs stripped (with a warning) and user-context generic writes to it rejected. DeclareuserActionsopening the verbs the object legitimately takes from a user context.better-authkeeps plugin-auth's identity write guard unchanged; the row-levelmanaged_byprovenance vocabulary (ADR-0066) is a different axis and is untouched. - Writable set declares
-
bfa3c3f: Broadcast a
transactionalBatchcapability bit in discovery so clients negotiate the atomic cross-object batch declaratively, instead of runtime-probing 404/405/501 (#3298).The atomic cross-object batch endpoint (
POST {basePath}/batch, #1604 / ADR-0034 item 4) and its typed SDK surface (client.data.batchTransaction, #3271) already shipped, but discovery never told a client whether a backend actually supports it. Consumers (notably ObjectUI'sObjectStackAdapter) had to probe: fire a/batch, read404/405(no route) or501(no runtime transaction), and only then fall back to non-atomic client-side simulation. That is "find out by calling", not capability negotiation — it cannot be decided at connect time and cannot serve as the "minimum backend supports/batch" gate that blocks hard-deleting the non-atomic fallback downstream.WellKnownCapabilitiesSchemagains a requiredtransactionalBatch: boolean, and every discovery producer fills it honestly (declared === enforced), so it never becomes a declared-but-unpopulated bit:@objectstack/metadata-protocol(getDiscovery) — reports whether the runtime engine can honour a transaction (typeof engine.transaction === 'function'). The/batchhandler runs its ops insideengine.transaction(), which degrades to a non-atomic passthrough (or 501) without one.@objectstack/rest(/discovery) — ANDs the engine signal with whether it actually mounts the route (api.enableBatch), so a server with batch disabled reportsfalseeven on a transaction-capable engine (never advertise an endpoint that would 404).@objectstack/plugin-hono-server(standalone discovery) — reportsfalse: this minimal surface registers CRUD only and does not mount/batch(that ships with@objectstack/rest). Under-reporting is the safe direction — a client keeps its correct-but-slower fallback rather than losing atomicity.@objectstack/client— already normalizes hierarchicalcapabilitiesto flat booleans, soclient.capabilities.transactionalBatchis exposed (and now typed) for declarative consumers.
The bit follows the existing capability semantics:
true⟺ the/batchroute is mounted and the runtime can honour a transaction — the exact condition under which the endpoint returns200rather than404/405/501. Additive and behavior-preserving; only the discovery payload gains a field. -
62a2117: Split the overloaded
managedBy: 'system'bucket with an explicitengine-ownedvalue (ADR-0103 addendum, #3343). ADR-0103 deferred the enum split ("revisitable later as a rename") because a newmanagedByvalue would fall through to the fully-editableplatformdefault on deployed Console clients. Both reasons against it are now retired — the server-side write guard /apiMethodsreconciliation //me/permissionsclamp make that fallthrough cosmetic (the write is rejected regardless of what the client renders), and objectui#2712 closed the UI union — so v16 lands it, additively.- New enum value
engine-ownedwith the same all-locked default affordance row assystem(create/import/edit/delete: false,exportCsv: true). It joinsENGINE_OWNED_BUCKETS(the engine write guard) andGUARDED_WRITE_BUCKETS(the/me/permissionsclamp); the guard,reconcileManagedApiMethods, and the clamp mechanisms are unchanged —engine-ownedis an explicit member of the set they already covered by resolved affordance. - 20 objects relabelled
system → engine-owned— the ones the engine owns end to end and that declared no write-openinguserActions(the metadata store, jobs, approval runtime rows, sharing rows,sys_automation_run, the messaging delivery/receipt pipeline,sys_secret, settings). One-line, behaviour-identical per object. - 8 admin/user-writable objects keep
managedBy: 'system'(the RBAC link tables,sys_user_preference,sys_approval_delegation, the messaging config grids) —systemnow reads as "engine-managed schema, writable viauserActions".
Behaviour-, enforcement- and wire-identical: resolved affordances, the guard verdict, the 405
apiMethodsreconciliation, and the permissions clamp are the same before and after — this is a self-documenting relabel, not a policy change. No data migration (managedByis schema metadata) and no code branches on the'system'literal. Retiring the overloadedsystementirely (moving the 8 writable objects to a dedicated bucket) is a breaking rename deferred to v17. - New enum value
-
efbcfe1: feat(observability): admin-only richer per-request timing detail via
X-OS-Debug-Timing: json(#2408)Completes the optional "richer JSON" diagnostic from #2408. In addition to the
basicServer-Timingheader, an admin/service caller can now request a
per-query breakdown — the slowest SQL statements and a query count — by sending
X-OS-Debug-Timing: json. The detail is returned in a separate
X-OS-Debug-Timing-Detailresponse header (compact JSON) and is admin-only,
even under global mode: an ordinary caller never sees SQL shapes.- observability:
PerfTiminggains opt-in per-event detail capture
(enableDetail/recordDetail/details) plus the ambient
recordServerTimingDetail. The disclosure gate gains aprivilegedlevel
(set byallowPerfDisclosure, read viaisPerfDisclosurePrivileged) so the
richer detail can be gated independently of the basic header. - driver-sql: when detail capture is on, the query listener additionally
records each query's parametrized statement (knex'sq.sql,?
placeholders) — never the bindings, so no literal row value ever enters the
collector. Zero overhead when detail is off. - plugin-hono-server:
X-OS-Debug-Timing: jsonenables detail capture; the
middleware emitsX-OS-Debug-Timing-Detail(slowest queries, capped and
sanitized to header-safe ASCII) only when the principal is a proven admin.
Basic and global behavior are unchanged;
jsonis purely additive. - observability:
-
2049b6a: feat(observability): admin-gated per-request
Server-TimingviaX-OS-Debug-Timing(#2408)Perf-tuning mode was previously global-only (
serverTimingoption /
OS_SERVER_TIMING), which discloses internal phase durations — a mild
backend-fingerprinting surface — to every caller. This adds the per-request
gating path from the design so an operator can pull a single request's
Server-Timingbreakdown on a live environment without turning the header on
for everyone.- observability: a request-scoped disclosure gate (
runWithPerfDisclosure,
allowPerfDisclosure,isPerfDisclosureAllowed,PerfDisclosureGate) k...
- observability: a request-scoped disclosure gate (
@objectstack/plugin-email@16.0.0
Patch Changes
- Updated dependencies [f972574]
- Updated dependencies [6289ec3]
- Updated dependencies [22013aa]
- Updated dependencies [3ad3dd5]
- Updated dependencies [8efa395]
- Updated dependencies [3a18b60]
- Updated dependencies [a8aa34c]
- Updated dependencies [e057f42]
- Updated dependencies [a3823b2]
- Updated dependencies [bc65105]
- Updated dependencies [43a3efb]
- Updated dependencies [524696a]
- Updated dependencies [6b51346]
- Updated dependencies [80273c8]
- Updated dependencies [bfa3c3f]
- Updated dependencies [5e3301d]
- Updated dependencies [dd9f223]
- Updated dependencies [46e876c]
- Updated dependencies [7125007]
- Updated dependencies [5f05de2]
- Updated dependencies [021ba4c]
- Updated dependencies [158aa14]
- Updated dependencies [62a2117]
- Updated dependencies [d2723e2]
- Updated dependencies [fefcd54]
- Updated dependencies [beaf2de]
- Updated dependencies [369eb6e]
- Updated dependencies [06ff734]
- Updated dependencies [b659111]
- Updated dependencies [5754a23]
- Updated dependencies [6c270a6]
- Updated dependencies [290e2f0]
- Updated dependencies [668dd17]
- Updated dependencies [8abf133]
- Updated dependencies [e0859b1]
- Updated dependencies [04ecd4e]
- Updated dependencies [4d5a892]
- Updated dependencies [16cebeb]
- Updated dependencies [86d30af]
- Updated dependencies [8923843]
- Updated dependencies [ea32ec7]
- Updated dependencies [a2795f6]
- Updated dependencies [f16b492]
- Updated dependencies [4b6fde8]
- Updated dependencies [2018df9]
- Updated dependencies [fc5a3a2]
- Updated dependencies [8ff9210]
- @objectstack/spec@16.0.0
- @objectstack/platform-objects@16.0.0
- @objectstack/core@16.0.0
- @objectstack/formula@16.0.0
@objectstack/plugin-dev@16.0.0
Patch Changes
- Updated dependencies [b39c65d]
- Updated dependencies [f972574]
- Updated dependencies [6289ec3]
- Updated dependencies [2f3c641]
- Updated dependencies [e38da5b]
- Updated dependencies [f9b118d]
- Updated dependencies [22013aa]
- Updated dependencies [3ad3dd5]
- Updated dependencies [8efa395]
- Updated dependencies [3a18b60]
- Updated dependencies [deb7e7e]
- Updated dependencies [a8aa34c]
- Updated dependencies [e057f42]
- Updated dependencies [a3823b2]
- Updated dependencies [43a3efb]
- Updated dependencies [524696a]
- Updated dependencies [fdc244e]
- Updated dependencies [bfa3c3f]
- Updated dependencies [5e3301d]
- Updated dependencies [dd9f223]
- Updated dependencies [46e876c]
- Updated dependencies [2ea08ee]
- Updated dependencies [d1d1c40]
- Updated dependencies [616e839]
- Updated dependencies [ee0a499]
- Updated dependencies [5f05de2]
- Updated dependencies [021ba4c]
- Updated dependencies [158aa14]
- Updated dependencies [9d897b3]
- Updated dependencies [62a2117]
- Updated dependencies [83e8f7d]
- Updated dependencies [d2723e2]
- Updated dependencies [674457a]
- Updated dependencies [fefcd54]
- Updated dependencies [efbcfe1]
- Updated dependencies [2049b6a]
- Updated dependencies [beaf2de]
- Updated dependencies [369eb6e]
- Updated dependencies [06ff734]
- Updated dependencies [b659111]
- Updated dependencies [5754a23]
- Updated dependencies [6c270a6]
- Updated dependencies [290e2f0]
- Updated dependencies [668dd17]
- Updated dependencies [8abf133]
- Updated dependencies [e0859b1]
- Updated dependencies [92f5f19]
- Updated dependencies [a2d6555]
- Updated dependencies [3a6310c]
- Updated dependencies [32899e6]
- Updated dependencies [515f11a]
- Updated dependencies [4174a07]
- Updated dependencies [ce468c8]
- Updated dependencies [04ecd4e]
- Updated dependencies [4d5a892]
- Updated dependencies [16cebeb]
- Updated dependencies [86d30af]
- Updated dependencies [8923843]
- Updated dependencies [a2795f6]
- Updated dependencies [f16b492]
- Updated dependencies [4b6fde8]
- Updated dependencies [2018df9]
- Updated dependencies [fc5a3a2]
- Updated dependencies [8ff9210]
- @objectstack/runtime@16.0.0
- @objectstack/spec@16.0.0
- @objectstack/plugin-security@16.0.0
- @objectstack/objectql@16.0.0
- @objectstack/plugin-hono-server@16.0.0
- @objectstack/rest@16.0.0
- @objectstack/plugin-auth@16.0.0
- @objectstack/core@16.0.0
- @objectstack/types@16.0.0
- @objectstack/account@16.0.0
- @objectstack/setup@16.0.0
- @objectstack/driver-memory@16.0.0
- @objectstack/service-i18n@16.0.0
@objectstack/plugin-auth@16.0.0
Minor Changes
-
616e839: Bulk user import defaults to
auto— prefer invite per row, temporary only for undeliverable rows (#3236). The identity import endpoint (POST /api/v1/auth/admin/import-users) gains a fourthpasswordPolicy,auto, and it is now the default (wasnone).autodecides per row instead of forcing one policy on the whole batch:- a row with a deliverable channel — a real email + a wired email service, or a phone + a wired SMS-invite path — is invited (a set-your-password email, or an invitation SMS for phone-only rows), so no shared secret ever leaves the server;
- a row with no deliverable channel (placeholder email, phone-only without SMS, or an email row when no email service is wired) falls back to a temporary password, returned once in the response with
must_change_passwordstamped.
This shrinks the temporary-password blast radius from "the whole batch" to "only the rows that genuinely can't be reached", and — unlike
invite—autonever rejects the request for missing infrastructure: with nothing wired, every row simply degrades to temporary. The per-row outcome is surfaced onrows[].delivery(email/sms/temporary) with a batch breakdown onsummary.delivery(also recorded in the run audit).The three existing policies are unchanged and still selectable explicitly:
invite— force the invite path for every row; unreachable rows are failed per-row (never downgraded). Pick this when a temporary-password fallback is unacceptable.temporary— force a generated temporary password for every row.none— identity only, no password and no invitation.
Behavior change to note: callers that omit
passwordPolicypreviously gotnone(no credential, no outbound message); they now getauto, which proactively sends invitations to deliverable rows (and returns temporary passwords for the rest). Callers that want the old identity-only behavior must passpasswordPolicy: 'none'explicitly. Every call that already passes an explicit policy is unaffected, and the response is a strict superset (adds thedeliveryfields).
Patch Changes
-
deb7e7e: fix(plugin-auth): run better-auth adapter WRITES as system context so #2948 doesn't strip readonly identity columns (#3164)
The better-auth ObjectQL adapter wrapped the engine so its READS carried
isSystem(to bypass the control-plane org-scope read hook), but its WRITES
passed through with no context. The static-readonlyUPDATE strip (#2948) runs
on any non-system update — and since the adapter carries no caller context,
!ctx?.isSystemwastrue, so the strip silently DROPPED better-auth's own
writes to readonlysys_usercolumns:email(change-email),banned/
ban_reason/ban_expires(admin ban). Those operations returned success but
never persisted.withSystemReadContextis renamed towithSystemContext(a deprecated alias is
kept for one release) and now injectsisSystemoninsert/update/
deleteas well as reads. This is correct because these are the identity
authority's own writes — user-context writes tomanagedBy: 'better-auth'tables
are already rejected upstream by the ADR-0092 identity write guard, so the
adapter path only ever carries better-auth's internal writes.Found while implementing #3043 (the INSERT-side readonly strip). This is its
UPDATE-side dual: #3043 relocated the insert strip to the external ingress
precisely because internal writers (this adapter included) don't declare
isSystem; the pre-existing engine-level UPDATE strip has no such relocation, so
the adapter had to declare its writes system. -
fdc244e: Dev-loop DX fixes from the 15.1 third-party evaluation (P2 batch):
- Hot-added objects are now queryable without a restart. Adding a
*.object.tsunderos devused to recompile "green" while every query answeredno such table(ornot registered) until a manual restart: the artifact reload never notified the ObjectQL registry, tables were only created at boot, and seeds only loaded from the boot-time bundle. Themetadata:reloadedpayload now carries the parsed artifact; ObjectQL ingests the object definitions and re-runs the idempotent schema sync (sameskipSchemaSyncopt-out as boot), and the runtime loads seeds for first-seen objects (dev, single-tenant).os devalso prints✚ new object(s): …on recompile. - Dev admin credentials stay visible. The
os devstartup banner only showedadmin@objectos.ai / admin123on the boot that actually seeded it; with the persistent default DB every later boot hid it, and the Console login page never knew it existed. The hint now re-arms on every dev boot for as long as the account still verifies against the default password, andGET /api/v1/auth/configexposes a dev-gateddevSeedAdminfield (never present outsideNODE_ENV=development) so the login page can show it. os doctorreference analysis understands current metadata shapes. Objects bound throughdefineViewcontainers (list/listViews/form/formViews→data.object, subformchildObject, lookup form fields) and app navigation (objectName, nestedchildren,areas) were reported as "defined but not referenced". The collector now walks the canonical shapes (plus flow nodeconfig.object/objectName) and the orphan-view check descends into containers.
- Hot-added objects are now queryable without a restart. Adding a
-
Updated dependencies [f972574]
-
Updated dependencies [6289ec3]
-
Updated dependencies [22013aa]
-
Updated dependencies [3ad3dd5]
-
Updated dependencies [8efa395]
-
Updated dependencies [3a18b60]
-
Updated dependencies [a8aa34c]
-
Updated dependencies [e057f42]
-
Updated dependencies [a3823b2]
-
Updated dependencies [bc65105]
-
Updated dependencies [43a3efb]
-
Updated dependencies [524696a]
-
Updated dependencies [bfa3c3f]
-
Updated dependencies [5e3301d]
-
Updated dependencies [dd9f223]
-
Updated dependencies [46e876c]
-
Updated dependencies [5f05de2]
-
Updated dependencies [021ba4c]
-
Updated dependencies [158aa14]
-
Updated dependencies [62a2117]
-
Updated dependencies [83e8f7d]
-
Updated dependencies [d2723e2]
-
Updated dependencies [fefcd54]
-
Updated dependencies [beaf2de]
-
Updated dependencies [369eb6e]
-
Updated dependencies [06ff734]
-
Updated dependencies [b659111]
-
Updated dependencies [5754a23]
-
Updated dependencies [6c270a6]
-
Updated dependencies [290e2f0]
-
Updated dependencies [668dd17]
-
Updated dependencies [8abf133]
-
Updated dependencies [e0859b1]
-
Updated dependencies [92f5f19]
-
Updated dependencies [32899e6]
-
Updated dependencies [04ecd4e]
-
Updated dependencies [4d5a892]
-
Updated dependencies [16cebeb]
-
Updated dependencies [86d30af]
-
Updated dependencies [8923843]
-
Updated dependencies [a2795f6]
-
Updated dependencies [f16b492]
-
Updated dependencies [4b6fde8]
-
Updated dependencies [2018df9]
-
Updated dependencies [fc5a3a2]
-
Updated dependencies [8ff9210]
- @objectstack/spec@16.0.0
- @objectstack/platform-objects@16.0.0
- @objectstack/rest@16.0.0
- @objectstack/core@16.0.0
- @objectstack/types@16.0.0
@objectstack/plugin-audit@16.0.0
Patch Changes
-
2ea08ee: Flow trigger observability — kill the four-layer silence around record-change flows that never fire (2026-07-17 third-party eval).
A misauthored auto-launched flow (wrong
objectName, missingrequires: ['automation','triggers'], failing start condition) produced ZERO output at every layer: the engine's own registration/binding logs land inside the CLI's boot-quiet stdout window (which swallows debug/info/warn — only error/fatal reach stderr), and each "didn't happen" path was itself silent. Fixes:- Startup banner
Flows:section (os serve/os dev/os start): flow count, bound-to-trigger count, registered trigger types, draft count — plus loud⚠lines for flows declared with no automation engine enabled (requiresmissing), flows whose trigger type has no registered trigger, and bound record-change flows targeting an unknown object (dead binding). Printed after stdout is restored, so it is immune to the boot-quiet window. - Trigger-fired run failures now log at ERROR (stderr — always visible): the automation engine no longer drops the AutomationResult of a trigger-fired execution; condition-evaluation faults and node failures surface with the flow name. Condition-not-met skips stay at debug (high-frequency, intentional).
RecordChangeTriggerprobes object existence at bind time and warns when a flow'sobjectNamematches no registered object (exact-name matching), instead of silently arming a hook that can never fire.kernel:bootstrappedbinding audit in the automation plugin: warns per enabled-but-unbound triggered flow with the reason, and reports registered/bound/draft counts (AutomationEngine.getTriggerBindingAudit(), extendedgetFlowRuntimeStates()withstatus/triggerType/object).os validateflow-wiring advisories (@objectstack/lintvalidateFlowTriggerReadiness): warns when a record-triggered flow targets an object the stack does not define, and when an auto-triggered flow's status isdraft(authored or defaulted — draft flows still fire; declareactiveorobsolete).- Removed leftover boot-debug writes (
registerApp/AppPlugin/StandaloneStack/AuditPluginstderr noise) that previous debugging of this same silence had left behind.
- Startup banner
-
ee0a499: feat(i18n): localize collaboration notification titles and the storage objects; wire the notifications REST routes
Three gaps behind one report (a
sys_file "repro.png" assigned to you
notification that was English on an all-Chinese workspace, opened an English
detail page, and never cleared its unread state):-
plugin-audit — the assignment (
collab.assignment) and @mention
(collab.mention) bell titles were hardcoded English literals built from the
raw object API name. They now resolve through the i18n service with the same
key shapes as the activity summaries (framework#3039): new
messages.assignedToYou/messages.mentionedYou/
messages.mentionedYouAnonymoustemplates (en / zh-CN / ja-JP / es-ES), the
object named by its translated label (objects.{name}.label→ authored def
label → API name), and the locale resolved for the recipient (they read
the bell), not the acting user. Every step stays best-effort: no locale / no
i18n / key miss degrades to the English literal — which now also prefers the
authored object label over the API name. -
service-storage —
sys_file/sys_upload_sessionhad no translation
bundle at all, so the file detail page (labels, and the Pending Upload /
Committed / Deleted status pipeline) rendered English on every locale. The
service now ships its own ADR-0029 D8 bundle (en / zh-CN / ja-JP / es-ES,
src/translations+scripts/i18n-extract.config.ts) and contributes it via
i18n.loadTranslationsonkernel:ready, matching service-messaging.
(sys_attachmentstays in platform-objects' bundles pending the
storage-domain decomposition.) -
runtime — the in-app notifications REST surface (
GET /api/v1/notifications,POST /api/v1/notifications/read,POST /api/v1/notifications/read/all; ADR-0030) had itshandleNotification
dispatch branch and discovery entry, but noserver.<verb>()mount in
dispatcher-plugin, so only the cloud hosts' hono catch-all reached it — the
standalone /os devserver 404'd every request. That left mark-read with no
working endpoint (the console's directsys_notification_receiptwrite is
rejected by ADR-0103's engine-owned gate), so unread notifications could never
clear. The three routes are now mounted explicitly, guarded by the
route-registration regression test.
-
-
Updated dependencies [f972574]
-
Updated dependencies [6289ec3]
-
Updated dependencies [22013aa]
-
Updated dependencies [3ad3dd5]
-
Updated dependencies [8efa395]
-
Updated dependencies [3a18b60]
-
Updated dependencies [a8aa34c]
-
Updated dependencies [e057f42]
-
Updated dependencies [a3823b2]
-
Updated dependencies [bc65105]
-
Updated dependencies [43a3efb]
-
Updated dependencies [524696a]
-
Updated dependencies [bfa3c3f]
-
Updated dependencies [5e3301d]
-
Updated dependencies [dd9f223]
-
Updated dependencies [46e876c]
-
Updated dependencies [5f05de2]
-
Updated dependencies [021ba4c]
-
Updated dependencies [158aa14]
-
Updated dependencies [62a2117]
-
Updated dependencies [d2723e2]
-
Updated dependencies [fefcd54]
-
Updated dependencies [beaf2de]
-
Updated dependencies [369eb6e]
-
Updated dependencies [06ff734]
-
Updated dependencies [b659111]
-
Updated dependencies [5754a23]
-
Updated dependencies [6c270a6]
-
Updated dependencies [290e2f0]
-
Updated dependencies [668dd17]
-
Updated dependencies [8abf133]
-
Updated dependencies [e0859b1]
-
Updated dependencies [04ecd4e]
-
Updated dependencies [4d5a892]
-
Updated dependencies [16cebeb]
-
Updated dependencies [86d30af]
-
Updated dependencies [8923843]
-
Updated dependencies [a2795f6]
-
Updated dependencies [f16b492]
-
Updated dependencies [4b6fde8]
-
Updated dependencies [2018df9]
-
Updated dependencies [fc5a3a2]
-
Updated dependencies [8ff9210]
- @objectstack/spec@16.0.0
- @objectstack/platform-objects@16.0.0
- @objectstack/core@16.0.0
@objectstack/plugin-approvals@16.0.0
Minor Changes
-
e412fb6: feat(approvals): declare file attachments on approve/reject decisions
The declared
approval_approve/approval_rejectactions on
sys_approval_requestgain an optional multi-fileattachmentsparam
(type: 'file',multiple). The console renderstype:'file'action params
through the shared upload widget (objectui ADR-0059) and POSTs the resolved
attachments: string[], so a reviewer can attach supporting files to a
decision through the generic declared-action dialog — letting the approvals
inbox retire its hand-wired attachment composer (objectui#2698).Purely additive metadata: the decision route already forwards
body.attachmentstoApprovalService.decide, and the
sys_approval_action.attachmentscolumn (file, multiple) already persists them
(#3266/#3274). No service or route change. -
8efa395: feat(approvals): server-computed
viewercapability for precise decision-action gatinggetRequest/listRequestsnow attach a per-viewer block —
viewer: { can_act, is_submitter }— computed from the caller's context
(ApprovalRequestRow.viewer):can_act— the caller is a current pending approver (their user id is in the
request's resolvedpending_approverswhile it is stillpending). This is
the same check the decision methods authorize with, so it already reflects
position/team/manager resolution — strictly more accurate than a client-side
identity guess.is_submitter— the caller submitted the request.
The declared decision actions on
sys_approval_requestnow gate on it: approver
actions (approve/reject/reassign/send-back/request-info) use
record.viewer.can_act; submitter levers (remind/recall/resubmit) use
record.viewer.is_submitter. Previously approver actions only trimmed the
non-pending case, so a submitter viewing their own pending request saw buttons
they couldn't use (the backend 403'd); a position-addressed approver could be
wrongly hidden by the old client heuristic. Wherevieweris absent (a row
surfaced outside a service read with a user context), the predicate fails closed. -
3a18b60: feat(approvals): rename the
roleapprover type toorg_membership_level(#3133)ApproverType.rolewas the last platform surface projecting the reserved word
"role" (ADR-0090 D3). It is not covered by D3's better-auth exception: that
exception protects better-auth's ownsys_member.rolecolumn, which we do
not own —ApproverTypeis our own enum, an authoring surface, and D3 mandates
that the projection of that concept is spelledorg_membership_leveland
labelled "organization membership", never "role".The sentence licensing the leak was also false: ADR-0090 D3 claims
sys_member.roleis "already relabelledorg_membership_levelin the platform
projection", butorg_membership_levelexisted nowhere in the codebase and
ADR-0057 D7 lists that relabel under "Deferred (evidence-gated, P4)". The
projection never landed, so the word reached authors.The name manufactured a real, silent failure — "hotcrm class": every other
surface renamed toposition(sys_role,ShareRecipientType.role,
ctx.roles[]), so{ type: 'role', value: 'sales_manager' }reads as the
legacy spelling of a position. It resolves against the membership tier, finds
no member row, falls back to an inertrole:sales_managerliteral, and the
request waits forever on an approver that cannot exist.- spec:
ApproverTypegainsorg_membership_level;rolestays as a
deprecated alias for one window (a published 15.x flow keeps loading) with
DEPRECATED_APPROVER_TYPES+canonicalApproverType()as the single source
for the mapping. Removed in the next major. - plugin-approvals: resolves on the canonical type and warns on the
deprecated spelling. Thetype:valuefallback literal keeps the authored
spelling — storedsys_approval_approverrows andpending_approversslots
from 15.x carryrole:<v>, and rewriting it would orphan them. - lint:
approval-role-not-membership-tier→approval-approver-not-membership-tier
(the rule id carried the reserved word too), plus a new
approval-approver-type-deprecated. The two are mutually exclusive: a bad
value wins, because prescribingorg_membership_levelfor a position name
would be wrong advice — the fix there isposition.
Authoring
type: 'role'keeps working and now says so out loud. Rewrite it as
org_membership_level; if the value is an org position, the fix isposition. - spec:
Patch Changes
-
22013aa: Split the overloaded
managedBy: 'system'bucket into engine-owned vs. admin-writable, and enforce engine-owned writes (ADR-0103, #3220). Thesystembucket conflated two incompatible write policies: rows a platform service owns end to end (never user-written), and platform-defined schema whose rows are legitimately admin/user-writable. It carried the same all-false affordance row asbetter-auth/append-onlybut, unlikebetter-auth, had no engine enforcement — a wildcard admin could raw-write these rows through the generic data API (ADR-0049 gap).Rather than add a new
managedByenum value (which would fall through to fully-editableplatformdefaults on already-deployed Console clients), the write policy is now the resolved affordance (resolveCrudAffordances= bucket default +userActions), and engine-owned is defined as asystem/append-onlyobject that grants no write:- Writable set declares
userActions— the RBAC link tables (sys_user_position,sys_user_permission_set,sys_position_permission_set),sys_user_preference,sys_approval_delegation, and the messaging config grids (sys_notification_preference/…_subscription/…_template) now declareuserActions: { create, edit, delete: true }. The affordance is a declaration only — theDelegatedAdminGate/ RLS / permission sets remain the authz. - Engine-owned objects locked to reads —
apiMethods: ['get','list']added where absent (jobs, notifications, approval request/approver/token/action,sys_record_share,sys_automation_run, mail/settings/secret audit, the messaging delivery pipeline).sys_secretis explicitly read-locked (an emptyapiMethodsarray fails open). sys_import_jobstays engine-owned: the REST import route now writes its job rowsisSystem-elevated (attribution preserved via the explicitcreated_bystamp) and the object is locked to['get','list'].- New engine write guard (
assertEngineOwnedWriteAllowed, plugin-security) fail-closed rejects user-context generic writes to engine-ownedsystem/append-onlyobjects, keyed off the resolved affordance;isSystemand context-less engine/service writes bypass by construction. Wired into the security middleware alongside the other data-layer gates. reconcileManagedApiMethods(objectql registry) now runs for every managed bucket, not justbetter-auth: any advertised write verb an object's resolved affordances forbid is stripped at registration with a warning (the drift backstop, ADR-0049)./me/permissionsclamp (plugin-hono-server) now clampssystem/append-onlyas well asbetter-auth, so the client hint reflectspermission ∩ guard.
Potentially breaking: a downstream/third-party
systemobject that advertised generic write verbs relying on today's fail-open behaviour will have those verbs stripped (with a warning) and user-context generic writes to it rejected. DeclareuserActionsopening the verbs the object legitimately takes from a user context.better-authkeeps plugin-auth's identity write guard unchanged; the row-levelmanaged_byprovenance vocabulary (ADR-0066) is a different axis and is untouched. - Writable set declares
-
62a2117: Split the overloaded
managedBy: 'system'bucket with an explicitengine-ownedvalue (ADR-0103 addendum, #3343). ADR-0103 deferred the enum split ("revisitable later as a rename") because a newmanagedByvalue would fall through to the fully-editableplatformdefault on deployed Console clients. Both reasons against it are now retired — the server-side write guard /apiMethodsreconciliation //me/permissionsclamp make that fallthrough cosmetic (the write is rejected regardless of what the client renders), and objectui#2712 closed the UI union — so v16 lands it, additively.- New enum value
engine-ownedwith the same all-locked default affordance row assystem(create/import/edit/delete: false,exportCsv: true). It joinsENGINE_OWNED_BUCKETS(the engine write guard) andGUARDED_WRITE_BUCKETS(the/me/permissionsclamp); the guard,reconcileManagedApiMethods, and the clamp mechanisms are unchanged —engine-ownedis an explicit member of the set they already covered by resolved affordance. - 20 objects relabelled
system → engine-owned— the ones the engine owns end to end and that declared no write-openinguserActions(the metadata store, jobs, approval runtime rows, sharing rows,sys_automation_run, the messaging delivery/receipt pipeline,sys_secret, settings). One-line, behaviour-identical per object. - 8 admin/user-writable objects keep
managedBy: 'system'(the RBAC link tables,sys_user_preference,sys_approval_delegation, the messaging config grids) —systemnow reads as "engine-managed schema, writable viauserActions".
Behaviour-, enforcement- and wire-identical: resolved affordances, the guard verdict, the 405
apiMethodsreconciliation, and the permissions clamp are the same before and after — this is a self-documenting relabel, not a policy change. No data migration (managedByis schema metadata) and no code branches on the'system'literal. Retiring the overloadedsystementirely (moving the 8 writable objects to a dedicated bucket) is a breaking rename deferred to v17.
... - New enum value
@objectstack/platform-objects@16.0.0
Minor Changes
-
bc65105: feat(platform-objects): surface phone number in the create_user result dialog
sys_user'screate_useraction now declaresuser.phoneNumberin its
resultDialog.fields, so admins creating phone-based accounts see the
sign-in phone number alongside the email and temporary password. The
create-user response carriesphoneNumberonly for phone-based users;
objectui's ActionResultDialog skips declared fields whose path is absent
from the payload, so email-only users see no extra row.
Patch Changes
-
6289ec3: feat(i18n): translation slot for action
resultDialogcopy — the one-shot secret-reveal dialogs are now localizableThe post-success
resultDialog(temporary passwords, 2FA backup codes, OAuth
client secrets) had no slot in the translation protocol, so its title /
description / acknowledge button / field labels always rendered the hardcoded
English metadata literals even on fully-translated locales.- spec.
_actions.<action>(object + object-first node) and
globalActions.<action>gain an optionalresultDialogtranslation node
(ActionResultDialogTranslationSchema):title,description,
acknowledge, andfieldskeyed by the literal result-field path
(e.g."user.email"— keys may contain dots; resolvers index the record
directly, never split on.). NewresolveActionResultDialogoverlay
resolver, wired intotranslateActionfor API-boundary translation. - cli.
os i18n extractemits the newresultDialog.*keys (title /
description / acknowledge /fields.<path>for labelled fields), so
coverage and skeleton generation see them. - platform-objects. en / zh-CN / ja-JP / es-ES bundles ship the
resultDialog copy for all six shipped dialogs:sys_user.create_user,
sys_user.set_user_password,sys_two_factor.enable_two_factor,
sys_two_factor.regenerate_backup_codes,
sys_oauth_application.create_oauth_application, and
sys_oauth_application.rotate_client_secret.
Client-side rendering lands in objectui (
actionResultDialogresolver in
@object-ui/i18n+ result-dialog handlers). Purely additive — untranslated
locales keep falling back to the metadata literals. - spec.
-
22013aa: Split the overloaded
managedBy: 'system'bucket into engine-owned vs. admin-writable, and enforce engine-owned writes (ADR-0103, #3220). Thesystembucket conflated two incompatible write policies: rows a platform service owns end to end (never user-written), and platform-defined schema whose rows are legitimately admin/user-writable. It carried the same all-false affordance row asbetter-auth/append-onlybut, unlikebetter-auth, had no engine enforcement — a wildcard admin could raw-write these rows through the generic data API (ADR-0049 gap).Rather than add a new
managedByenum value (which would fall through to fully-editableplatformdefaults on already-deployed Console clients), the write policy is now the resolved affordance (resolveCrudAffordances= bucket default +userActions), and engine-owned is defined as asystem/append-onlyobject that grants no write:- Writable set declares
userActions— the RBAC link tables (sys_user_position,sys_user_permission_set,sys_position_permission_set),sys_user_preference,sys_approval_delegation, and the messaging config grids (sys_notification_preference/…_subscription/…_template) now declareuserActions: { create, edit, delete: true }. The affordance is a declaration only — theDelegatedAdminGate/ RLS / permission sets remain the authz. - Engine-owned objects locked to reads —
apiMethods: ['get','list']added where absent (jobs, notifications, approval request/approver/token/action,sys_record_share,sys_automation_run, mail/settings/secret audit, the messaging delivery pipeline).sys_secretis explicitly read-locked (an emptyapiMethodsarray fails open). sys_import_jobstays engine-owned: the REST import route now writes its job rowsisSystem-elevated (attribution preserved via the explicitcreated_bystamp) and the object is locked to['get','list'].- New engine write guard (
assertEngineOwnedWriteAllowed, plugin-security) fail-closed rejects user-context generic writes to engine-ownedsystem/append-onlyobjects, keyed off the resolved affordance;isSystemand context-less engine/service writes bypass by construction. Wired into the security middleware alongside the other data-layer gates. reconcileManagedApiMethods(objectql registry) now runs for every managed bucket, not justbetter-auth: any advertised write verb an object's resolved affordances forbid is stripped at registration with a warning (the drift backstop, ADR-0049)./me/permissionsclamp (plugin-hono-server) now clampssystem/append-onlyas well asbetter-auth, so the client hint reflectspermission ∩ guard.
Potentially breaking: a downstream/third-party
systemobject that advertised generic write verbs relying on today's fail-open behaviour will have those verbs stripped (with a warning) and user-context generic writes to it rejected. DeclareuserActionsopening the verbs the object legitimately takes from a user context.better-authkeeps plugin-auth's identity write guard unchanged; the row-levelmanaged_byprovenance vocabulary (ADR-0066) is a different axis and is untouched. - Writable set declares
-
62a2117: Split the overloaded
managedBy: 'system'bucket with an explicitengine-ownedvalue (ADR-0103 addendum, #3343). ADR-0103 deferred the enum split ("revisitable later as a rename") because a newmanagedByvalue would fall through to the fully-editableplatformdefault on deployed Console clients. Both reasons against it are now retired — the server-side write guard /apiMethodsreconciliation //me/permissionsclamp make that fallthrough cosmetic (the write is rejected regardless of what the client renders), and objectui#2712 closed the UI union — so v16 lands it, additively.- New enum value
engine-ownedwith the same all-locked default affordance row assystem(create/import/edit/delete: false,exportCsv: true). It joinsENGINE_OWNED_BUCKETS(the engine write guard) andGUARDED_WRITE_BUCKETS(the/me/permissionsclamp); the guard,reconcileManagedApiMethods, and the clamp mechanisms are unchanged —engine-ownedis an explicit member of the set they already covered by resolved affordance. - 20 objects relabelled
system → engine-owned— the ones the engine owns end to end and that declared no write-openinguserActions(the metadata store, jobs, approval runtime rows, sharing rows,sys_automation_run, the messaging delivery/receipt pipeline,sys_secret, settings). One-line, behaviour-identical per object. - 8 admin/user-writable objects keep
managedBy: 'system'(the RBAC link tables,sys_user_preference,sys_approval_delegation, the messaging config grids) —systemnow reads as "engine-managed schema, writable viauserActions".
Behaviour-, enforcement- and wire-identical: resolved affordances, the guard verdict, the 405
apiMethodsreconciliation, and the permissions clamp are the same before and after — this is a self-documenting relabel, not a policy change. No data migration (managedByis schema metadata) and no code branches on the'system'literal. Retiring the overloadedsystementirely (moving the 8 writable objects to a dedicated bucket) is a breaking rename deferred to v17. - New enum value
-
Updated dependencies [f972574]
-
Updated dependencies [6289ec3]
-
Updated dependencies [22013aa]
-
Updated dependencies [3ad3dd5]
-
Updated dependencies [8efa395]
-
Updated dependencies [3a18b60]
-
Updated dependencies [a8aa34c]
-
Updated dependencies [a3823b2]
-
Updated dependencies [43a3efb]
-
Updated dependencies [524696a]
-
Updated dependencies [bfa3c3f]
-
Updated dependencies [5e3301d]
-
Updated dependencies [46e876c]
-
Updated dependencies [158aa14]
-
Updated dependencies [62a2117]
-
Updated dependencies [d2723e2]
-
Updated dependencies [fefcd54]
-
Updated dependencies [beaf2de]
-
Updated dependencies [06cb319]
-
Updated dependencies [369eb6e]
-
Updated dependencies [06ff734]
-
Updated dependencies [b659111]
-
Updated dependencies [5754a23]
-
Updated dependencies [6c270a6]
-
Updated dependencies [668dd17]
-
Updated dependencies [8abf133]
-
Updated dependencies [e0859b1]
-
Updated dependencies [04ecd4e]
-
Updated dependencies [4d5a892]
-
Updated dependencies [16cebeb]
-
Updated dependencies [86d30af]
-
Updated dependencies [8923843]
-
Updated dependencies [a2795f6]
-
Updated dependencies [f16b492]
-
Updated dependencies [4b6fde8]
-
Updated dependencies [2018df9]
-
Updated dependencies [fc5a3a2]
-
Updated dependencies [8ff9210]
- @objectstack/spec@16.0.0
- @objectstack/metadata-core@16.0.0