Releases: objectstack-ai/objectstack
Release list
create-objectstack@17.2.0
Minor Changes
-
5a616d5:
create-objectstacknow closes with a "Created files" summary derived from a
walk of the finished project directory, so it names everything the run wrote —
including the files written after the template copy (#10323).The old summary was the template copy's own list, printed before
<pm> installand beforenpx skills add. Measured against published
create-objectstack@17.1.0(create-objectstack demo-app, then a full walk of
the result): 12 entries printed, 18,045 paths on disk, 18,033 of them
unreachable from the summary —AGENTS.md,.github/copilot-instructions.md,
pnpm-lock.yaml,skills-lock.json,node_modules/, and two ~968 KB trees of
agent instructions at.agents/skills/andagent/skills/.That mattered because the same run ends with the
skillsCLI printing "Review
skills before use; they run with full agent permissions." Advice to review
files the run never named, at paths it never showed, is advice a newcomer
cannot act on — the wrong failure direction for a security-flavoured warning.The list could not have been correct where it stood: two of the three write
phases belong to other processes, and theskillsinstaller's destination set
moves with its releases, not ours. Reading the directory afterwards makes
the summary self-correcting instead. Large directories collapse to one line
carrying their path, entry count and size, so the bulk stays reviewable without
18,000 lines of output, and the paths the skills installer created are marked
⚠ skillswith the permissions warning tied to them.Same run, after the change: 20 entries printed, 0 written paths unreachable.
Patch Changes
-
cec9d23: Fix
create-objectstack's startup banner hardcoding◆ Create ObjectStack v6.x
regardless of the package's real, released version — eleven majors stale, on
the first line of output a newcomer ever sees (#10325). The banner now calls
readCliVersion(), the same reader.version()already used, instead of a
literal string.Dropping the real version in without recomputing the box's padding would have
reintroduced the same defect one line later — the border is a fixed run of
═computed for the 4-characterv6.x, and a longer real version (v17.1.0
is 7 characters) would push the right border out of alignment (the sibling
bug fixed in #10322, one function away in the same file). The box now derives
its width from the version string's plain length and widens the frame — never
truncates — for a version long enough to need more room; ordinary versions
still render at the historical box size.No behaviour change beyond the printed banner.
-
3a3f209: Tell a newcomer that the
blankstarter ships no app, so an empty Console
reads as the intended starting point rather than a broken install (#10317).Measured on a real scaffold-and-boot (
create-objectstack my-app -t blank,
published 17.1.0 packages,objectstack dev --ui):GET /api/v1/meta/app
returns the two platform apps (Setup, Account) and nothing of the project's
own, whileGET /api/v1/data/my_app_noteserves the scaffolded object the
whole time. The template shipssrc/objects/only — deliberately, as every
scaffolder template in this repo does — but nothing the newcomer could reach
said so, andpnpm devadvertises the Console URL on every boot.Documentation only: a new "The Console" section in the generated
README.md
naming the Console path, the consequence, andsrc/apps/*.app.tsas the
remedy. No change to what the scaffolder writes intosrc/. -
7bf3fb7: Point every documentation link in these packages' published READMEs — and in
the projectcreate-objectstackscaffolds — at the canonical docs origin
https://objectstack.ai, replacing thedocs.objectstack.aispelling.Both spellings reach the same pages (the alias redirects to the apex,
path-preserving), so no link was broken. The reason it needs a release rather
than an in-repo fix alone: a README ships inside the npm tarball, so the
version already on npm keeps showing the old host to every reader of the
package page until a new one is published. -
675ab57: First-run polish: a brand-new scaffold's very first
pnpm installno longer reports two unmet peer dependencies (#10326).Reproduced on a clean scaffold from published
create-objectstack@17.1.0— no lockfile,node_modulesremoved, nothing configured by the user — and again on the second scaffold path,objectstack init. Both printed the same two:✕ unmet peer better-call Installed: 1.4.0 Wanted: 1.3.7: @better-auth/scim@1.7.0-rc.1 ✕ unmet peer better-sqlite3 Installed: 13.0.3 Wanted: ^12.0.0: better-auth@1.7.1Nothing was broken — but it is the first screen a newcomer sees, and there is nothing they did to cause it or can do about it.
better-sqlite3: the pin is right and the upstream range is stale — so it is widened, not corrected. better-auth 1.7.1 declaresbetter-sqlite3as an optional peer at^12.0.0, and it governs exactly one configuration: a raw better-sqlite3Databasehanded to better-auth'sdatabaseoption, which its Kysely dialect then drives. ObjectStack never takes that path —AuthManager.createDatabaseConfig()returnscreateObjectQLAdapterFactory(dataEngine), and everybetter-sqlite3use underplugin-authis knex'sclient: 'better-sqlite3'beneath ObjectQL. Measured anyway on the configuration the range does govern: better-auth 1.7.1 withdatabase: new Database(':memory:'), runninggetMigrations().runMigrations(),signUpEmail,signInEmailand adapterfindOne/update/delete, is green on better-sqlite3 13.0.3 and byte-for-byte equivalent on 12.11.1. The same probe withDatabase.prototype.prepareneutered fails, so that green is the driver's and not an unexercised path. Pinning our own^13.0.3declarations back to^12would downgrade a native module across the platform to satisfy a range measurement shows is simply behind.@better-auth/scim: the rc pin stays, and onebetter-callcopy is the correct tree.npm view @better-auth/scim dist-tagsreadslatest: '1.7.1', but stable 1.7.x ships the rc.2 whole-model rewrite, so adopting it is a separate migration rather than a version bump; the exact1.7.0-rc.1pin is deliberate. The rc peers an exactbetter-call@1.3.7while better-auth 1.7.1 depends on1.4.0— and a better-auth plugin has to share the host's better-call instance, so the single 1.4.0 copy every install already resolves is right, not a skew to repair. This declaration retires together with the rc pin.What changed, and what deliberately did not. Both remedies are pnpm
peerDependencyRules.allowedVersionsentries, scoped<declaring package>><peer>so each widens exactly one declaration. They ship inside the scaffold — the bundledpnpm-workspace.yamltemplate and the oneobjectstack initrenders — because a block in this repo's own workspace file does not travel with published packages.allowedVersionschanges what pnpm reports, never what it resolves: measured on both scaffold paths, the lockfile is byte-identical with and without it (0 lines of diff), and no dependency version, range or resolution moved anywhere. This repo's own resolutions are untouched. -
e85182d: Converge the blank scaffold template's
README.mddocs links on the ruled
canonical origin,https://objectstack.ai(maintainer ruling, 2026-08-21:
「这个仓的文档站规范 URL 是 https://objectstack.ai」; enforced by
CANONICAL_DOCS_ORIGINinscripts/check-published-readme-links.mjs). The
template previously linked the accepted-but-unratifieddocs.objectstack.ai
alias in three places, which disagreed with the rootREADME.md's already-
canonical spelling — so a singlenpm create objectstack@latestrun handed
the user two different hostnames for the same docs site. -
aea1e64: Fix the declared bin (
bin/create-objectstack.js) being tracked non-executable
in git. It carries a#!/usr/bin/env nodeshebang and is pnpm's link target
for thecreate-objectstackcommand, but was committed100644instead of
100755— matching the sibling declared binpackages/cli/bin/run.js, which
was already tracked executable.Patch bump: this is a packaging-mode correction with no content, API or
behavior change (the blob hash is identical) — it only fixes how the file is
tracked in git and therefore how it is packed for npm. -
818e027: Fix
objectstack init's closing "Created files" summary omittingpnpm-lock.yaml/package-lock.jsonandnode_modules/(#10557).The summary used to be printed from a list accumulated while the template
files were written — before<pm> installran — so it could never name what
the package manager wrote.initnow prints it after the install attempt
(succeeded or failed) from a walk of the finished project directory, reusing
create-objectstack'screated-summary.ts(now published as the
create-objectstack/created-summarysubpath) instead of a second copy of the
same renderer. -
afe1c4e: fix(cli): declare the four
@better-auth/utilspeer skews a freshly scaffolded project reports (#10931)Both scaffold paths emit a
peerDependencyRules.allowedVersionsblock whose
stated purpose is that a brand-new project's firstpnpm installdoes not open
with a peer-skew report. It declared two skews and left four showing:├─┬ @better-auth/core 1.7.1 │ └── ✕ unmet peer @better-auth/utils@0.4.2: found 0.5.0 ├─┬ @better-auth/scim 1.7.0-rc.1 │ └── ✕ unmet peer @better-auth/utils@0.4.2: found 0.5.0 ├─┬ @better-auth/oauth-provider 1.7.1 │ └── ✕ unmet peer @better-auth/utils@0.4.2: found 0.5.0 └─┬ @better-auth/sso 1.7.1 ...
@objectstack/verify@17.2.0
Patch Changes
-
46d34ab:
createHostImporter: resolve the undeclared fallback from the CALLER, not from@objectstack/typesThe helper's documented contract said the undeclared case "falls back to the importing
package's own resolution". It did not. The fallback was a bareimport()written inside
@objectstack/types, and Node ESM resolves a bare specifier against the module that
CONTAINS the call — so it resolved from@objectstack/types, which under a pnpm-isolated
layout can see only its own single dependency,@objectstack/spec. Measured from an app
declaring nothing:@objectstack/plugin-auth,@objectstack/plugin-auditandchalkall
resolve frompackages/cliand all failed through the helper. Under a hoisted npm/yarn
layout the same fallback usually does find the caller's dependencies, so the claim was
green in some installs and absent in others.createHostImporter(hostRoot, options)now takes the caller's resolution base as
options.fallbackImport— the caller's ownimport(), written in the calling module:createHostImporter(hostRoot, { fallbackImport: (s) => import(s) })
minor, not patch, and not major. New exported API (
HostImporterOptions,
FallbackImport, a second parameter) makes it additive rather than a fix-only patch. It
is not a breaking change because the parameter is optional and omitting it keeps the
previous resolution base exactly — an existing caller compiles and behaves as before. The
undeclaredfailure text now names that retained default when a caller has not passed a
base, so the gap reports itself instead of being rediscovered by measurement.@objectstack/verify(patch) passes its own base frombootStack. Measured: this changes
nothing for@objectstack/organizations, the only specifier it routes through the helper —
that package is cloud-private and resolves from nowhere in the framework workspace. It is
what stops the next app-supplied package added to that path from silently missing
packages/verify's own dependencies.A string
parentURL/import.meta.urlbase was measured on Node v22.22.2 and rejected in
both spellings:import.meta.resolve's parent argument is silently ignored without
--experimental-import-meta-resolve(a change that would have compiled, run, and pinned
green while ignoring the base), andcreateRequire(parentURL)is CJS resolution, which
honoursNODE_PATH— the hole the declaration gate exists to close, re-opened on the
fallback path. -
Updated dependencies [8f04d9a]
-
Updated dependencies [4d7c564]
-
Updated dependencies [6936d07]
-
Updated dependencies [59eb04d]
-
Updated dependencies [9f05b7d]
-
Updated dependencies [7d483e1]
-
Updated dependencies [530c1df]
-
Updated dependencies [163a162]
-
Updated dependencies [26dea14]
-
Updated dependencies [128684d]
-
Updated dependencies [3b2af5e]
-
Updated dependencies [5337ef1]
-
Updated dependencies [7d2d112]
-
Updated dependencies [03bdd14]
-
Updated dependencies [5fa0d72]
-
Updated dependencies [8cc8401]
-
Updated dependencies [7bf3fb7]
-
Updated dependencies [02b3b07]
-
Updated dependencies [2570ab0]
-
Updated dependencies [5886ee6]
-
Updated dependencies [bbe643c]
-
Updated dependencies [e634ecf]
-
Updated dependencies [ec79b11]
-
Updated dependencies [222d06f]
-
Updated dependencies [95437e7]
-
Updated dependencies [b20c8d2]
-
Updated dependencies [f76fe42]
-
Updated dependencies [4257e4e]
-
Updated dependencies [3e26359]
-
Updated dependencies [6ce58a7]
-
Updated dependencies [d806081]
-
Updated dependencies [d23e3a0]
-
Updated dependencies [9a1ed7a]
-
Updated dependencies [f3a8134]
-
Updated dependencies [98ea344]
-
Updated dependencies [b03a880]
-
Updated dependencies [46d34ab]
-
Updated dependencies [914c413]
-
Updated dependencies [55809a0]
-
Updated dependencies [5b0af2b]
-
Updated dependencies [ee2ff45]
-
Updated dependencies [5b39785]
-
Updated dependencies [47cd3ec]
-
Updated dependencies [1048500]
-
Updated dependencies [52db1d1]
-
Updated dependencies [5649efb]
-
Updated dependencies [9d7d2de]
-
Updated dependencies [c815c50]
-
Updated dependencies [795ea05]
-
Updated dependencies [2306a76]
-
Updated dependencies [e5ea701]
-
Updated dependencies [26f3588]
-
Updated dependencies [9e04c3e]
-
Updated dependencies [38cf397]
-
Updated dependencies [67630c4]
-
Updated dependencies [a40dcc1]
-
Updated dependencies [def0d3e]
-
Updated dependencies [8d0bb79]
-
Updated dependencies [57e4571]
-
Updated dependencies [112a8c6]
-
Updated dependencies [13a3dca]
-
Updated dependencies [5acb58d]
-
Updated dependencies [a16ff50]
-
Updated dependencies [e222a53]
-
Updated dependencies [acb4dbc]
-
Updated dependencies [2e3cf95]
-
Updated dependencies [4c93387]
-
Updated dependencies [d728325]
-
Updated dependencies [504c8d5]
-
Updated dependencies [a037f7c]
-
Updated dependencies [c49007a]
-
Updated dependencies [047ac86]
-
Updated dependencies [3ee8ddf]
-
Updated dependencies [16cef97]
-
Updated dependencies [a79bd35]
-
Updated dependencies [490879a]
-
Updated dependencies [6ceaa4b]
-
Updated dependencies [145ba75]
-
Updated dependencies [15ea214]
-
Updated dependencies [de19489]
-
Updated dependencies [c684d00]
-
Updated dependencies [d29e271]
-
Updated dependencies [4389fe9]
-
Updated dependencies [13a6cb4]
-
Updated dependencies [9f483d9]
-
Updated dependencies [923c424]
-
Updated dependencies [b419135]
-
Updated dependencies [88e32a8]
-
Updated dependencies [0ab81d1]
-
Updated dependencies [ba6c9ed]
-
Updated dependencies [a24b7fa]
-
Updated dependencies [1ec36b7]
-
Updated dependencies [df287e6]
-
Updated dependencies [93304c2]
-
Updated dependencies [bc400af]
-
Updated dependencies [5f2e54c]
-
Updated dependencies [189373b]
-
Updated dependencies [af1636c]
-
Updated dependencies [86a8ec9]
-
Updated dependencies [d9353b9]
-
Updated dependencies [35ad101]
-
Updated dependencies [ceb33a9]
-
Updated dependencies [dccbcec]
-
Updated dependencies [6439f8b]
-
Updated dependencies [73d9795]
-
Updated dependencies [8012960]
-
Updated dependencies [266654d]
-
Updated dependencies [45204a5]
-
Updated dependencies [9b0172d]
-
Updated dependencies [f34f56b]
-
Updated dependencies [24ba050]
-
Updated dependencies [f399618]
-
Updated dependencies [75e9301]
-
Updated dependencies [2810695]
- @objectstack/platform-objects@17.2.0
- @objectstack/plugin-auth@17.2.0
- @objectstack/spec@17.2.0
- @objectstack/objectql@17.2.0
- @objectstack/runtime@17.2.0
- @objectstack/core@17.2.0
- @objectstack/plugin-security@17.2.0
- @objectstack/service-analytics@17.2.0
- @objectstack/service-automation@17.2.0
- @objectstack/rest@17.2.0
- @objectstack/service-datasource@17.2.0
- @objectstack/plugin-hono-server@17.2.0
- @objectstack/types@17.2.0
- @objectstack/plugin-sharing@17.2.0
- @objectstack/service-settings@17.2.0
@objectstack/types@17.2.0
Minor Changes
-
46d34ab:
createHostImporter: resolve the undeclared fallback from the CALLER, not from@objectstack/typesThe helper's documented contract said the undeclared case "falls back to the importing
package's own resolution". It did not. The fallback was a bareimport()written inside
@objectstack/types, and Node ESM resolves a bare specifier against the module that
CONTAINS the call — so it resolved from@objectstack/types, which under a pnpm-isolated
layout can see only its own single dependency,@objectstack/spec. Measured from an app
declaring nothing:@objectstack/plugin-auth,@objectstack/plugin-auditandchalkall
resolve frompackages/cliand all failed through the helper. Under a hoisted npm/yarn
layout the same fallback usually does find the caller's dependencies, so the claim was
green in some installs and absent in others.createHostImporter(hostRoot, options)now takes the caller's resolution base as
options.fallbackImport— the caller's ownimport(), written in the calling module:createHostImporter(hostRoot, { fallbackImport: (s) => import(s) })
minor, not patch, and not major. New exported API (
HostImporterOptions,
FallbackImport, a second parameter) makes it additive rather than a fix-only patch. It
is not a breaking change because the parameter is optional and omitting it keeps the
previous resolution base exactly — an existing caller compiles and behaves as before. The
undeclaredfailure text now names that retained default when a caller has not passed a
base, so the gap reports itself instead of being rediscovered by measurement.@objectstack/verify(patch) passes its own base frombootStack. Measured: this changes
nothing for@objectstack/organizations, the only specifier it routes through the helper —
that package is cloud-private and resolves from nowhere in the framework workspace. It is
what stops the next app-supplied package added to that path from silently missing
packages/verify's own dependencies.A string
parentURL/import.meta.urlbase was measured on Node v22.22.2 and rejected in
both spellings:import.meta.resolve's parent argument is silently ignored without
--experimental-import-meta-resolve(a change that would have compiled, run, and pinned
green while ignoring the base), andcreateRequire(parentURL)is CJS resolution, which
honoursNODE_PATH— the hole the declaration gate exists to close, re-opened on the
fallback path.
Patch Changes
- Updated dependencies [6936d07]
- Updated dependencies [59eb04d]
- Updated dependencies [9f05b7d]
- Updated dependencies [7d2d112]
- Updated dependencies [5fa0d72]
- Updated dependencies [02b3b07]
- Updated dependencies [914c413]
- Updated dependencies [55809a0]
- Updated dependencies [52db1d1]
- Updated dependencies [5649efb]
- Updated dependencies [2306a76]
- Updated dependencies [e5ea701]
- Updated dependencies [a40dcc1]
- Updated dependencies [def0d3e]
- Updated dependencies [8d0bb79]
- Updated dependencies [5acb58d]
- Updated dependencies [2e3cf95]
- Updated dependencies [4c93387]
- Updated dependencies [a037f7c]
- Updated dependencies [3ee8ddf]
- Updated dependencies [16cef97]
- Updated dependencies [a79bd35]
- Updated dependencies [6ceaa4b]
- Updated dependencies [15ea214]
- Updated dependencies [de19489]
- Updated dependencies [c684d00]
- Updated dependencies [923c424]
- Updated dependencies [1ec36b7]
- Updated dependencies [5f2e54c]
- Updated dependencies [189373b]
- Updated dependencies [35ad101]
- Updated dependencies [ceb33a9]
- Updated dependencies [73d9795]
- Updated dependencies [8012960]
- Updated dependencies [f34f56b]
- Updated dependencies [f399618]
- Updated dependencies [75e9301]
- Updated dependencies [2810695]
- @objectstack/spec@17.2.0
@objectstack/trigger-schedule@17.2.0
Minor Changes
-
73d9795: Time-relative sweeps are now idempotent per matched window (#10220). Previously the sweep
held no cross-tick memory, so every re-scan of the same window re-dispatched the same
records — a 5s-interval flow minted 15 duplicate reminders in ~70s, and even under a daily
cron a kernel rebuild re-dispatched the day's window.@objectstack/service-automation— new platform objectsys_flow_dispatch: a persisted
dispatch-claim ledger (ADR-0057 telemetry retention, 30 days), registered alongside
sys_automation_runand exposed asAutomationEngine.claim(key): Promise<boolean>on
the automation service surface (check-and-record; a concurrent duplicate insert re-reads
and reports the key as already claimed). When no ObjectQL engine / registration is
available the engine degrades to in-process dedup and logs the weakened guarantee once;
when the ledger errors, the claim falls back to the in-process check for that key so a
store outage never blocks a dispatch (availability over strict-once).@objectstack/trigger-schedule— the time-relative sweep computes a dispatch key from
the MATCHED WINDOW's identity and claims it before launching: offset mode keys on
(flowName, recordId, windowDay, offset)— so a dateField edit that moves the window
legitimately re-fires — and range mode keys on(flowName, recordId, sweepDay, rangeSpec), preserving the documentedwithinDayssemantic ("fires every day the
record stays in range") while never firing twice in one day. The trigger resolves the
claim surface structurally from the automation service; without one it dedups
in-process and warns once.@objectstack/spec—sys_flow_dispatchadded toPLATFORM_OBJECTS_BY_PACKAGEunder
service-automation(registry conformance).
Patch Changes
-
6ceaa4b: docs: name packages that exist in seven published documents, and gate the class (#10893)
A published README ships inside the npm tarball, so an install instruction in one
reaches every reader of the package. Nine@objectstack/names across seven
published documents named a package that is in no directory of this repo, and
five of those sat onimportlines inside runnable fences.check:published-readme-exportscould not see any of it, by construction. It
resolves a documented import against the package's built type surface through the
workspace member map, so a specifier that is not a member has no type entry to
compare against and the gate reads no further — strict about a member that exists,
silent about one that does not. The gate now makes the member-existence claim
first: an@objectstack/-scoped specifier that names no workspace member is a
finding, and the run header prints the scoped population asN/Nso a recogniser
that stops matching shows up as a denominator that fell.What each dead claim now says, and why:
@objectstack/trigger-scheduleand@objectstack/trigger-record-change
each misnamed themselves. Both READMEs — including their#titles and
every fenced import — said@objectstack/plugin-trigger-…, a name that has
never been published. The exported class names (ScheduleTriggerPlugin,
TimeRelativeTriggerPlugin,RecordChangeTriggerPlugin) were correct all
along; only the package name was wrong, so this is a rename pinned by each
package's ownnamefield.@objectstack/plugin-securitytold readers toinstall @objectstack/plugin-org-scopingand register anOrgScopingPluginfrom it. No
such package exists. The organization wall ships as the enterprise
@objectstack/organizationsruntime, whoseOrganizationsPluginregisters the
org-scopingservice this plugin probes — the nameobjectstack serveand
objectstack doctorboth print. Asking for the wall without it is a refusal to
boot (ADR-0093 D5), not a silent downgrade, and the page now says so. The
tenant-isolation bullet pointed at@objectstack/service-tenant, which is the
cloud control-plane runtime from the separatecloudrepository and not where
the wall comes from either.@objectstack/service-packagedescribed packages being "delivered to
runtime kernels that load them through@objectstack/service-marketplace". That
package was never built: ADR-0003, ADR-0016 and ADR-0025 all name it as future
work. The loading half that does exist here is
@objectstack/cloud-connection'sMarketplaceInstallLocalPlugin.@objectstack/embedder-openaihad a fenced example importing
KnowledgeTursoPluginfrom@objectstack/knowledge-turso— the worst shape,
because a reader pastes it. No knowledge adapter in this repository consumes an
IEmbedderat all:knowledge-memoryandknowledge-ragflowtake no embedder
option, and the adapters the contract is written for are not here. The example
is now theembed()surface that does exist, with the gap stated rather than
papered over with a substitute package name.@objectstack/driver-sqlite-wasm's "When to use" table compared it against
@objectstack/driver-sqliteand@objectstack/driver-postgres. Neither has
ever existed;@objectstack/driver-sqlcovers PostgreSQL, MySQL and SQLite
through Knex, choosing the client from its optional peers.@objectstack/spec's publishedprompts/architecture.mdinstructed code
generators to writeimport { User } from '@objectstack/protocol'. The package
is@objectstack/spec, which the same sentence names as the path being
replaced.
Four
@objectstack/names that are not in this repo are deliberately left as
they are, because prose may name a package this repo does not build and a runnable
import may not:@objectstack/security-enterprise(the enterprise edition, whose
install hint the CLI prints and a CLI test pins),@objectstack/service-tenant
(the cloud runtime),@objectstack/framework(the umbrella install name), and the
two namesservice-datasource's README recalls as its own past. -
Updated dependencies [6936d07]
-
Updated dependencies [59eb04d]
-
Updated dependencies [9f05b7d]
-
Updated dependencies [3b2af5e]
-
Updated dependencies [7d2d112]
-
Updated dependencies [5fa0d72]
-
Updated dependencies [02b3b07]
-
Updated dependencies [914c413]
-
Updated dependencies [55809a0]
-
Updated dependencies [ee2ff45]
-
Updated dependencies [47cd3ec]
-
Updated dependencies [52db1d1]
-
Updated dependencies [5649efb]
-
Updated dependencies [9d7d2de]
-
Updated dependencies [c815c50]
-
Updated dependencies [795ea05]
-
Updated dependencies [2306a76]
-
Updated dependencies [e5ea701]
-
Updated dependencies [a40dcc1]
-
Updated dependencies [def0d3e]
-
Updated dependencies [8d0bb79]
-
Updated dependencies [5acb58d]
-
Updated dependencies [2e3cf95]
-
Updated dependencies [4c93387]
-
Updated dependencies [504c8d5]
-
Updated dependencies [a037f7c]
-
Updated dependencies [3ee8ddf]
-
Updated dependencies [16cef97]
-
Updated dependencies [a79bd35]
-
Updated dependencies [6ceaa4b]
-
Updated dependencies [15ea214]
-
Updated dependencies [de19489]
-
Updated dependencies [c684d00]
-
Updated dependencies [923c424]
-
Updated dependencies [1ec36b7]
-
Updated dependencies [5f2e54c]
-
Updated dependencies [189373b]
-
Updated dependencies [35ad101]
-
Updated dependencies [ceb33a9]
-
Updated dependencies [73d9795]
-
Updated dependencies [8012960]
-
Updated dependencies [f34f56b]
-
Updated dependencies [f399618]
-
Updated dependencies [75e9301]
-
Updated dependencies [2810695]
- @objectstack/spec@17.2.0
- @objectstack/core@17.2.0
@objectstack/trigger-record-change@17.2.0
Patch Changes
-
6ceaa4b: docs: name packages that exist in seven published documents, and gate the class (#10893)
A published README ships inside the npm tarball, so an install instruction in one
reaches every reader of the package. Nine@objectstack/names across seven
published documents named a package that is in no directory of this repo, and
five of those sat onimportlines inside runnable fences.check:published-readme-exportscould not see any of it, by construction. It
resolves a documented import against the package's built type surface through the
workspace member map, so a specifier that is not a member has no type entry to
compare against and the gate reads no further — strict about a member that exists,
silent about one that does not. The gate now makes the member-existence claim
first: an@objectstack/-scoped specifier that names no workspace member is a
finding, and the run header prints the scoped population asN/Nso a recogniser
that stops matching shows up as a denominator that fell.What each dead claim now says, and why:
@objectstack/trigger-scheduleand@objectstack/trigger-record-change
each misnamed themselves. Both READMEs — including their#titles and
every fenced import — said@objectstack/plugin-trigger-…, a name that has
never been published. The exported class names (ScheduleTriggerPlugin,
TimeRelativeTriggerPlugin,RecordChangeTriggerPlugin) were correct all
along; only the package name was wrong, so this is a rename pinned by each
package's ownnamefield.@objectstack/plugin-securitytold readers toinstall @objectstack/plugin-org-scopingand register anOrgScopingPluginfrom it. No
such package exists. The organization wall ships as the enterprise
@objectstack/organizationsruntime, whoseOrganizationsPluginregisters the
org-scopingservice this plugin probes — the nameobjectstack serveand
objectstack doctorboth print. Asking for the wall without it is a refusal to
boot (ADR-0093 D5), not a silent downgrade, and the page now says so. The
tenant-isolation bullet pointed at@objectstack/service-tenant, which is the
cloud control-plane runtime from the separatecloudrepository and not where
the wall comes from either.@objectstack/service-packagedescribed packages being "delivered to
runtime kernels that load them through@objectstack/service-marketplace". That
package was never built: ADR-0003, ADR-0016 and ADR-0025 all name it as future
work. The loading half that does exist here is
@objectstack/cloud-connection'sMarketplaceInstallLocalPlugin.@objectstack/embedder-openaihad a fenced example importing
KnowledgeTursoPluginfrom@objectstack/knowledge-turso— the worst shape,
because a reader pastes it. No knowledge adapter in this repository consumes an
IEmbedderat all:knowledge-memoryandknowledge-ragflowtake no embedder
option, and the adapters the contract is written for are not here. The example
is now theembed()surface that does exist, with the gap stated rather than
papered over with a substitute package name.@objectstack/driver-sqlite-wasm's "When to use" table compared it against
@objectstack/driver-sqliteand@objectstack/driver-postgres. Neither has
ever existed;@objectstack/driver-sqlcovers PostgreSQL, MySQL and SQLite
through Knex, choosing the client from its optional peers.@objectstack/spec's publishedprompts/architecture.mdinstructed code
generators to writeimport { User } from '@objectstack/protocol'. The package
is@objectstack/spec, which the same sentence names as the path being
replaced.
Four
@objectstack/names that are not in this repo are deliberately left as
they are, because prose may name a package this repo does not build and a runnable
import may not:@objectstack/security-enterprise(the enterprise edition, whose
install hint the CLI prints and a CLI test pins),@objectstack/service-tenant
(the cloud runtime),@objectstack/framework(the umbrella install name), and the
two namesservice-datasource's README recalls as its own past. -
Updated dependencies [6936d07]
-
Updated dependencies [59eb04d]
-
Updated dependencies [9f05b7d]
-
Updated dependencies [3b2af5e]
-
Updated dependencies [7d2d112]
-
Updated dependencies [5fa0d72]
-
Updated dependencies [02b3b07]
-
Updated dependencies [914c413]
-
Updated dependencies [55809a0]
-
Updated dependencies [ee2ff45]
-
Updated dependencies [47cd3ec]
-
Updated dependencies [52db1d1]
-
Updated dependencies [5649efb]
-
Updated dependencies [9d7d2de]
-
Updated dependencies [c815c50]
-
Updated dependencies [795ea05]
-
Updated dependencies [2306a76]
-
Updated dependencies [e5ea701]
-
Updated dependencies [a40dcc1]
-
Updated dependencies [def0d3e]
-
Updated dependencies [8d0bb79]
-
Updated dependencies [5acb58d]
-
Updated dependencies [2e3cf95]
-
Updated dependencies [4c93387]
-
Updated dependencies [504c8d5]
-
Updated dependencies [a037f7c]
-
Updated dependencies [3ee8ddf]
-
Updated dependencies [16cef97]
-
Updated dependencies [a79bd35]
-
Updated dependencies [6ceaa4b]
-
Updated dependencies [15ea214]
-
Updated dependencies [de19489]
-
Updated dependencies [c684d00]
-
Updated dependencies [923c424]
-
Updated dependencies [1ec36b7]
-
Updated dependencies [5f2e54c]
-
Updated dependencies [189373b]
-
Updated dependencies [35ad101]
-
Updated dependencies [ceb33a9]
-
Updated dependencies [73d9795]
-
Updated dependencies [8012960]
-
Updated dependencies [f34f56b]
-
Updated dependencies [f399618]
-
Updated dependencies [75e9301]
-
Updated dependencies [2810695]
- @objectstack/spec@17.2.0
- @objectstack/core@17.2.0
@objectstack/trigger-api@17.2.0
Patch Changes
- Updated dependencies [6936d07]
- Updated dependencies [59eb04d]
- Updated dependencies [9f05b7d]
- Updated dependencies [3b2af5e]
- Updated dependencies [7d2d112]
- Updated dependencies [5fa0d72]
- Updated dependencies [02b3b07]
- Updated dependencies [914c413]
- Updated dependencies [55809a0]
- Updated dependencies [ee2ff45]
- Updated dependencies [47cd3ec]
- Updated dependencies [52db1d1]
- Updated dependencies [5649efb]
- Updated dependencies [9d7d2de]
- Updated dependencies [c815c50]
- Updated dependencies [795ea05]
- Updated dependencies [2306a76]
- Updated dependencies [e5ea701]
- Updated dependencies [a40dcc1]
- Updated dependencies [def0d3e]
- Updated dependencies [8d0bb79]
- Updated dependencies [5acb58d]
- Updated dependencies [2e3cf95]
- Updated dependencies [4c93387]
- Updated dependencies [504c8d5]
- Updated dependencies [a037f7c]
- Updated dependencies [3ee8ddf]
- Updated dependencies [16cef97]
- Updated dependencies [a79bd35]
- Updated dependencies [6ceaa4b]
- Updated dependencies [15ea214]
- Updated dependencies [de19489]
- Updated dependencies [c684d00]
- Updated dependencies [923c424]
- Updated dependencies [1ec36b7]
- Updated dependencies [5f2e54c]
- Updated dependencies [189373b]
- Updated dependencies [35ad101]
- Updated dependencies [ceb33a9]
- Updated dependencies [73d9795]
- Updated dependencies [8012960]
- Updated dependencies [f34f56b]
- Updated dependencies [f399618]
- Updated dependencies [75e9301]
- Updated dependencies [2810695]
- @objectstack/spec@17.2.0
- @objectstack/core@17.2.0
@objectstack/studio@17.2.0
Patch Changes
-
02b3b07: Point every runtime-emitted documentation URL at the canonical host, and retarget the
metadata-protectiondocsUrlat a page that actually exists.Two defects, one string. The host half:
docs.objectstack.aiis an alias that redirects
tohttps://objectstack.aipath-preservingly, so nothing here was a broken link — it was
the unratified spelling sitting in the places a user copies from. The CLI's spec-version
advisory, the Setup and Studio in-app overview pages (English and Chinese alike), and a
showcase demo action now all name the canonical host.The path half is the real fix. All 29
protection.docsUrlvalues on the platform's
system objects and apps pointed at/adr/0010-metadata-protection, and/adr/...is not
a route on any host: the docs site mountscontent/docsunder/docs,docs/adr/is
not published, and no redirect source lives outside the/docsspace. The slug was wrong
too — the record is0010-metadata-protection-model.md. Studio renders this URL as a
link in the lock banner, so an operator asking why an item is locked was being sent
nowhere. They now point athttps://objectstack.ai/docs/references/shared/protection,
the published reference for the very schema that carries the field. -
28ad84a: Corrected the package header's transitional NOTE, which had drifted stale in
two opposite directions.Verified directly against the boot path at head, not against the header's
own narrative:plugin-dev'sDevPluginboot loop andcli'sos serve
app-package loop both deliberately register only@objectstack/setupand
@objectstack/account— Studio's exclusion is a settled decision, not a
pending follow-up, because the console ships its own dedicated Studio
surface.plugin-auth's manifest has likewise already stopped registering
Studio (ADR-0048); that removal is done, not "landing separately" as the
stale header implied.The header's other transitional claim is still accurate and was left
unchanged:STUDIO_APPis still imported from
@objectstack/platform-objects/appsrather than defined in this package.Comment-only: no export, behaviour, or boot path changed.
-
Updated dependencies [8f04d9a]
-
Updated dependencies [6936d07]
-
Updated dependencies [59eb04d]
-
Updated dependencies [9f05b7d]
-
Updated dependencies [7d2d112]
-
Updated dependencies [5fa0d72]
-
Updated dependencies [02b3b07]
-
Updated dependencies [914c413]
-
Updated dependencies [55809a0]
-
Updated dependencies [52db1d1]
-
Updated dependencies [5649efb]
-
Updated dependencies [2306a76]
-
Updated dependencies [e5ea701]
-
Updated dependencies [a40dcc1]
-
Updated dependencies [def0d3e]
-
Updated dependencies [8d0bb79]
-
Updated dependencies [5acb58d]
-
Updated dependencies [2e3cf95]
-
Updated dependencies [4c93387]
-
Updated dependencies [a037f7c]
-
Updated dependencies [3ee8ddf]
-
Updated dependencies [16cef97]
-
Updated dependencies [a79bd35]
-
Updated dependencies [6ceaa4b]
-
Updated dependencies [15ea214]
-
Updated dependencies [de19489]
-
Updated dependencies [c684d00]
-
Updated dependencies [923c424]
-
Updated dependencies [0ab81d1]
-
Updated dependencies [1ec36b7]
-
Updated dependencies [5f2e54c]
-
Updated dependencies [189373b]
-
Updated dependencies [35ad101]
-
Updated dependencies [ceb33a9]
-
Updated dependencies [dccbcec]
-
Updated dependencies [73d9795]
-
Updated dependencies [8012960]
-
Updated dependencies [266654d]
-
Updated dependencies [f34f56b]
-
Updated dependencies [f399618]
-
Updated dependencies [75e9301]
-
Updated dependencies [2810695]
- @objectstack/platform-objects@17.2.0
- @objectstack/spec@17.2.0
@objectstack/spec@17.2.0
Minor Changes
-
6936d07:
engine.aggregatehonours a per-aggregationfilter(#10576, the contract
half of #10413).AggregationNodeSchema.filter— declared since #4286 but
marked experimental and enforced by nothing — is now live with SQL
FILTER (WHERE …)semantics: the predicate narrows the SOURCE rows that one
aggregation reads while sibling aggregations in the same call keep seeing
every row of the group, so a measure-scoped filter (stage: 'closed_won')
can finally reach the engine instead of being silently dropped (the #10413
wrong-numbers defect on the ObjectQL analytics path). The
StrategyContext.executeAggregatebridge (@objectstack/spec/contracts)
gains the same optionalfilteron its aggregation entries so analytics
strategies can lower measure filters into it (#10413 phase 2 consumes this
seam next).Execution is the correct-first two-tier shape date bucketing and HAVING use:
the engine lowers filtered aggregations in memory for every driver (unknown
operators refuse loudly withINVALID_FILTER/400 naming the aggregation
position; a group emptied by its filter answers the ruled empty-group values
— count/sum 0, avg/min/max null). No driver compiles conditional aggregation
natively today, so each native aggregate face (driver-sql — inherited by
driver-sqlite-wasm and Turso local —, the Turso remote transport,
driver-mongodb's pipeline builder, driver-memory'sperformAggregation)
refuses a directly-delivered per-aggregation filter with
NOT_IMPLEMENTED/501 instead of silently aggregating the unfiltered rows.
Aggregations without afilterare byte-identically unchanged, including
their native pushdown path. -
9f05b7d: Declare
organization_id?: string | nullonApprovalRequestRowand
ApprovalActionRow(#10331). The approval service has always stamped the
tenancy placement on the rows it inserts — and returns it on request-row
reads — but the published contract types omitted the field, so consumers had
to cast past the contract to reach it. Type-only widening: one declared
optional field per row, no runtime change. -
7d2d112: Add an optional
sharingModelslot (enumprivate | public_read | public_read_write | controlled_by_parent) toBlueprintObjectSchemaand, as a required-but-nullable key, to the OpenAI-strict structured-output mirror (SolutionBlueprintStrictSchema). The propose-stage LLM can now author a deliberate Org-Wide Default (OWD) choice — e.g.privatefor an object the user described as personal/sensitive — instead of having the platform's deterministic default silently override the intent expressed at propose time. Omitting the key (or emittingnullin the strict mirror) still defers to the platform default (business object →public_read_write, master-detail child →controlled_by_parent). -
914c413: fix(observability): BREAKING —
http_request_errors_totalis retired (ADR-0049 enforce-or-remove, #9834)⛔ If you have a Grafana panel, an alert rule or a recording rule keyed on
http_request_errors_total, it will read a FLAT ZERO after this upgrade. That
zero is the removal, not a healthy server, and it is the one way this change can
hurt you — nothing throws, nothing warns, the series simply stops receiving
samples. Rewrite the query before you deploy.Maintainer ruling 2026-08-20: RETIRE. The name was declared in
SEMCONVas
part of a stable namespace "so hosts can wire alerts/dashboards against it",
but the only emitter was@objectstack/runtime'sinstrumentRouteHandler,
applied only by the dispatcher's own route Proxy — so the series never saw
auth'sgetRawApp()mount, the REST data API viaRouteManager, or any other
inbound surface. Its two siblings in the same HTTP family moved to the
IHttpServer.afterResponsetransport seam (http_requests_total, #9650/#9835;
http_request_duration_ms, #9834/#10004) and this one could not follow:
HttpResponseObservationcarries{method, routePattern, status, elapsedMs}
and no throw signal of any kind, so every transport-side shape would have
counted a different population rather than the same one more widely.Migration (FROM → TO):
Wrote Write instead rate(http_request_errors_total[5m])in a panel or alertrate(http_requests_total{status=~"5.."}[5m])— emitted by the transport, so it covers every inbound surface instead of the dispatcher's routes onlysum by (route) (http_request_errors_total)sum by (route) (http_requests_total{status=~"5.."})SEMCONV.httpRequestErrorsTotal/RUNTIME_METRICS.httpRequestErrorsTotalin host codeDelete the read. Both members are gone; tscreports the missing property at the read site.One-line fix: replace the metric name with
http_requests_total{status=~"5.."}.The replacement is wider, not merely different. The retired counter was
divergent from a 5xx rate in both directions, measured: the dispatcher answers
its own errors througherrorResponseBase, which sets a status and does not
re-throw — so the counter missed those — while itscatchincremented
unconditionally, so a thrown 4xx WAS counted as an error. And
http_requests_totalalready carries astatuslabel, so a status-class error
counter was fully derivable from data the transport already publishes. Prove the
new query wider rather than merely non-empty: make an auth route or a REST
data-API route answer 5xx and confirm it moves, where the retired counter would
not have moved at all.If what you were actually alerting on was "a handler threw rather than
returning an error envelope" — the one signal this counter uniquely carried —
that is theerrorReporter, not a metric. Wire anErrorReporteradapter
(Sentry / Datadog / your own); it still fires on every 5xx throw and is
untouched by this change.What is NOT removed:
http_requests_total,http_request_duration_ms,
request-id propagation, the 5xx error reporter, and the
res.__obsRecordedErrorside channel that carries a swallowed error to it. The
dispatcher still instruments every route it mounts; it just no longer publishes
a fourth series whose name promised more coverage than it had. -
55809a0: fix(spec): reject the retired
key/defaultValuespellings in inline locale maps BY NAME, in any combination — and stop claiming the retired form "resolves to nothing" (#10492)Two legs, both on
InlineLocaleMapSchemainpackages/spec/src/ui/i18n.zod.ts:-
Message accuracy. The
INLINE_LOCALE_KEYrejection message said the
retired key-reference form (#5055) "resolves to nothing". Measured false:
both resolvers —resolveI18nLabelhere and objectui'spickLocalized,
parity-pinned — fall through to their last resort (first string value, in
key insertion order) and return the raw dotted key, which renders as the
visible label. The message now states the measured behaviour. -
Enforcement hole closed.
keyis three letters — syntactically a valid
BCP-47 primary subtag — so{ key: 'common.save' }alone parsed as a
"languagekeyinline locale map" and paintedcommon.saveon screen; the
pair form was rejected only becausedefaultValuefails the tag grammar.
The key pattern now refuses the two retired spellings by name, in any
combination, matching the emitted type's{ key?: never; defaultValue?: never }narrowing (#9925, maintainer ruling 2026-08-19, option B). This is
an enforcement gap of the #5055 retirement, not a new contract: nothing else
is denied — real 2–3 letter subtags (deu,fra,yue) still parse.
FROM → TO: a label authored as
{ key: '<i18n.key>' }(or any inline map
carrying akey/defaultValueentry) is now refused at parse time with the
named message; write the inline locale map form{ en: '…', 'zh-CN': '…' },
or a plain string resolved through a translation bundle. This is the same
prescription the #5055 retirement and the #9925 type narrowing already carry —
the runtime now enforces what the type already refused. -
-
2306a76: fix(spec):
theme/analytics_cubeare validated at the/metawrite door (#10194)The two doors #6245 left open, closed the same way. Both are declared,
authorable stack collections with real.strict()schemas —
defineStack({ themes })validates withThemeSchema,
defineStack({ analyticsCubes })withCubeSchema— yet neither was bound in
UNREGISTERED_KIND_SCHEMAS, sogetMetadataTypeSchema()answeredundefined
andsaveMetaItemtook its documented "unregistered type → store without
validation" branch: a body the stack door strictly refuses was stored,
unvalidated and badgedsuccess: true, through the metadata door. Fortheme
that is the console's own styling surface — a malformed one failed at render
rather than at write, with nothing at the write point to say so.FROM
PUT /meta/theme/:name/PUT /meta/analytics_cube/:namewith any
JSON →200 { success: true }, stored unvalidated.
TO a malformed body →422 INVALID_METADATAwith structuredissues[],
the same envelope every other kind already returned. A well-formed body is
accepted exactly as before.Each entry binds the same schema its stack collection is validated against
(ThemeSchemaatstack.zod.tsthemes:,CubeSchemaatanalyticsCubes:),
and that closing invariant is now pinned by identity for all five map entries.No new capability surface. Shape validation only: ...
@objectstack/setup@17.2.0
Patch Changes
-
02b3b07: Point every runtime-emitted documentation URL at the canonical host, and retarget the
metadata-protectiondocsUrlat a page that actually exists.Two defects, one string. The host half:
docs.objectstack.aiis an alias that redirects
tohttps://objectstack.aipath-preservingly, so nothing here was a broken link — it was
the unratified spelling sitting in the places a user copies from. The CLI's spec-version
advisory, the Setup and Studio in-app overview pages (English and Chinese alike), and a
showcase demo action now all name the canonical host.The path half is the real fix. All 29
protection.docsUrlvalues on the platform's
system objects and apps pointed at/adr/0010-metadata-protection, and/adr/...is not
a route on any host: the docs site mountscontent/docsunder/docs,docs/adr/is
not published, and no redirect source lives outside the/docsspace. The slug was wrong
too — the record is0010-metadata-protection-model.md. Studio renders this URL as a
link in the lock banner, so an operator asking why an item is locked was being sent
nowhere. They now point athttps://objectstack.ai/docs/references/shared/protection,
the published reference for the very schema that carries the field. -
Updated dependencies [8f04d9a]
-
Updated dependencies [6936d07]
-
Updated dependencies [59eb04d]
-
Updated dependencies [9f05b7d]
-
Updated dependencies [7d2d112]
-
Updated dependencies [5fa0d72]
-
Updated dependencies [02b3b07]
-
Updated dependencies [914c413]
-
Updated dependencies [55809a0]
-
Updated dependencies [52db1d1]
-
Updated dependencies [5649efb]
-
Updated dependencies [2306a76]
-
Updated dependencies [e5ea701]
-
Updated dependencies [a40dcc1]
-
Updated dependencies [def0d3e]
-
Updated dependencies [8d0bb79]
-
Updated dependencies [5acb58d]
-
Updated dependencies [2e3cf95]
-
Updated dependencies [4c93387]
-
Updated dependencies [a037f7c]
-
Updated dependencies [3ee8ddf]
-
Updated dependencies [16cef97]
-
Updated dependencies [a79bd35]
-
Updated dependencies [6ceaa4b]
-
Updated dependencies [15ea214]
-
Updated dependencies [de19489]
-
Updated dependencies [c684d00]
-
Updated dependencies [923c424]
-
Updated dependencies [0ab81d1]
-
Updated dependencies [1ec36b7]
-
Updated dependencies [5f2e54c]
-
Updated dependencies [189373b]
-
Updated dependencies [35ad101]
-
Updated dependencies [ceb33a9]
-
Updated dependencies [dccbcec]
-
Updated dependencies [73d9795]
-
Updated dependencies [8012960]
-
Updated dependencies [266654d]
-
Updated dependencies [f34f56b]
-
Updated dependencies [f399618]
-
Updated dependencies [75e9301]
-
Updated dependencies [2810695]
- @objectstack/platform-objects@17.2.0
- @objectstack/spec@17.2.0
@objectstack/service-storage@17.2.0
Patch Changes
-
da891e0: Behaviour change (tightening): updates of
sys_attachmentrows are now authorization-gated, where they previously ran with no record-level check at all (#10091).installAttachmentAccessHooksgated insert (parent-edit access,uploaded_byserver-stamped) and delete (uploader-or-parent-editor), but registered nobeforeUpdatehook — so under the default member permission sets (wildcard CRUD, no row scoping) any member could rewrite any attachment row: re-pointparent_idat a record they cannot read, or rewriteuploaded_byand then walk through the delete gate's uploader shortcut. Thesys_commentkit — explicitly derived from this one — has gated update with the same rule since #4630; the source kit was missing the limb its derivative copied.The new
beforeUpdategate narrows the accept set as follows; if a currently-working update starts failing, the caller lacked rights the other two verbs already required:- Row rule: the caller must be the attachment's uploader OR hold edit on its parent record (
ISharingService.canEdit; degrades to caller-scoped parent READ visibility when no sharing service is present). A multi-row update requires EVERY matched row to pass. Refusals are HTTP 403 with the standard catalog codeRECORD_NOT_ACCESSIBLE(ADR-0112: generic permission conditions take the catalog — the same envelope the comment kit's update gate emits; the insert/delete gates keep their grandfatheredATTACHMENT_*codes). - Re-point rule: an update that changes
parent_object/parent_idmust additionally satisfy the attach rule on the NEW parent (edit access, read visibility in degraded mode) — 403ATTACHMENT_PARENT_ACCESSotherwise, and a re-point half that names no record (null/empty) is refused rather than left to validation. - Unscoped shape: an unscoped
multi: trueupdate (nowhereat all) is refused outright via thedispatchUnscopedMultiWritewhole-operation dispatch (#9974), mirroring the delete verb's #4757 refusal. The explicit match-allwhere: {}is still accepted and authorized per row.
System-context operations and context-less programmatic calls on bare kernels bypass the gate exactly as the insert/delete gates do.
uploaded_byis deliberately not re-stamped on update: the caller is already verified as uploader or parent editor before the write proceeds, so the rewrite-then-uploader-delete escalation is closed by the row rule itself. - Row rule: the caller must be the attachment's uploader OR hold edit on its parent record (
-
a38c3ff: Bug fix (retention leak): an UPDATE that re-points a
sys_attachmentrow'sfile_idnow detaches the PRIOR file the same way deleting that row would — tombstoning it when the re-pointed row was its last reference (#10171).installAttachmentLifecycleHooksregistered only delete-side and insert-side handlers, so afile_idre-point left the oldsys_filesitting atstatus='committed'with zero join rows and nodeleted_at. That is not the module's "fail toward retention" bias, which buys a later look:sys_file's declared lifecycle nominates a row for the sweep only throughttl { field: 'deleted_at' }orretention { onlyWhen: { status: 'pending' } }, and a silently detached file matches neither — so the reap guard is never asked about it and the storage bytes are stranded permanently, with no later re-examination.The new
afterUpdatehandler fires only when the payload actually carriesfile_idand the value actually changes, then runs the existing orphan rule (zero remaining join rows, attachments-scope, committed) on the prior id. It is best-effort like its siblings and never blocks the user's write; with no pre-image available it tombstones nothing, keeping the file.The departed id comes from the engine-bound pre-image
ctx.previous, not from abeforeUpdatestash mirroring the delete pair. Since #5574 (ADR-0058 Addendum II D1/D2) a predicate write dispatches one fresh context per matched row in each phase, so a stash written inbeforeUpdatereachesafterUpdateon the by-id path and is lost on the predicate path — a stash-based twin would have been silently half-dead on exactly the multi-row updates that orphan the most files. Readingpreviousalso adds no driver round trip: the prior-row read is memoized per operation and already demanded on this object.No revival leg was added, deliberately. Re-pointing a row ONTO a grace-window tombstone is already handled by the reap guard's sweep-time re-verification, which resolves current references, un-tombstones the file and vetoes the reap rather than reclaiming bytes. A second revival mechanism here would be a duplicate answer to a question that already has one.
-
a24b7fa: Make the settings ordering contract declared and enforced, and make the
residual pre-bind READ audible (#10250).SettingsServicePluginbinds its data engine from akernel:readyhook
registered in itsstart(). Three shipped plugins read a settings namespace
from akernel:readyhook registered in theirstart()—plugin-email
(mail: SMTP/provider/from-address),service-sms(sms: provider
credentials and the daily cost ceiling) andservice-storage(storage:
backend and credentials). Hooks fire in registration order, so a reader that
started before the settings plugin readSettingsService's in-memory fallback,
which is empty at boot: the caller received the manifest default with
source: 'default'andlocked: false, no diagnostic anywhere, while the
operator's saved row sat unread insys_setting.Nothing constrained that order. None of the three declared any dependency on
com.objectstack.service.settings, so their position was purekernel.use()
order. It was correct underos serveonly because the always-on slate happens
to listsettingsfirst — andserveprepends an app's declaredrequires,
so an ordinaryrequires: ['email']produced email-before-settings and bypassed
that; cloud's per-tenant runtime mounts the slate from its own wiring.Three changes, one contract:
- Declared order. Each of the three plugins now declares
optionalDependencies: ['com.objectstack.service.settings']. The kernel
resolves both the init and the start phase from that graph
(resolvePluginOrder, ADR-0116), so the bind is ordered ahead of the read
wherever the plugin is composed, in any host. Soft, not hard: a kernel with
no settings service still boots these plugins unchanged. - The residual is audible. A settings read issued while a bind is
declared but pending now emits one operator-actionablewarnper namespace
naming the repair. Deliberately not a refusal — an in-window read of a
setting with genuinely no persisted row must answer the manifest default, and
refusing would turn a correct startup sequence into an error. It stays silent
in every case that is not the window: afterbindEngine, on a kernel with no
objectqlat all (settleWithoutEngine), for a directly constructed
SettingsService, and for a read satisfied by anOS_*env override. - The slate pin now derives its boundary. The foundational-prefix
assertion coveredslice(0, 6)whilesms— a settings reader — sits at
index 6, one past the end. The new pin
(packages/cli/src/commands/serve-settings-ordering.pin.test.ts) states the
rule instead of the count: every always-on entry that is not one of the
services others bind into atkernel:readymust be mounted after all of
them. An entry added tomorrow is covered wherever it lands.
No behaviour changes for a deployment whose order was already correct.
- Declared order. Each of the three plugins now declares
-
9e93fc6: Fix attachment tombstoning silently no-opping on a predicate (
multi: true) deleteDeleting
sys_attachmentjoin rows by PREDICATE left the file they referenced at
status='committed'withdeleted_atNULL, even when the deleted row was the
file's last reference. The tombstone hooks handed file ids frombeforeDeleteto
afterDeleteon the hook context itself, on the premise that the engine passes
the sameHookContextto both events. Since ADR-0058 Addendum II (D1/D2) a
predicate write dispatches one FRESH context per matched row in each phase, so
that hand-off never arrived and no tombstone was written.The bytes were stranded permanently rather than late:
sys_file's declared
lifecycle nominates a sweep candidate only viattl { field: 'deleted_at' }or
retention { onlyWhen: { status: 'pending' } }, and an untombstoned orphan
matches neither — so the reap guard was never asked about it. The by-id delete
verb, and both dispatch paths of the update verb, were unaffected.The departed id now comes from
ctx.previous.file_id, which the engine binds on
both phases and both dispatch paths — the same slot the update verb's detach leg
already reads.What an upgrader needs to know. New predicate deletes tombstone correctly
from this version on. Files ALREADY stranded by the old behaviour are not
retro-actively tombstoned by this change: they sit atstatus='committed'with
live storage bytes and no join row, and nothing in the platform sweep will
nominate them. Recovering that existing backlog needs a one-off reconciliation
pass oversys_file(attachments-scope,status='committed', zero
sys_attachmentreferences) and is deliberately not part of this fix. -
Updated dependencies [8f04d9a]
-
Updated dependencies [6936d07]
-
Updated dependencies [59eb04d]
-
Updated dependencies [9f05b7d]
-
Updated dependencies [3b2af5e]
-
Updated dependencies [7d2d112]
-
Updated dependencies [5fa0d72]
-
Updated dependencies [02b3b07]
-
Updated dependencies [46d34ab]
-
Updated dependencies [914c413]
-
Updated dependencies [55809a0]
-
Updated dependencies [ee2ff45]
-
Updated dependencies [47cd3ec]
-
Updated dependencies [52db1d1]
-
Updated dependencies [5649efb]
-
Update...