Releases: objectstack-ai/objectstack
Release list
objectstack-vscode@16.0.0
objectstack-vscode@16.0.0
create-objectstack@16.0.0
Minor Changes
-
3f218e4: feat(create-objectstack): the blank scaffold ships the three generic connector executors by default
npm create objectstacknow generates anobjectstack.config.tsthat wires the
rest,openapi, andmcpconnector executor plugins (ADR-0022/0023/0024 +
ADR-0097) intoplugins:, alongsiderequires: ['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
declarativeconnectors:entry namingprovider: '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
mcpstdio transports stay denied by default —
opt in per host withnew 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 devboot (#3167)The MCP HTTP surface (
/api/v1/mcp) and the long-lived stdio transport used to
share one env var:OS_MCP_SERVER_ENABLED=trueturned 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— newresolveMcpStdioAutoStart(). Stdio auto-start is
now its own switch,OS_MCP_STDIO_ENABLED(default off);OS_MCP_SERVER_ENABLED
governs only the HTTP surface. The legacyOS_MCP_SERVER_ENABLED=truetrigger
still starts stdio for one release, flagged as deprecated.=falseis unchanged
(it only ever gated HTTP).@objectstack/mcp—MCPServerPlugin.start()gates stdio on the new switch
and logs a one-time deprecation warning when started via the legacy alias.@objectstack/cli—os devnow prints the MCP endpoint, the agent-skill
URL, and a ready-to-pasteclaude mcp addcommand 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.
- data engine with no per-request principal (unscoped). An operator setting it to
-
3b6ef8a: Scaffolded projects ship with a
.gitignoreagain —npx create-objectstackproduced none, leavingnode_modules/and.envun-ignored for every new user.npm pack/pnpm packstrip.gitignorefrom a tarball unconditionally, at every depth. The blank template committed one atsrc/templates/blank/.gitignoreand the build faithfully copied it todist/templates/blank/.gitignore, butfiles: ["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 shipsdist/templates/blank/.dockerignoreand no.gitignore.The template is now committed as
_gitignore(a name npm does not strip) and restored to.gitignorewhen the template is copied, via aTEMPLATE_FILE_ALIASESmap in the newtemplate-copy.ts. Only.gitignoreis aliased: the strip list is.gitignoreand.npmrc, not "every dotfile" —.dockerignorepacks fine and stays literal.The restored ignore rules also cover
.env/.env.*, which they never did. The template README has users writeOS_AUTH_SECRETandOS_SECRET_KEYinto a.env, anddocker-compose.ymlcalls that file "never committed" — but only the prose said so, and.dockerignorewas the only file that listed it.A packing ratchet in
template-consistency.test.tsguards 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 installno longer exits 1 on pnpm 11pnpm 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 1The scaffold now ships a
pnpm-workspace.yamlapproving the two packages it
actually depends on building —better-sqlite3(the native sqlite driver behind
@objectstack/driver-sql) andesbuild(compilesobjectstack.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.onlyBuiltDependenciesalone still errors.onlyBuiltDependencies(a list) — pnpm 10.0–10.30, which ignoreallowBuilds.
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--allimplies--skill '*'— which includes evenmetadata.internalskills — so repo-internal tooling like.claude/skills/dogfood-verificationlanded in every new project's.agents/skills/. All install commands are now scoped to the published catalog via the/skillssubpath (npx skills add objectstack-ai/framework/skills --all), the internal skill is additionally markedmetadata.internal: trueto 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
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
Minor Changes
-
83e8f7d: feat(mcp): decouple the stdio auto-start switch from the HTTP surface + surface the MCP endpoint on
os devboot (#3167)The MCP HTTP surface (
/api/v1/mcp) and the long-lived stdio transport used to
share one env var:OS_MCP_SERVER_ENABLED=trueturned 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— newresolveMcpStdioAutoStart(). Stdio auto-start is
now its own switch,OS_MCP_STDIO_ENABLED(default off);OS_MCP_SERVER_ENABLED
governs only the HTTP surface. The legacyOS_MCP_SERVER_ENABLED=truetrigger
still starts stdio for one release, flagged as deprecated.=falseis unchanged
(it only ever gated HTTP).@objectstack/mcp—MCPServerPlugin.start()gates stdio on the new switch
and logs a one-time deprecation warning when started via the legacy alias.@objectstack/cli—os devnow prints the MCP endpoint, the agent-skill
URL, and a ready-to-pasteclaude mcp addcommand 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.
- data engine with no per-request principal (unscoped). An operator setting it to
-
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, thehookTimeoutMs/
actionTimeoutMsrunner options, andOS_SANDBOX_HOOK_TIMEOUT_MS/
OS_SANDBOX_ACTION_TIMEOUT_MSkeep 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→ eitherexceeded CPU budget of Nms(script burned its CPU budget) orexceeded 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):
QuickJSScriptRunneroptionwallCeilingMsand envOS_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 duringinstallCtxand 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. - Meaning of the timeout knobs.
-
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
intermittenthook '…' exceeded timeout of 250msflake on PRs that never
touched the sandbox path.The per-invocation timeout DEFAULT is now resolvable from the environment via
resolveSandboxTimeoutMs(@objectstack/types), whichQuickJSScriptRunner
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/actionTimeoutMspassed
to the runner still wins over the env var, and a body's own declaredtimeoutMs
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=10000so the shared-runner
load flake can't recur; genuine hangs stay bounded by each test's own timeout.
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 [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 [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/trigger-schedule@16.0.0
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 arecord_changeflow gated on a date-equality
condition likeend_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 (contractsrenewal_alert, hrdocument_expiring_soon,
procurementpo_overdue, …).A flow's start node can now declare a
timeRelativedescriptor 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_relativetrigger (shipped in@objectstack/trigger-scheduleas
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-nodeconditiongate 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.timeRelativeto the
time_relativetrigger (ahead of the plainscheduletrigger, whose behavior is
unchanged), andos validategains readiness checks for the new descriptor
(unknown swept object, ambiguous draft status). New authorable spec key:
TimeRelativeTriggerSchema(@objectstack/spec/automation).
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 [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-record-change@16.0.0
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, 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
Patch Changes
-
6c270a6: BREAKING: remove the deprecated
ctx.session.tenantId/ctx.user.tenantIdalias from the hook & action authoring surface — converge onorganizationId(#3290).#3280 made
organizationIdthe blessed developer-facing name for the caller's active org across the JS authoring surface and kepttenantIdas a@deprecatedalias carrying the identical value. That alias is now removed from the hookctx.session, the action-bodyctx.session, and the action-bodyctx.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.tsbody):ctx.session.tenantId→ctx.session.organizationIdctx.user.tenantId(action body) →ctx.user.organizationId
The value is unchanged —
organizationIdis the same active-org id, matching theorganization_idcolumn andcurrent_user.organizationIdin RLS/sharing.ctx.userisundefinedfor system / unauthenticated writes, so readctx.session?.organizationIdwhen a hook or action must work regardless of a resolved user.What changed internally:
@objectstack/spec—HookContextSchema.sessiondrops thetenantIdfield (onlyorganizationIdremains). A straytenantIdon a constructed session is now stripped by the schema.@objectstack/objectql— the engine'sbuildSession()no longer emitssession.tenantId; the audit-stamp plugin sources thetenant_idcolumn fromsession.organizationId.@objectstack/runtime—buildActionSession()and the REST actionctx.userno longer emittenantId.@objectstack/trigger-record-change— readssession.organizationId(wassession.tenantId) when forwarding the writer's org to arunAs:'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, andExecutionLog.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-timecheck:org-identifierguard now also coverspackages/**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
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 [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/studio@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 [a3823b2]
- Updated dependencies [bc65105]
- 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 [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/platform-objects@16.0.0
@objectstack/spec@16.0.0
Major Changes
-
6c270a6: BREAKING: remove the deprecated
ctx.session.tenantId/ctx.user.tenantIdalias from the hook & action authoring surface — converge onorganizationId(#3290).#3280 made
organizationIdthe blessed developer-facing name for the caller's active org across the JS authoring surface and kepttenantIdas a@deprecatedalias carrying the identical value. That alias is now removed from the hookctx.session, the action-bodyctx.session, and the action-bodyctx.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.tsbody):ctx.session.tenantId→ctx.session.organizationIdctx.user.tenantId(action body) →ctx.user.organizationId
The value is unchanged —
organizationIdis the same active-org id, matching theorganization_idcolumn andcurrent_user.organizationIdin RLS/sharing.ctx.userisundefinedfor system / unauthenticated writes, so readctx.session?.organizationIdwhen a hook or action must work regardless of a resolved user.What changed internally:
@objectstack/spec—HookContextSchema.sessiondrops thetenantIdfield (onlyorganizationIdremains). A straytenantIdon a constructed session is now stripped by the schema.@objectstack/objectql— the engine'sbuildSession()no longer emitssession.tenantId; the audit-stamp plugin sources thetenant_idcolumn fromsession.organizationId.@objectstack/runtime—buildActionSession()and the REST actionctx.userno longer emittenantId.@objectstack/trigger-record-change— readssession.organizationId(wassession.tenantId) when forwarding the writer's org to arunAs:'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, andExecutionLog.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-timecheck:org-identifierguard now also coverspackages/**to keep reference bodies off the removed name.
Minor Changes
-
f972574: feat(spec):
ActionParamSchemagains optional widget config —multiple,accept,maxSizeThe 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, mirrorsFieldSchema.multiple), and the upload constraintsaccept
(MIME types / extensions) andmaxSize(bytes) forfile/imageparams.
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
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.
-
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:
-
43a3efb: fix(rest): gate the cross-object transactional batch by the same per-object API rules as single-record writes (#1604)
The
POST {basePath}/batchroute (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 anapiEnabled: falseobject, or run an
operation outside an object'sapiMethodswhitelist, 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
missingobjectis a400instead of a500; - enforces
enable.apiEnabled/enable.apiMethodsfor 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
idforupdate/delete(400); - rejects an unresolvable
{ $ref }with400 BATCH_UNRESOLVED_REFinstead of
silently writing anullFK; - rejects an explicit
atomic: false(...
- validates the body against a new
@objectstack/setup@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 [a3823b2]
- Updated dependencies [bc65105]
- 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 [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/platform-objects@16.0.0