Sitelet https://github.com/objectstack-ai/objectstack/releases
Skip to content

Releases: objectstack-ai/objectstack

create-objectstack@17.2.0

Choose a tag to compare

@github-actions github-actions released this 23 Aug 07:01
e7d2cc6

Minor Changes

  • 5a616d5: create-objectstack now 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> install and before npx 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/ and agent/skills/.

    That mattered because the same run ends with the skills CLI 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 the skills installer'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
    ⚠ skills with 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-character v6.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 blank starter 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, while GET /api/v1/data/my_app_note serves the scaffolded object the
    whole time. The template ships src/objects/ only — deliberately, as every
    scaffolder template in this repo does — but nothing the newcomer could reach
    said so, and pnpm dev advertises the Console URL on every boot.

    Documentation only: a new "The Console" section in the generated README.md
    naming the Console path, the consequence, and src/apps/*.app.ts as the
    remedy. No change to what the scaffolder writes into src/.

  • 7bf3fb7: Point every documentation link in these packages' published READMEs — and in
    the project create-objectstack scaffolds — at the canonical docs origin
    https://objectstack.ai, replacing the docs.objectstack.ai spelling.

    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 install no longer reports two unmet peer dependencies (#10326).

    Reproduced on a clean scaffold from published create-objectstack@17.1.0 — no lockfile, node_modules removed, 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.1
    

    Nothing 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 declares better-sqlite3 as an optional peer at ^12.0.0, and it governs exactly one configuration: a raw better-sqlite3 Database handed to better-auth's database option, which its Kysely dialect then drives. ObjectStack never takes that path — AuthManager.createDatabaseConfig() returns createObjectQLAdapterFactory(dataEngine), and every better-sqlite3 use under plugin-auth is knex's client: 'better-sqlite3' beneath ObjectQL. Measured anyway on the configuration the range does govern: better-auth 1.7.1 with database: new Database(':memory:'), running getMigrations().runMigrations(), signUpEmail, signInEmail and adapter findOne/update/delete, is green on better-sqlite3 13.0.3 and byte-for-byte equivalent on 12.11.1. The same probe with Database.prototype.prepare neutered fails, so that green is the driver's and not an unexercised path. Pinning our own ^13.0.3 declarations back to ^12 would downgrade a native module across the platform to satisfy a range measurement shows is simply behind.

    @better-auth/scim: the rc pin stays, and one better-call copy is the correct tree. npm view @better-auth/scim dist-tags reads latest: '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 exact 1.7.0-rc.1 pin is deliberate. The rc peers an exact better-call@1.3.7 while better-auth 1.7.1 depends on 1.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.allowedVersions entries, scoped <declaring package>><peer> so each widens exactly one declaration. They ship inside the scaffold — the bundled pnpm-workspace.yaml template and the one objectstack init renders — because a block in this repo's own workspace file does not travel with published packages. allowedVersions changes 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.md docs links on the ruled
    canonical origin, https://objectstack.ai (maintainer ruling, 2026-08-21:
    「这个仓的文档站规范 URL 是 https://objectstack.ai」; enforced by
    CANONICAL_DOCS_ORIGIN in scripts/check-published-readme-links.mjs). The
    template previously linked the accepted-but-unratified docs.objectstack.ai
    alias in three places, which disagreed with the root README.md's already-
    canonical spelling — so a single npm create objectstack@latest run 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 node shebang and is pnpm's link target
    for the create-objectstack command, but was committed 100644 instead of
    100755 — matching the sibling declared bin packages/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 omitting pnpm-lock.yaml / package-lock.json and node_modules/ (#10557).

    The summary used to be printed from a list accumulated while the template
    files were written — before <pm> install ran — so it could never name what
    the package manager wrote. init now prints it after the install attempt
    (succeeded or failed) from a walk of the finished project directory, reusing
    create-objectstack's created-summary.ts (now published as the
    create-objectstack/created-summary subpath) instead of a second copy of the
    same renderer.

  • afe1c4e: fix(cli): declare the four @better-auth/utils peer skews a freshly scaffolded project reports (#10931)

    Both scaffold paths emit a peerDependencyRules.allowedVersions block whose
    stated purpose is that a brand-new project's first pnpm install does 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
             ...
    
Read more

@objectstack/verify@17.2.0

Choose a tag to compare

@github-actions github-actions released this 23 Aug 07:01
e7d2cc6

Patch Changes

  • 46d34ab: createHostImporter: resolve the undeclared fallback from the CALLER, not from @objectstack/types

    The helper's documented contract said the undeclared case "falls back to the importing
    package's own resolution". It did not. The fallback was a bare import() 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-audit and chalk all
    resolve from packages/cli and 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 own import(), 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
    undeclared failure 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 from bootStack. 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.url base 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), and createRequire(parentURL) is CJS resolution, which
    honours NODE_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

Choose a tag to compare

@github-actions github-actions released this 23 Aug 07:01
e7d2cc6

Minor Changes

  • 46d34ab: createHostImporter: resolve the undeclared fallback from the CALLER, not from @objectstack/types

    The helper's documented contract said the undeclared case "falls back to the importing
    package's own resolution". It did not. The fallback was a bare import() 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-audit and chalk all
    resolve from packages/cli and 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 own import(), 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
    undeclared failure 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 from bootStack. 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.url base 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), and createRequire(parentURL) is CJS resolution, which
    honours NODE_PATH — the hole the declaration gate exists to close, re-opened on the
    fallback path.

Patch Changes

@objectstack/trigger-schedule@17.2.0

Choose a tag to compare

@github-actions github-actions released this 23 Aug 07:02
e7d2cc6

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 object sys_flow_dispatch: a persisted
      dispatch-claim ledger (ADR-0057 telemetry retention, 30 days), registered alongside
      sys_automation_run and exposed as AutomationEngine.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 documented withinDays semantic ("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/specsys_flow_dispatch added to PLATFORM_OBJECTS_BY_PACKAGE under
      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 on import lines inside runnable fences.

    check:published-readme-exports could 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 as N/N so a recogniser
    that stops matching shows up as a denominator that fell.

    What each dead claim now says, and why:

    • @objectstack/trigger-schedule and @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 own name field.
    • @objectstack/plugin-security told readers to install @objectstack/plugin-org-scoping and register an OrgScopingPlugin from it. No
      such package exists. The organization wall ships as the enterprise
      @objectstack/organizations runtime, whose OrganizationsPlugin registers the
      org-scoping service this plugin probes — the name objectstack serve and
      objectstack doctor both 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 separate cloud repository and not where
      the wall comes from either.
    • @objectstack/service-package described 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's MarketplaceInstallLocalPlugin.
    • @objectstack/embedder-openai had a fenced example importing
      KnowledgeTursoPlugin from @objectstack/knowledge-turso — the worst shape,
      because a reader pastes it. No knowledge adapter in this repository consumes an
      IEmbedder at all: knowledge-memory and knowledge-ragflow take no embedder
      option, and the adapters the contract is written for are not here. The example
      is now the embed() 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-sqlite and @objectstack/driver-postgres. Neither has
      ever existed; @objectstack/driver-sql covers PostgreSQL, MySQL and SQLite
      through Knex, choosing the client from its optional peers.
    • @objectstack/spec's published prompts/architecture.md instructed code
      generators to write import { 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 names service-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

Choose a tag to compare

@github-actions github-actions released this 23 Aug 07:02
e7d2cc6

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 on import lines inside runnable fences.

    check:published-readme-exports could 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 as N/N so a recogniser
    that stops matching shows up as a denominator that fell.

    What each dead claim now says, and why:

    • @objectstack/trigger-schedule and @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 own name field.
    • @objectstack/plugin-security told readers to install @objectstack/plugin-org-scoping and register an OrgScopingPlugin from it. No
      such package exists. The organization wall ships as the enterprise
      @objectstack/organizations runtime, whose OrganizationsPlugin registers the
      org-scoping service this plugin probes — the name objectstack serve and
      objectstack doctor both 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 separate cloud repository and not where
      the wall comes from either.
    • @objectstack/service-package described 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's MarketplaceInstallLocalPlugin.
    • @objectstack/embedder-openai had a fenced example importing
      KnowledgeTursoPlugin from @objectstack/knowledge-turso — the worst shape,
      because a reader pastes it. No knowledge adapter in this repository consumes an
      IEmbedder at all: knowledge-memory and knowledge-ragflow take no embedder
      option, and the adapters the contract is written for are not here. The example
      is now the embed() 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-sqlite and @objectstack/driver-postgres. Neither has
      ever existed; @objectstack/driver-sql covers PostgreSQL, MySQL and SQLite
      through Knex, choosing the client from its optional peers.
    • @objectstack/spec's published prompts/architecture.md instructed code
      generators to write import { 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 names service-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

Choose a tag to compare

@github-actions github-actions released this 23 Aug 07:02
e7d2cc6

Patch Changes

@objectstack/studio@17.2.0

Choose a tag to compare

@github-actions github-actions released this 23 Aug 07:01
e7d2cc6

Patch Changes

  • 02b3b07: Point every runtime-emitted documentation URL at the canonical host, and retarget the
    metadata-protection docsUrl at a page that actually exists.

    Two defects, one string. The host half: docs.objectstack.ai is an alias that redirects
    to https://objectstack.ai path-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.docsUrl values 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 mounts content/docs under /docs, docs/adr/ is
    not published, and no redirect source lives outside the /docs space. The slug was wrong
    too — the record is 0010-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 at https://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's DevPlugin boot loop and cli's os serve
    app-package loop both deliberately register only @objectstack/setup and
    @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_APP is still imported from
    @objectstack/platform-objects/apps rather 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

Choose a tag to compare

@github-actions github-actions released this 23 Aug 07:01
e7d2cc6

Minor Changes

  • 6936d07: engine.aggregate honours a per-aggregation filter (#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.executeAggregate bridge (@objectstack/spec/contracts)
    gains the same optional filter on 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 with INVALID_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's performAggregation)
    refuses a directly-delivered per-aggregation filter with
    NOT_IMPLEMENTED/501 instead of silently aggregating the unfiltered rows.
    Aggregations without a filter are byte-identically unchanged, including
    their native pushdown path.

  • 9f05b7d: Declare organization_id?: string | null on ApprovalRequestRow and
    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 sharingModel slot (enum private | public_read | public_read_write | controlled_by_parent) to BlueprintObjectSchema and, 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. private for 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 emitting null in the strict mirror) still defers to the platform default (business object → public_read_write, master-detail child → controlled_by_parent).

  • 914c413: fix(observability): BREAKINGhttp_request_errors_total is 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 SEMCONV as
    part of a stable namespace "so hosts can wire alerts/dashboards against it",
    but the only emitter was @objectstack/runtime's instrumentRouteHandler,
    applied only by the dispatcher's own route Proxy — so the series never saw
    auth's getRawApp() mount, the REST data API via RouteManager, or any other
    inbound surface. Its two siblings in the same HTTP family moved to the
    IHttpServer.afterResponse transport seam (http_requests_total, #9650/#9835;
    http_request_duration_ms, #9834/#10004) and this one could not follow:
    HttpResponseObservation carries {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 alert rate(http_requests_total{status=~"5.."}[5m]) — emitted by the transport, so it covers every inbound surface instead of the dispatcher's routes only
    sum by (route) (http_request_errors_total) sum by (route) (http_requests_total{status=~"5.."})
    SEMCONV.httpRequestErrorsTotal / RUNTIME_METRICS.httpRequestErrorsTotal in host code Delete the read. Both members are gone; tsc reports 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 through errorResponseBase, which sets a status and does not
    re-throw — so the counter missed those — while its catch incremented
    unconditionally, so a thrown 4xx WAS counted as an error. And
    http_requests_total already carries a status label, 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 the errorReporter, not a metric. Wire an ErrorReporter adapter
    (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.__obsRecordedError side 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/defaultValue spellings in inline locale maps BY NAME, in any combination — and stop claiming the retired form "resolves to nothing" (#10492)

    Two legs, both on InlineLocaleMapSchema in packages/spec/src/ui/i18n.zod.ts:

    1. Message accuracy. The INLINE_LOCALE_KEY rejection message said the
      retired key-reference form (#5055) "resolves to nothing". Measured false:
      both resolvers — resolveI18nLabel here and objectui's pickLocalized,
      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.

    2. Enforcement hole closed. key is three letters — syntactically a valid
      BCP-47 primary subtag — so { key: 'common.save' } alone parsed as a
      "language key inline locale map" and painted common.save on screen; the
      pair form was rejected only because defaultValue fails 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 a key/defaultValue entry) 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_cube are validated at the /meta write 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 with ThemeSchema,
    defineStack({ analyticsCubes }) with CubeSchema — yet neither was bound in
    UNREGISTERED_KIND_SCHEMAS, so getMetadataTypeSchema() answered undefined
    and saveMetaItem took its documented "unregistered type → store without
    validation" branch: a body the stack door strictly refuses was stored,
    unvalidated and badged success: true, through the metadata door. For theme
    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/:name with any
    JSON → 200 { success: true }, stored unvalidated.
    TO a malformed body → 422 INVALID_METADATA with structured issues[],
    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
    (ThemeSchema at stack.zod.ts themes:, CubeSchema at analyticsCubes:),
    and that closing invariant is now pinned by identity for all five map entries.

    No new capability surface. Shape validation only: ...

Read more

@objectstack/setup@17.2.0

Choose a tag to compare

@github-actions github-actions released this 23 Aug 07:01
e7d2cc6

Patch Changes

  • 02b3b07: Point every runtime-emitted documentation URL at the canonical host, and retarget the
    metadata-protection docsUrl at a page that actually exists.

    Two defects, one string. The host half: docs.objectstack.ai is an alias that redirects
    to https://objectstack.ai path-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.docsUrl values 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 mounts content/docs under /docs, docs/adr/ is
    not published, and no redirect source lives outside the /docs space. The slug was wrong
    too — the record is 0010-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 at https://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

Choose a tag to compare

@github-actions github-actions released this 23 Aug 07:02
e7d2cc6

Patch Changes

  • da891e0: Behaviour change (tightening): updates of sys_attachment rows are now authorization-gated, where they previously ran with no record-level check at all (#10091).

    installAttachmentAccessHooks gated insert (parent-edit access, uploaded_by server-stamped) and delete (uploader-or-parent-editor), but registered no beforeUpdate hook — so under the default member permission sets (wildcard CRUD, no row scoping) any member could rewrite any attachment row: re-point parent_id at a record they cannot read, or rewrite uploaded_by and then walk through the delete gate's uploader shortcut. The sys_comment kit — 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 beforeUpdate gate 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 code RECORD_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 grandfathered ATTACHMENT_* codes).
    • Re-point rule: an update that changes parent_object/parent_id must additionally satisfy the attach rule on the NEW parent (edit access, read visibility in degraded mode) — 403 ATTACHMENT_PARENT_ACCESS otherwise, and a re-point half that names no record (null/empty) is refused rather than left to validation.
    • Unscoped shape: an unscoped multi: true update (no where at all) is refused outright via the dispatchUnscopedMultiWrite whole-operation dispatch (#9974), mirroring the delete verb's #4757 refusal. The explicit match-all where: {} 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_by is 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.

  • a38c3ff: Bug fix (retention leak): an UPDATE that re-points a sys_attachment row's file_id now detaches the PRIOR file the same way deleting that row would — tombstoning it when the re-pointed row was its last reference (#10171).

    installAttachmentLifecycleHooks registered only delete-side and insert-side handlers, so a file_id re-point left the old sys_file sitting at status='committed' with zero join rows and no deleted_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 through ttl { field: 'deleted_at' } or retention { 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 afterUpdate handler fires only when the payload actually carries file_id and 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 a beforeUpdate stash 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 in beforeUpdate reaches afterUpdate on 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. Reading previous also 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).

    SettingsServicePlugin binds its data engine from a kernel:ready hook
    registered in its start(). Three shipped plugins read a settings namespace
    from a kernel:ready hook registered in their start()plugin-email
    (mail: SMTP/provider/from-address), service-sms (sms: provider
    credentials and the daily cost ceiling) and service-storage (storage:
    backend and credentials). Hooks fire in registration order, so a reader that
    started before the settings plugin read SettingsService's in-memory fallback,
    which is empty at boot: the caller received the manifest default with
    source: 'default' and locked: false, no diagnostic anywhere, while the
    operator's saved row sat unread in sys_setting.

    Nothing constrained that order. None of the three declared any dependency on
    com.objectstack.service.settings, so their position was pure kernel.use()
    order. It was correct under os serve only because the always-on slate happens
    to list settings first — and serve prepends an app's declared requires,
    so an ordinary requires: ['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-actionable warn per 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: after bindEngine, on a kernel with no
      objectql at all (settleWithoutEngine), for a directly constructed
      SettingsService, and for a read satisfied by an OS_* env override.
    • The slate pin now derives its boundary. The foundational-prefix
      assertion covered slice(0, 6) while sms — 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 at kernel:ready must 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.

  • 9e93fc6: Fix attachment tombstoning silently no-opping on a predicate (multi: true) delete

    Deleting sys_attachment join rows by PREDICATE left the file they referenced at
    status='committed' with deleted_at NULL, even when the deleted row was the
    file's last reference. The tombstone hooks handed file ids from beforeDelete to
    afterDelete on the hook context itself, on the premise that the engine passes
    the same HookContext to 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 via ttl { 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 at status='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 over sys_file (attachments-scope, status='committed', zero
    sys_attachment references) 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...

Read more