Skip to content

Releases: objectstack-ai/objectstack

@objectstack/plugin-security@16.0.0

Choose a tag to compare

@github-actions github-actions released this 21 Jul 06:26
f55be04

Minor Changes

  • 22013aa: Split the overloaded managedBy: 'system' bucket into engine-owned vs. admin-writable, and enforce engine-owned writes (ADR-0103, #3220). The system bucket 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 as better-auth/append-only but, unlike better-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 managedBy enum value (which would fall through to fully-editable platform defaults on already-deployed Console clients), the write policy is now the resolved affordance (resolveCrudAffordances = bucket default + userActions), and engine-owned is defined as a system/append-only object 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 declare userActions: { create, edit, delete: true }. The affordance is a declaration only — the DelegatedAdminGate / RLS / permission sets remain the authz.
    • Engine-owned objects locked to readsapiMethods: ['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_secret is explicitly read-locked (an empty apiMethods array fails open).
    • sys_import_job stays engine-owned: the REST import route now writes its job rows isSystem-elevated (attribution preserved via the explicit created_by stamp) and the object is locked to ['get','list'].
    • New engine write guard (assertEngineOwnedWriteAllowed, plugin-security) fail-closed rejects user-context generic writes to engine-owned system/append-only objects, keyed off the resolved affordance; isSystem and 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 just better-auth: any advertised write verb an object's resolved affordances forbid is stripped at registration with a warning (the drift backstop, ADR-0049).
    • /me/permissions clamp (plugin-hono-server) now clamps system/append-only as well as better-auth, so the client hint reflects permission ∩ guard.

    Potentially breaking: a downstream/third-party system object 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. Declare userActions opening the verbs the object legitimately takes from a user context. better-auth keeps plugin-auth's identity write guard unchanged; the row-level managed_by provenance vocabulary (ADR-0066) is a different axis and is untouched.

  • 62a2117: Split the overloaded managedBy: 'system' bucket with an explicit engine-owned value (ADR-0103 addendum, #3343). ADR-0103 deferred the enum split ("revisitable later as a rename") because a new managedBy value would fall through to the fully-editable platform default on deployed Console clients. Both reasons against it are now retired — the server-side write guard / apiMethods reconciliation / /me/permissions clamp 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-owned with the same all-locked default affordance row as system (create/import/edit/delete: false, exportCsv: true). It joins ENGINE_OWNED_BUCKETS (the engine write guard) and GUARDED_WRITE_BUCKETS (the /me/permissions clamp); the guard, reconcileManagedApiMethods, and the clamp mechanisms are unchanged — engine-owned is 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-opening userActions (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) — system now reads as "engine-managed schema, writable via userActions".

    Behaviour-, enforcement- and wire-identical: resolved affordances, the guard verdict, the 405 apiMethods reconciliation, and the permissions clamp are the same before and after — this is a self-documenting relabel, not a policy change. No data migration (managedBy is schema metadata) and no code branches on the 'system' literal. Retiring the overloaded system entirely (moving the 8 writable objects to a dedicated bucket) is a breaking rename deferred to v17.

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 adversarial KNOWN DIVERGENCE pins (scoped admin_full_access grant; 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 pure hasPlatformAdminCapability (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.posture rung (#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/organizations only): a principal whose carried rung is not PLATFORM_ADMIN no 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 scoped admin_full_access grant (sys_user_permission_set.organization_id non-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_set for admin_full_access rows with a non-null organization_id, and custom permission sets whose systemPermissions intersect {manage_metadata, manage_platform_settings, studio.access, manage_users}. To restore cross-tenant operator access for such a principal, grant the unscoped admin_full_access instead. 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/modifyAllRecords short-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_OBJECTS list — exactly the drift ADR-0092 forbids, and it had already drifted (the list carried 17 names while 22 schemas declare managedBy: 'better-auth', leaving sys_scim_provider, sys_sso_provider, and three sys_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 registered managedBy: 'better-auth' object into the four write-granting default sets (organization_admin, member_default, viewer_readonly, MCP write) at kernel:ready, mutating the shared in-memory bootstrapPermissionSets in place (the array the evaluator resolves and the seeder serializes — a DB-row-only fix would be dead code). Never touches admin_full_access, never overrides an existing explicit entry, ignores userActions (the better-auth bucket is hard-denied — sys_user's userActions.edit opens only a field-level whitelist the identity guard enforces).
    • The static BETTER_AUTH_MANAGED_OBJECTS list is completed to 22 and kept as a compile-time baseline (covers the pre-kernel:ready window), now pinned bidirectionally...
Read more

@objectstack/plugin-reports@16.0.0

Choose a tag to compare

@github-actions github-actions released this 21 Jul 06:26
f55be04

Patch Changes

@objectstack/plugin-pinyin-search@16.0.0

Choose a tag to compare

@github-actions github-actions released this 21 Jul 06:26
f55be04

Patch Changes

@objectstack/plugin-hono-server@16.0.0

Choose a tag to compare

@github-actions github-actions released this 21 Jul 06:26
f55be04

Minor Changes

  • 22013aa: Split the overloaded managedBy: 'system' bucket into engine-owned vs. admin-writable, and enforce engine-owned writes (ADR-0103, #3220). The system bucket 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 as better-auth/append-only but, unlike better-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 managedBy enum value (which would fall through to fully-editable platform defaults on already-deployed Console clients), the write policy is now the resolved affordance (resolveCrudAffordances = bucket default + userActions), and engine-owned is defined as a system/append-only object 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 declare userActions: { create, edit, delete: true }. The affordance is a declaration only — the DelegatedAdminGate / RLS / permission sets remain the authz.
    • Engine-owned objects locked to readsapiMethods: ['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_secret is explicitly read-locked (an empty apiMethods array fails open).
    • sys_import_job stays engine-owned: the REST import route now writes its job rows isSystem-elevated (attribution preserved via the explicit created_by stamp) and the object is locked to ['get','list'].
    • New engine write guard (assertEngineOwnedWriteAllowed, plugin-security) fail-closed rejects user-context generic writes to engine-owned system/append-only objects, keyed off the resolved affordance; isSystem and 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 just better-auth: any advertised write verb an object's resolved affordances forbid is stripped at registration with a warning (the drift backstop, ADR-0049).
    • /me/permissions clamp (plugin-hono-server) now clamps system/append-only as well as better-auth, so the client hint reflects permission ∩ guard.

    Potentially breaking: a downstream/third-party system object 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. Declare userActions opening the verbs the object legitimately takes from a user context. better-auth keeps plugin-auth's identity write guard unchanged; the row-level managed_by provenance vocabulary (ADR-0066) is a different axis and is untouched.

  • bfa3c3f: Broadcast a transactionalBatch capability 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's ObjectStackAdapter) had to probe: fire a /batch, read 404/405 (no route) or 501 (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.

    WellKnownCapabilitiesSchema gains a required transactionalBatch: 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 /batch handler runs its ops inside engine.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 reports false even on a transaction-capable engine (never advertise an endpoint that would 404).
    • @objectstack/plugin-hono-server (standalone discovery) — reports false: 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 hierarchical capabilities to flat booleans, so client.capabilities.transactionalBatch is exposed (and now typed) for declarative consumers.

    The bit follows the existing capability semantics: true ⟺ the /batch route is mounted and the runtime can honour a transaction — the exact condition under which the endpoint returns 200 rather than 404/405/501. Additive and behavior-preserving; only the discovery payload gains a field.

  • 62a2117: Split the overloaded managedBy: 'system' bucket with an explicit engine-owned value (ADR-0103 addendum, #3343). ADR-0103 deferred the enum split ("revisitable later as a rename") because a new managedBy value would fall through to the fully-editable platform default on deployed Console clients. Both reasons against it are now retired — the server-side write guard / apiMethods reconciliation / /me/permissions clamp 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-owned with the same all-locked default affordance row as system (create/import/edit/delete: false, exportCsv: true). It joins ENGINE_OWNED_BUCKETS (the engine write guard) and GUARDED_WRITE_BUCKETS (the /me/permissions clamp); the guard, reconcileManagedApiMethods, and the clamp mechanisms are unchanged — engine-owned is 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-opening userActions (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) — system now reads as "engine-managed schema, writable via userActions".

    Behaviour-, enforcement- and wire-identical: resolved affordances, the guard verdict, the 405 apiMethods reconciliation, and the permissions clamp are the same before and after — this is a self-documenting relabel, not a policy change. No data migration (managedBy is schema metadata) and no code branches on the 'system' literal. Retiring the overloaded system entirely (moving the 8 writable objects to a dedicated bucket) is a breaking rename deferred to v17.

  • 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
    basic Server-Timing header, 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-Detail response header (compact JSON) and is admin-only,
    even under global mode
    : an ordinary caller never sees SQL shapes.

    • observability: PerfTiming gains opt-in per-event detail capture
      (enableDetail / recordDetail / details) plus the ambient
      recordServerTimingDetail. The disclosure gate gains a privileged level
      (set by allowPerfDisclosure, read via isPerfDisclosurePrivileged) 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's q.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: json enables detail capture; the
      middleware emits X-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; json is purely additive.

  • 2049b6a: feat(observability): admin-gated per-request Server-Timing via X-OS-Debug-Timing (#2408)

    Perf-tuning mode was previously global-only (serverTiming option /
    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-Timing breakdown on a live environment without turning the header on
    for everyone.

    • observability: a request-scoped disclosure gate (runWithPerfDisclosure,
      allowPerfDisclosure, isPerfDisclosureAllowed, PerfDisclosureGate) k...
Read more

@objectstack/plugin-email@16.0.0

Choose a tag to compare

@github-actions github-actions released this 21 Jul 06:26
f55be04

Patch Changes

@objectstack/plugin-dev@16.0.0

Choose a tag to compare

@github-actions github-actions released this 21 Jul 06:26
f55be04

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

Choose a tag to compare

@github-actions github-actions released this 21 Jul 06:26
f55be04

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 fourth passwordPolicy, auto, and it is now the default (was none).

    auto decides 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_password stamped.

    This shrinks the temporary-password blast radius from "the whole batch" to "only the rows that genuinely can't be reached", and — unlike inviteauto never rejects the request for missing infrastructure: with nothing wired, every row simply degrades to temporary. The per-row outcome is surfaced on rows[].delivery (email / sms / temporary) with a batch breakdown on summary.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 passwordPolicy previously got none (no credential, no outbound message); they now get auto, which proactively sends invitations to deliverable rows (and returns temporary passwords for the rest). Callers that want the old identity-only behavior must pass passwordPolicy: 'none' explicitly. Every call that already passes an explicit policy is unaffected, and the response is a strict superset (adds the delivery fields).

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-readonly UPDATE strip (#2948) runs
    on any non-system update — and since the adapter carries no caller context,
    !ctx?.isSystem was true, so the strip silently DROPPED better-auth's own
    writes to readonly sys_user columns: email (change-email), banned /
    ban_reason / ban_expires (admin ban). Those operations returned success but
    never persisted.

    withSystemReadContext is renamed to withSystemContext (a deprecated alias is
    kept for one release) and now injects isSystem on insert / update /
    delete as well as reads. This is correct because these are the identity
    authority's own writes — user-context writes to managedBy: '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.ts under os dev used to recompile "green" while every query answered no such table (or not 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. The metadata:reloaded payload now carries the parsed artifact; ObjectQL ingests the object definitions and re-runs the idempotent schema sync (same skipSchemaSync opt-out as boot), and the runtime loads seeds for first-seen objects (dev, single-tenant). os dev also prints ✚ new object(s): … on recompile.
    • Dev admin credentials stay visible. The os dev startup banner only showed admin@objectos.ai / admin123 on 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, and GET /api/v1/auth/config exposes a dev-gated devSeedAdmin field (never present outside NODE_ENV=development) so the login page can show it.
    • os doctor reference analysis understands current metadata shapes. Objects bound through defineView containers (list/listViews/form/formViewsdata.object, subform childObject, lookup form fields) and app navigation (objectName, nested children, areas) were reported as "defined but not referenced". The collector now walks the canonical shapes (plus flow node config.object/objectName) and the orphan-view check descends into containers.
  • 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

Choose a tag to compare

@github-actions github-actions released this 21 Jul 06:26
f55be04

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, missing requires: ['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 (requires missing), 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).
    • RecordChangeTrigger probes object existence at bind time and warns when a flow's objectName matches no registered object (exact-name matching), instead of silently arming a hook that can never fire.
    • kernel:bootstrapped binding audit in the automation plugin: warns per enabled-but-unbound triggered flow with the reason, and reports registered/bound/draft counts (AutomationEngine.getTriggerBindingAudit(), extended getFlowRuntimeStates() with status/triggerType/object).
    • os validate flow-wiring advisories (@objectstack/lint validateFlowTriggerReadiness): warns when a record-triggered flow targets an object the stack does not define, and when an auto-triggered flow's status is draft (authored or defaulted — draft flows still fire; declare active or obsolete).
    • Removed leftover boot-debug writes (registerApp/AppPlugin/StandaloneStack/AuditPlugin stderr noise) that previous debugging of this same silence had left behind.
  • 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.mentionedYouAnonymous templates (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-storagesys_file / sys_upload_session had 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.loadTranslations on kernel:ready, matching service-messaging.
      (sys_attachment stays 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 its handleNotification
      dispatch branch and discovery entry, but no server.<verb>() mount in
      dispatcher-plugin, so only the cloud hosts' hono catch-all reached it — the
      standalone / os dev server 404'd every request. That left mark-read with no
      working endpoint (the console's direct sys_notification_receipt write 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

Choose a tag to compare

@github-actions github-actions released this 21 Jul 06:26
f55be04

Minor Changes

  • e412fb6: feat(approvals): declare file attachments on approve/reject decisions

    The declared approval_approve / approval_reject actions on
    sys_approval_request gain an optional multi-file attachments param
    (type: 'file', multiple). The console renders type:'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.attachments to ApprovalService.decide, and the
    sys_approval_action.attachments column (file, multiple) already persists them
    (#3266/#3274). No service or route change.

  • 8efa395: feat(approvals): server-computed viewer capability for precise decision-action gating

    getRequest / listRequests now 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 resolved pending_approvers while it is still pending). 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_request now 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. Where viewer is absent (a row
    surfaced outside a service read with a user context), the predicate fails closed.

  • 3a18b60: feat(approvals): rename the role approver type to org_membership_level (#3133)

    ApproverType.role was 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 own sys_member.role column, which we do
    not own — ApproverType is our own enum, an authoring surface, and D3 mandates
    that the projection of that concept is spelled org_membership_level and
    labelled "organization membership", never "role".

    The sentence licensing the leak was also false: ADR-0090 D3 claims
    sys_member.role is "already relabelled org_membership_level in the platform
    projection", but org_membership_level existed 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 to position (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 inert role:sales_manager literal, and the
    request waits forever on an approver that cannot exist.

    • spec: ApproverType gains org_membership_level; role stays 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. The type:value fallback literal keeps the authored
      spelling — stored sys_approval_approver rows and pending_approvers slots
      from 15.x carry role:<v>, and rewriting it would orphan them.
    • lint: approval-role-not-membership-tierapproval-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 prescribing org_membership_level for a position name
      would be wrong advice — the fix there is position.

    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 is position.

Patch Changes

  • 22013aa: Split the overloaded managedBy: 'system' bucket into engine-owned vs. admin-writable, and enforce engine-owned writes (ADR-0103, #3220). The system bucket 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 as better-auth/append-only but, unlike better-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 managedBy enum value (which would fall through to fully-editable platform defaults on already-deployed Console clients), the write policy is now the resolved affordance (resolveCrudAffordances = bucket default + userActions), and engine-owned is defined as a system/append-only object 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 declare userActions: { create, edit, delete: true }. The affordance is a declaration only — the DelegatedAdminGate / RLS / permission sets remain the authz.
    • Engine-owned objects locked to readsapiMethods: ['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_secret is explicitly read-locked (an empty apiMethods array fails open).
    • sys_import_job stays engine-owned: the REST import route now writes its job rows isSystem-elevated (attribution preserved via the explicit created_by stamp) and the object is locked to ['get','list'].
    • New engine write guard (assertEngineOwnedWriteAllowed, plugin-security) fail-closed rejects user-context generic writes to engine-owned system/append-only objects, keyed off the resolved affordance; isSystem and 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 just better-auth: any advertised write verb an object's resolved affordances forbid is stripped at registration with a warning (the drift backstop, ADR-0049).
    • /me/permissions clamp (plugin-hono-server) now clamps system/append-only as well as better-auth, so the client hint reflects permission ∩ guard.

    Potentially breaking: a downstream/third-party system object 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. Declare userActions opening the verbs the object legitimately takes from a user context. better-auth keeps plugin-auth's identity write guard unchanged; the row-level managed_by provenance vocabulary (ADR-0066) is a different axis and is untouched.

  • 62a2117: Split the overloaded managedBy: 'system' bucket with an explicit engine-owned value (ADR-0103 addendum, #3343). ADR-0103 deferred the enum split ("revisitable later as a rename") because a new managedBy value would fall through to the fully-editable platform default on deployed Console clients. Both reasons against it are now retired — the server-side write guard / apiMethods reconciliation / /me/permissions clamp 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-owned with the same all-locked default affordance row as system (create/import/edit/delete: false, exportCsv: true). It joins ENGINE_OWNED_BUCKETS (the engine write guard) and GUARDED_WRITE_BUCKETS (the /me/permissions clamp); the guard, reconcileManagedApiMethods, and the clamp mechanisms are unchanged — engine-owned is 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-opening userActions (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) — system now reads as "engine-managed schema, writable via userActions".

    Behaviour-, enforcement- and wire-identical: resolved affordances, the guard verdict, the 405 apiMethods reconciliation, and the permissions clamp are the same before and after — this is a self-documenting relabel, not a policy change. No data migration (managedBy is schema metadata) and no code branches on the 'system' literal. Retiring the overloaded system entirely (moving the 8 writable objects to a dedicated bucket) is a breaking rename deferred to v17.
    ...

Read more

@objectstack/platform-objects@16.0.0

Choose a tag to compare

@github-actions github-actions released this 21 Jul 06:25
f55be04

Minor Changes

  • bc65105: feat(platform-objects): surface phone number in the create_user result dialog

    sys_user's create_user action now declares user.phoneNumber in 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 carries phoneNumber only 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 resultDialog copy — the one-shot secret-reveal dialogs are now localizable

    The 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 optional resultDialog translation node
      (ActionResultDialogTranslationSchema): title, description,
      acknowledge, and fields keyed by the literal result-field path
      (e.g. "user.email" — keys may contain dots; resolvers index the record
      directly, never split on .). New resolveActionResultDialog overlay
      resolver, wired into translateAction for API-boundary translation.
    • cli. os i18n extract emits the new resultDialog.* 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 (actionResultDialog resolver in
    @object-ui/i18n + result-dialog handlers). Purely additive — untranslated
    locales keep falling back to the metadata literals.

  • 22013aa: Split the overloaded managedBy: 'system' bucket into engine-owned vs. admin-writable, and enforce engine-owned writes (ADR-0103, #3220). The system bucket 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 as better-auth/append-only but, unlike better-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 managedBy enum value (which would fall through to fully-editable platform defaults on already-deployed Console clients), the write policy is now the resolved affordance (resolveCrudAffordances = bucket default + userActions), and engine-owned is defined as a system/append-only object 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 declare userActions: { create, edit, delete: true }. The affordance is a declaration only — the DelegatedAdminGate / RLS / permission sets remain the authz.
    • Engine-owned objects locked to readsapiMethods: ['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_secret is explicitly read-locked (an empty apiMethods array fails open).
    • sys_import_job stays engine-owned: the REST import route now writes its job rows isSystem-elevated (attribution preserved via the explicit created_by stamp) and the object is locked to ['get','list'].
    • New engine write guard (assertEngineOwnedWriteAllowed, plugin-security) fail-closed rejects user-context generic writes to engine-owned system/append-only objects, keyed off the resolved affordance; isSystem and 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 just better-auth: any advertised write verb an object's resolved affordances forbid is stripped at registration with a warning (the drift backstop, ADR-0049).
    • /me/permissions clamp (plugin-hono-server) now clamps system/append-only as well as better-auth, so the client hint reflects permission ∩ guard.

    Potentially breaking: a downstream/third-party system object 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. Declare userActions opening the verbs the object legitimately takes from a user context. better-auth keeps plugin-auth's identity write guard unchanged; the row-level managed_by provenance vocabulary (ADR-0066) is a different axis and is untouched.

  • 62a2117: Split the overloaded managedBy: 'system' bucket with an explicit engine-owned value (ADR-0103 addendum, #3343). ADR-0103 deferred the enum split ("revisitable later as a rename") because a new managedBy value would fall through to the fully-editable platform default on deployed Console clients. Both reasons against it are now retired — the server-side write guard / apiMethods reconciliation / /me/permissions clamp 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-owned with the same all-locked default affordance row as system (create/import/edit/delete: false, exportCsv: true). It joins ENGINE_OWNED_BUCKETS (the engine write guard) and GUARDED_WRITE_BUCKETS (the /me/permissions clamp); the guard, reconcileManagedApiMethods, and the clamp mechanisms are unchanged — engine-owned is 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-opening userActions (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) — system now reads as "engine-managed schema, writable via userActions".

    Behaviour-, enforcement- and wire-identical: resolved affordances, the guard verdict, the 405 apiMethods reconciliation, and the permissions clamp are the same before and after — this is a self-documenting relabel, not a policy change. No data migration (managedBy is schema metadata) and no code branches on the 'system' literal. Retiring the overloaded system entirely (moving the 8 writable objects to a dedicated bucket) is a breaking rename deferred to v17.

  • 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