Skip to content

Releases: objectstack-ai/objectstack

objectstack-vscode@16.0.0

Choose a tag to compare

@github-actions github-actions released this 21 Jul 06:28
f55be04
objectstack-vscode@16.0.0

create-objectstack@16.0.0

Choose a tag to compare

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

Minor Changes

  • 3f218e4: feat(create-objectstack): the blank scaffold ships the three generic connector executors by default

    npm create objectstack now generates an objectstack.config.ts that wires the
    rest, openapi, and mcp connector executor plugins (ADR-0022/0023/0024 +
    ADR-0097) into plugins:, alongside requires: ['automation']. This closes the
    last authoring gap in the ADR-0097 promise that integrations are expressible
    and executable as pure metadata: an author (human or AI) can now add a
    declarative connectors: entry naming provider: 'rest' | 'openapi' | 'mcp'
    and have it materialize into a live, dispatchable connector at boot — with no
    host-code edit.

    • plugins:new ConnectorRestPlugin(), new ConnectorOpenApiPlugin(),
      new ConnectorMcpPlugin() (zero-arg = contribute the provider factory only).
    • requires: ['automation'] — the automation service performs the
      materialization and owns the registry the executors register into. It is also
      a hard dependency of the connector plugins, so a scaffold that lists them in
      plugins: without it fails boot; automation ships transitively via
      @objectstack/cli.
    • deps — @objectstack/connector-rest, @objectstack/connector-openapi,
      @objectstack/connector-mcp.
    • Security (#3055): declarative mcp stdio transports stay denied by default —
      opt in per host with new ConnectorMcpPlugin({ declarativeStdio: ['node'] }).

    Brand connectors (Slack, …) remain marketplace/opt-in.

Patch Changes

  • 83e8f7d: feat(mcp): decouple the stdio auto-start switch from the HTTP surface + surface the MCP endpoint on os dev boot (#3167)

    The MCP HTTP surface (/api/v1/mcp) and the long-lived stdio transport used to
    share one env var: OS_MCP_SERVER_ENABLED=true turned the HTTP surface on and
    silently auto-started the stdio transport — which bridges the raw metadata service

    • data engine with no per-request principal (unscoped). An operator setting it to
      "make sure MCP is on" got an unscoped transport as a side effect.
    • @objectstack/types — new resolveMcpStdioAutoStart(). Stdio auto-start is
      now its own switch, OS_MCP_STDIO_ENABLED (default off); OS_MCP_SERVER_ENABLED
      governs only the HTTP surface. The legacy OS_MCP_SERVER_ENABLED=true trigger
      still starts stdio for one release, flagged as deprecated. =false is unchanged
      (it only ever gated HTTP).
    • @objectstack/mcpMCPServerPlugin.start() gates stdio on the new switch
      and logs a one-time deprecation warning when started via the legacy alias.
    • @objectstack/clios dev now prints the MCP endpoint, the agent-skill
      URL, and a ready-to-paste claude mcp add command on boot (gated on the HTTP
      surface being on), so the "an agent operates the app it's building" loop is
      discoverable at dev time.
    • create-objectstack — the blank scaffold README documents that the app is
      itself an MCP server (the serve side), distinct from the consume-side connector.
  • 3b6ef8a: Scaffolded projects ship with a .gitignore again — npx create-objectstack produced none, leaving node_modules/ and .env un-ignored for every new user.

    npm pack / pnpm pack strip .gitignore from a tarball unconditionally, at every depth. The blank template committed one at src/templates/blank/.gitignore and the build faithfully copied it to dist/templates/blank/.gitignore, but files: ["dist"] publishing dropped it on the way to the registry — so the file was present in the repo, present in every local build, and absent from all 11 files of a real scaffold. Verified against the published 15.1.1 tarball, which ships dist/templates/blank/.dockerignore and no .gitignore.

    The template is now committed as _gitignore (a name npm does not strip) and restored to .gitignore when the template is copied, via a TEMPLATE_FILE_ALIASES map in the new template-copy.ts. Only .gitignore is aliased: the strip list is .gitignore and .npmrc, not "every dotfile" — .dockerignore packs fine and stays literal.

    The restored ignore rules also cover .env / .env.*, which they never did. The template README has users write OS_AUTH_SECRET and OS_SECRET_KEY into a .env, and docker-compose.yml calls that file "never committed" — but only the prose said so, and .dockerignore was the only file that listed it.

    A packing ratchet in template-consistency.test.ts guards both halves: it packs the real package, scaffolds from the extracted tarball with the real copy logic, and asserts every template file lands under its intended name. Source-level assertions cannot see this class of bug — the file only vanishes at publish.

  • 3a8ce9d: fix(create-objectstack): the blank scaffold declares pnpm build approvals, so a fresh pnpm install no longer exits 1 on pnpm 11

    pnpm 11 turned an unapproved dependency build script from a warning into a hard
    error. The blank template declared no build approvals, so the very first command
    a new user runs failed on any current pnpm:

    npx create-objectstack myapp && cd myapp && pnpm install
    # [ERR_PNPM_IGNORED_BUILDS] Ignored build scripts: better-sqlite3@12.11.1, esbuild@0.28.1
    # exit 1
    

    The scaffold now ships a pnpm-workspace.yaml approving the two packages it
    actually depends on building — better-sqlite3 (the native sqlite driver behind
    @objectstack/driver-sql) and esbuild (compiles objectstack.config.ts).

    Both approval keys are present because pnpm reads them by version, and neither
    alone covers the supported range:

    • allowBuilds (a package → boolean map) — the only key pnpm 11 honors, and
      understood back to pnpm 10.31. onlyBuiltDependencies alone still errors.
    • onlyBuiltDependencies (a list) — pnpm 10.0–10.30, which ignore allowBuilds.

    npm and yarn ignore the file, so the npm install path is unaffected. Both
    packages ship prebuilt binaries, so this was an install-time hard stop rather
    than a runtime defect — the project ran fine once installed.

    This is the #3091 failure class (in-repo settings masking what users resolve)
    and was caught by the publish smoke gate added in #3100, which installs the
    release candidate the way a user does — on whatever pnpm corepack hands a fresh
    machine.

  • 809214f: Stop leaking repo-internal skills into scaffolded projects. The scaffolder (and the docs) advertised npx skills add objectstack-ai/framework --all, and the skills CLI's --all implies --skill '*' — which includes even metadata.internal skills — so repo-internal tooling like .claude/skills/dogfood-verification landed in every new project's .agents/skills/. All install commands are now scoped to the published catalog via the /skills subpath (npx skills add objectstack-ai/framework/skills --all), the internal skill is additionally marked metadata.internal: true to hide it from interactive discovery, and a template-consistency ratchet plus a scaffold-e2e assertion keep the boundary from regressing.

@objectstack/verify@16.0.0

Choose a tag to compare

@github-actions github-actions released this 21 Jul 06:28
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 [a9459e6]
  • Updated dependencies [3ad3dd5]
  • Updated dependencies [8efa395]
  • Updated dependencies [3a18b60]
  • Updated dependencies [02eafa5]
  • 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 [780b4b5]
  • Updated dependencies [2ea08ee]
  • Updated dependencies [d1d1c40]
  • Updated dependencies [616e839]
  • Updated dependencies [b320158]
  • Updated dependencies [ee0a499]
  • Updated dependencies [5f05de2]
  • Updated dependencies [021ba4c]
  • Updated dependencies [158aa14]
  • Updated dependencies [9d897b3]
  • Updated dependencies [62a2117]
  • Updated dependencies [f8c1b69]
  • Updated dependencies [d2723e2]
  • Updated dependencies [674457a]
  • Updated dependencies [fefcd54]
  • Updated dependencies [efbcfe1]
  • Updated dependencies [2049b6a]
  • Updated dependencies [beaf2de]
  • Updated dependencies [1e145eb]
  • 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/service-automation@16.0.0
    • @objectstack/plugin-sharing@16.0.0
    • @objectstack/rest@16.0.0
    • @objectstack/service-analytics@16.0.0
    • @objectstack/plugin-auth@16.0.0
    • @objectstack/core@16.0.0
    • @objectstack/driver-sqlite-wasm@16.0.0
    • @objectstack/service-datasource@16.0.0
    • @objectstack/service-settings@16.0.0

@objectstack/types@16.0.0

Choose a tag to compare

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

Minor Changes

  • 83e8f7d: feat(mcp): decouple the stdio auto-start switch from the HTTP surface + surface the MCP endpoint on os dev boot (#3167)

    The MCP HTTP surface (/api/v1/mcp) and the long-lived stdio transport used to
    share one env var: OS_MCP_SERVER_ENABLED=true turned the HTTP surface on and
    silently auto-started the stdio transport — which bridges the raw metadata service

    • data engine with no per-request principal (unscoped). An operator setting it to
      "make sure MCP is on" got an unscoped transport as a side effect.
    • @objectstack/types — new resolveMcpStdioAutoStart(). Stdio auto-start is
      now its own switch, OS_MCP_STDIO_ENABLED (default off); OS_MCP_SERVER_ENABLED
      governs only the HTTP surface. The legacy OS_MCP_SERVER_ENABLED=true trigger
      still starts stdio for one release, flagged as deprecated. =false is unchanged
      (it only ever gated HTTP).
    • @objectstack/mcpMCPServerPlugin.start() gates stdio on the new switch
      and logs a one-time deprecation warning when started via the legacy alias.
    • @objectstack/clios dev now prints the MCP endpoint, the agent-skill
      URL, and a ready-to-paste claude mcp add command on boot (gated on the HTTP
      surface being on), so the "an agent operates the app it's building" loop is
      discoverable at dev time.
    • create-objectstack — the blank scaffold README documents that the app is
      itself an MCP server (the serve side), distinct from the consume-side connector.
  • 92f5f19: feat(runtime): sandbox budget is script CPU-time, not wall clock (ADR-0102 D1, #3295)

    The QuickJS sandbox now meters each hook/action invocation against how much
    VM-active (CPU) time the body burns, not wall clock. Idle host-await time and
    a nested hook's own execution (which runs host-side while the caller's VM is
    parked) are no longer charged to the caller — so a slow/loaded host or a deep
    nested-write chain can't trip the budget while a script is merely waiting (the
    root cause of the #3259 CI flake). A separate, generous wall-clock ceiling
    (default 30s, max(ceiling, cpuBudget)) remains as the backstop for a body stuck
    on a host call that never settles.

    What changes for consumers (behaviour, not API signatures):

    • Meaning of the timeout knobs. body.timeoutMs, the hookTimeoutMs /
      actionTimeoutMs runner options, and OS_SANDBOX_HOOK_TIMEOUT_MS /
      OS_SANDBOX_ACTION_TIMEOUT_MS keep their names, defaults (250ms / 5000ms),
      and precedence
      — but now bound CPU-time instead of wall-clock. In practice
      this only loosens legitimate slow/nested work; a runaway synchronous script
      is still cut at the same budget.
    • Error messages. exceeded timeout of Nms → either exceeded CPU budget of Nms (script burned its CPU budget) or exceeded wall-clock ceiling of Nms while awaiting host calls (stuck on a never-settling host call). Update any
      code/tests matching the old string.

    New knobs (additive):

    • QuickJSScriptRunner option wallCeilingMs and env OS_SANDBOX_WALL_CEILING_MS
      — tune the wall ceiling (explicit option › env › 30s).
    • resolveSandboxTimeoutMs (@objectstack/types) gains a 'wallCeiling' kind.

    Also fixes a latent init bug in the new accounting where the interrupt handler
    could fire during installCtx and corrupt ctx marshalling. The nested-write
    integration suites now run at the stock 250ms budget (previously forced to 10s),
    which is itself the regression guard for the nested-charging fix.

  • 32899e6: feat(runtime): env-overridable sandbox hook/action timeout default (#3259)

    The QuickJS sandbox enforces a wall-clock deadline on every hook/action
    invocation (250ms hooks / 5000ms actions). Each invocation compiles a fresh
    WASM module, and a nested hook compiles ANOTHER one inside the parent's budget,
    so on a heavily loaded or slow host — an oversubscribed CI runner, constrained
    production hardware — that fixed VM-creation cost alone can trip the hook
    default even while the VM is still making progress. On CI this surfaced as an
    intermittent hook '…' exceeded timeout of 250ms flake on PRs that never
    touched the sandbox path.

    The per-invocation timeout DEFAULT is now resolvable from the environment via
    resolveSandboxTimeoutMs (@objectstack/types), which QuickJSScriptRunner
    consults, so an operator can raise the floor once, deployment-wide, instead of
    re-tuning every call site:

    • OS_SANDBOX_HOOK_TIMEOUT_MS — default hook budget (ms)
    • OS_SANDBOX_ACTION_TIMEOUT_MS — default action budget (ms)

    Precedence is unchanged: an explicit hookTimeoutMs / actionTimeoutMs passed
    to the runner still wins over the env var, and a body's own declared timeoutMs
    still wins over the resolved default (the smaller of the explicit values). Only
    a positive integer is honored; unset / empty / non-numeric / non-positive keeps
    the built-in 250ms / 5000ms defaults, so behaviour is byte-for-byte unchanged
    when the vars are absent — production is unaffected unless it opts in.

    CI's Test Core now sets OS_SANDBOX_HOOK_TIMEOUT_MS=10000 so the shared-runner
    load flake can't recur; genuine hangs stay bounded by each test's own timeout.

Patch Changes

@objectstack/trigger-schedule@16.0.0

Choose a tag to compare

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

Minor Changes

  • a2795f6: feat(triggers): declarative time-relative trigger — daily sweep instead of fragile date-equality (#1874)

    Time-relative business rules ("alert 60 days before a contract's end_date")
    could only be expressed as a record_change flow gated on a date-equality
    condition like end_date == daysFromNow(60). That predicate is only evaluated
    when the record happens to change, so it fires only if a record is edited on
    exactly the threshold day — i.e. almost never, unattended. The robust
    alternative was a hand-written cron + range query that every author
    re-implemented (contracts renewal_alert, hr document_expiring_soon,
    procurement po_overdue, …).

    A flow's start node can now declare a timeRelative descriptor instead:

    config: {
      timeRelative: {
        object: 'contracts',
        dateField: 'end_date',
        offsetDays: [60, 30, 7],      // T-minus reminders — fires on each threshold day
        // — or — withinDays: 30      // "expiring soon" range; negative = overdue lookback
        filter: { status: 'active' }, // optional, ANDed with the date window
      },
      schedule: { type: 'cron', expression: '0 8 * * *' }, // optional; defaults to daily 08:00 UTC
    }

    The new time_relative trigger (shipped in @objectstack/trigger-schedule as
    TimeRelativeTriggerPlugin) sweeps the object on that schedule and launches the
    flow once per matching record, with the record on the automation context —
    so the start-node condition gate and {record.<field>} interpolation work
    exactly as for a record-change flow. Because the window is evaluated every day,
    a threshold is never missed regardless of when the record last changed. The
    discovery query runs as a system operation (RLS-bypassing) and is capped
    (maxRecords, default 1000) so a mis-scoped window can't fan out unboundedly;
    per-record failures are isolated so one bad row never aborts the sweep.

    The automation engine routes a start node carrying config.timeRelative to the
    time_relative trigger (ahead of the plain schedule trigger, whose behavior is
    unchanged), and os validate gains readiness checks for the new descriptor
    (unknown swept object, ambiguous draft status). New authorable spec key:
    TimeRelativeTriggerSchema (@objectstack/spec/automation).

Patch Changes

@objectstack/trigger-record-change@16.0.0

Choose a tag to compare

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

Minor 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.

Patch Changes

  • 6c270a6: BREAKING: remove the deprecated ctx.session.tenantId / ctx.user.tenantId alias from the hook & action authoring surface — converge on organizationId (#3290).

    #3280 made organizationId the blessed developer-facing name for the caller's active org across the JS authoring surface and kept tenantId as a @deprecated alias carrying the identical value. That alias is now removed from the hook ctx.session, the action-body ctx.session, and the action-body ctx.user. Read the caller's active org under the single blessed name:

    - const org = ctx.session.tenantId;   // hook or action body
    + const org = ctx.user?.organizationId ?? ctx.session?.organizationId;

    FROM → TO migration (in any *.hook.ts / *.action.ts body):

    • ctx.session.tenantIdctx.session.organizationId
    • ctx.user.tenantId (action body) → ctx.user.organizationId

    The value is unchanged — organizationId is the same active-org id, matching the organization_id column and current_user.organizationId in RLS/sharing. ctx.user is undefined for system / unauthenticated writes, so read ctx.session?.organizationId when a hook or action must work regardless of a resolved user.

    What changed internally:

    • @objectstack/specHookContextSchema.session drops the tenantId field (only organizationId remains). A stray tenantId on a constructed session is now stripped by the schema.
    • @objectstack/objectql — the engine's buildSession() no longer emits session.tenantId; the audit-stamp plugin sources the tenant_id column from session.organizationId.
    • @objectstack/runtimebuildActionSession() and the REST action ctx.user no longer emit tenantId.
    • @objectstack/trigger-record-change — reads session.organizationId (was session.tenantId) when forwarding the writer's org to a runAs:'user' flow; behavior is identical.

    Explicit non-goal (unchanged): the generic driver-layer tenancy abstraction is not touched — ExecutionContext.tenantId, DriverOptions.tenantId, SqlDriver.applyTenantScope / TenancyConfig.tenantField, and ExecutionLog.tenantId. That isolation column is configurable and legitimately carries an environment id in database-per-tenant kernels; it is a distinct axis from the developer-facing org. The build-time check:org-identifier guard now also covers packages/** to keep reference bodies off the removed name.

  • 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 [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/core@16.0.0

@objectstack/trigger-api@16.0.0

Choose a tag to compare

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

Patch Changes

@objectstack/studio@16.0.0

Choose a tag to compare

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

Patch Changes

@objectstack/spec@16.0.0

Choose a tag to compare

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

Major Changes

  • 6c270a6: BREAKING: remove the deprecated ctx.session.tenantId / ctx.user.tenantId alias from the hook & action authoring surface — converge on organizationId (#3290).

    #3280 made organizationId the blessed developer-facing name for the caller's active org across the JS authoring surface and kept tenantId as a @deprecated alias carrying the identical value. That alias is now removed from the hook ctx.session, the action-body ctx.session, and the action-body ctx.user. Read the caller's active org under the single blessed name:

    - const org = ctx.session.tenantId;   // hook or action body
    + const org = ctx.user?.organizationId ?? ctx.session?.organizationId;

    FROM → TO migration (in any *.hook.ts / *.action.ts body):

    • ctx.session.tenantIdctx.session.organizationId
    • ctx.user.tenantId (action body) → ctx.user.organizationId

    The value is unchanged — organizationId is the same active-org id, matching the organization_id column and current_user.organizationId in RLS/sharing. ctx.user is undefined for system / unauthenticated writes, so read ctx.session?.organizationId when a hook or action must work regardless of a resolved user.

    What changed internally:

    • @objectstack/specHookContextSchema.session drops the tenantId field (only organizationId remains). A stray tenantId on a constructed session is now stripped by the schema.
    • @objectstack/objectql — the engine's buildSession() no longer emits session.tenantId; the audit-stamp plugin sources the tenant_id column from session.organizationId.
    • @objectstack/runtimebuildActionSession() and the REST action ctx.user no longer emit tenantId.
    • @objectstack/trigger-record-change — reads session.organizationId (was session.tenantId) when forwarding the writer's org to a runAs:'user' flow; behavior is identical.

    Explicit non-goal (unchanged): the generic driver-layer tenancy abstraction is not touched — ExecutionContext.tenantId, DriverOptions.tenantId, SqlDriver.applyTenantScope / TenancyConfig.tenantField, and ExecutionLog.tenantId. That isolation column is configurable and legitimately carries an environment id in database-per-tenant kernels; it is a distinct axis from the developer-facing org. The build-time check:org-identifier guard now also covers packages/** to keep reference bodies off the removed name.

Minor Changes

  • f972574: feat(spec): ActionParamSchema gains optional widget config — multiple, accept, maxSize

    The console now renders action params through the same field-widget renderer
    the record form uses (objectui#2700, objectui ADR-0059), so inline params can
    declare the widget config the form widgets consume: multiple (array value
    shape, mirrors FieldSchema.multiple), and the upload constraints accept
    (MIME types / extensions) and maxSize (bytes) for file/image params.
    Field-backed params ({ field }) keep inheriting these from the referenced
    field at runtime; inline values override. Purely additive — no existing
    schema changes shape.

  • 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.

  • 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.

  • 43a3efb: fix(rest): gate the cross-object transactional batch by the same per-object API rules as single-record writes (#1604)

    The POST {basePath}/batch route (issue #1604 / ADR-0034) wraps N cross-object
    create/update/delete ops in one engine transaction, but it skipped the
    per-object API-exposure gate every single-record route applies — an
    authenticated caller could write to an apiEnabled: false object, or run an
    operation outside an object's apiMethods whitelist, straight through the batch
    surface (ADR-0049 / #1889 — the same "declared ≠ enforced" hole closed for the
    generic write path in #3220 / #3213).

    The route now:

    • validates the body against a new CrossObjectBatchRequestSchema
      (@objectstack/spec/api, Zod-First) — a malformed op, an unknown action, or a
      missing object is a 400 instead of a 500;
    • enforces enable.apiEnabled / enable.apiMethods for every op (metadata
      fetched once, each distinct (object, action) checked) BEFORE opening the
      transaction — 404 OBJECT_API_DISABLED / 405 OBJECT_API_METHOD_NOT_ALLOWED;
    • requires an id for update / delete (400);
    • rejects an unresolvable { $ref } with 400 BATCH_UNRESOLVED_REF instead of
      silently writing a null FK;
    • rejects an explicit atomic: false (...
Read more

@objectstack/setup@16.0.0

Choose a tag to compare

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

Patch Changes