diff --git a/.changeset/adr-0076-honest-capabilities-framework-slice.md b/.changeset/adr-0076-honest-capabilities-framework-slice.md deleted file mode 100644 index 422756a1a2..0000000000 --- a/.changeset/adr-0076-honest-capabilities-framework-slice.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -'@objectstack/spec': minor -'@objectstack/runtime': minor -'@objectstack/metadata-protocol': minor -'@objectstack/objectql': patch -'@objectstack/rest': patch -'@objectstack/plugin-hono-server': patch ---- - -feat(discovery): honest capabilities — standardized stub/fallback marker + realtime route honesty (ADR-0076 D12/A1.5 framework slice, #2462) - -**Spec** — new service self-description marker for honest discovery -(ADR-0076 D12): `SERVICE_SELF_INFO_KEY` (`__serviceInfo`), -`ServiceSelfInfoSchema` / `ServiceSelfInfo`, and `readServiceSelfInfo()`, -which also normalizes plugin-dev's legacy `_dev: true` flag to -`{ status: 'stub', handlerReady: false }`. A registered service that is a -stub / dev fake / degraded fallback self-identifies via this marker; a fully -real service carries no marker. - -**Runtime + metadata-protocol** — both discovery builders -(`HttpDispatcher.getDiscoveryInfo` and the protocol shim's `getDiscovery`) -now honor the marker instead of hardcoding `status: 'available', -handlerReady: true` for every registered service. Dev stubs report `stub`, -the ObjectQL analytics fallback reports `degraded` (it keeps serving — no -`/analytics` 404), and consumers can finally trust -`status === 'available'` / `handlerReady === true`. - -**Realtime honesty fix** — discovery no longer advertises a -`/realtime` route or `websockets: true`: `service-realtime` is an -in-process pub/sub bus, no dispatcher branch or plugin mounts any -`/realtime` HTTP surface, so the advertised route always 404'd. The -registered service now reports `status: 'degraded', handlerReady: false` -with no route (clients using the SDK are unaffected — it falls back to the -conventional path, which behaves exactly as before). Also corrects the -advertised realtime provider from the nonexistent `plugin-realtime` to -`service-realtime`. - -**REST (A1.5)** — the REST layer's protocol dependency is narrowed from the -`ObjectStackProtocol` god-union to the new `RestProtocol = -DataProtocol & MetadataProtocol` slice (exported from -`@objectstack/rest`), per the ADR-0076 D9 incremental narrowing guidance. -Type-level only; no runtime change. diff --git a/.changeset/adr-0085-2548-followups.md b/.changeset/adr-0085-2548-followups.md deleted file mode 100644 index 567bb8e601..0000000000 --- a/.changeset/adr-0085-2548-followups.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -'@objectstack/lint': patch -'@objectstack/plugin-audit': patch ---- - -ADR-0085 #2548 follow-ups surfaced by the real-backend browser pass: - -- **lint**: new `field-group-shadowed` warning in `validate-semantic-roles` — a - declared fieldGroup whose every visible member is hoisted into the detail - highlight strip (or is the record title) renders on forms but silently never - on detail pages (detail bodies hide the first 4 highlightFields). Warning - tier, same as the other semantic-role rules. -- **plugin-audit**: feed/audit summaries ("Created … / Deleted … / Updated …") - now name the object by its display label ("Semantic Zoo") instead of its API - name ("showcase_semantic_zoo") — these strings render verbatim in the record - Discussion feed and Setup dashboards. Falls back to the API name when the - object definition isn't resolvable. Existing stored rows are unchanged. diff --git a/.changeset/adr-0085-pr4-renderviaschema-comment-2548.md b/.changeset/adr-0085-pr4-renderviaschema-comment-2548.md deleted file mode 100644 index 8d7a6cc76a..0000000000 --- a/.changeset/adr-0085-pr4-renderviaschema-comment-2548.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -'@objectstack/spec': patch ---- - -docs(spec): retire the stale `renderViaSchema` forward-reference now that objectui#2546 landed (ADR-0085 PR4 follow-up, #2548) - -The `ObjectSchema` source comment forward-referenced `renderViaSchema` -retiring "together with the legacy monolith render path" — a promise about -work that had not yet shipped. That path, and the `detail.renderViaSchema` -kill-switch that was its only steering wheel, were removed in objectui#2546 -(ADR-0085 PR4). The comment now records the completed state with a breadcrumb -to that PR instead of a forward reference, closing the cleanup #2546 flagged. - -Comment-only change; no type, schema, or runtime behavior is affected. diff --git a/.changeset/adr-0087-completion.md b/.changeset/adr-0087-completion.md deleted file mode 100644 index 7026478a9f..0000000000 --- a/.changeset/adr-0087-completion.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -'@objectstack/spec': minor -'@objectstack/cli': minor -'@objectstack/runtime': minor -'@objectstack/service-package': minor -'create-objectstack': minor ---- - -feat(protocol): complete ADR-0087 — load-seam handshake, chain backfill 12–15, release artifacts (#2643) - -Closes the remaining ADR-0087 gaps (see the ADR's as-built Addendum): - -- **P0 load seams (D1).** The protocol handshake now runs on the boot-time - durable-package rehydration path (`@objectstack/service-package` refuses an - incompatible `sys_packages` row with the structured `OS_PROTOCOL_INCOMPATIBLE` - diagnostic and keeps booting) and on `AppPlugin` for code-defined stacks - (fail-fast before the manifest is decomposed). `objectstack lint` gains - `protocol/missing-engines-range` (warning + fix-it) and the - `create-objectstack` blank template stamps `engines: { protocol: '^' }` - (re-stamped at version time by `scripts/sync-template-versions.mjs`) — the - two ends of the grandfathering ratchet. -- **Chain backfill (D2/D3).** `MetadataConversion.retiredFromLoadPath` - implements the load-window's second half (retired entries replay only via - `migrate meta` / fixture CI). Steps 12–15 land: the `api.requireAuth` flip - (semantic), the ADR-0090 wave (3 retired conversions + 5 semantic TODOs), the - `BookAudience` rename (retired conversion), and the ADR-0089 visibility - unification (`visibleOn`/`visibility` → `visibleWhen` as LIVE load-window - conversions) + the `.strict()` flip (semantic). The protocol-11 - `compactLayout` → `highlightFields` rename is backfilled as a retired step-11 - conversion. `migrate meta --from 10` now reaches protocol 15. -- **Release artifacts (D4).** `spec-changes.json` is generated from the - registries (`gen:spec-changes`, CI drift-checked), ships in the npm artifact - together with `api-surface.json`, and is attached to each `@objectstack/spec` - GitHub Release with `added[]`/`removed[]` filled from the api-surface diff - against the previously published release. The upgrade guide - (`docs/protocol-upgrade-guide.md`) is generated from the same registries and - CI drift-checked — a projection that cannot drift. diff --git a/.changeset/adr-0096-declarative-connectors.md b/.changeset/adr-0096-declarative-connectors.md deleted file mode 100644 index 3ba3e2cd82..0000000000 --- a/.changeset/adr-0096-declarative-connectors.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -'@objectstack/spec': minor -'@objectstack/service-automation': minor -'@objectstack/connector-rest': minor -'@objectstack/connector-openapi': minor -'@objectstack/connector-mcp': minor ---- - -feat(connectors): ADR-0096 — provider-bound declarative connector instances materialized at boot (#2977) - -Declarative `connectors:` stack entries used to be **descriptor-only** (#2612): -registered as metadata but never dispatchable, the platform's one dead metadata -surface. An entry may now name a **`provider`** — an installed generic executor -(`openapi` / `mcp` / `rest`) — and the automation service **materializes** it -into a live, dispatchable connector at boot. AI can now wire an integration as -pure metadata and a flow `connector_action` calls it end-to-end. - -- **Schema (`@objectstack/spec`).** `ConnectorSchema` gains `provider`, - `providerConfig`, and `auth` (a `credentialRef`-based instance-auth shape — - `ConnectorInstanceAuthSchema` — that references credentials, never inlines - them); `authentication` now defaults to `{ type: 'none' }` so a provider-bound - instance need not author it (loosening — existing connectors are unaffected). - `DeclarativeConnectorEntrySchema` (used by `stack.zod.ts`) rejects inline - secrets, orphan `providerConfig`/`auth`, and authored `actions`/`triggers` on a - provider-bound entry. A new `integration/connector-provider.ts` defines the - provider-factory contract as pure types. - -- **Engine + boot (`@objectstack/service-automation`).** The engine adds a - connector-provider registry (`registerConnectorProvider`/`getConnectorProvider`) - and origin-tags registered connectors. At boot the service resolves each - provider-bound entry — looking up the factory, resolving `auth.credentialRef` - via a pluggable `CredentialResolver` (open-tier default: environment - variables), and registering the materialized connector. Boot **fails loudly** - for an unknown provider, invalid `providerConfig`, an unresolvable - `credentialRef`, or a name conflict with a plugin-registered connector (no - silent precedence). - -- **Providers (`connector-rest` / `connector-openapi` / `connector-mcp`).** Each - plugin registers a provider factory in `init()` reusing its existing - generator/adapter API. Plugin options are now **optional**: with none the - plugin contributes only its provider factory; with instance options it also - registers a hand-wired connector (back-compat). `connector-openapi` adds a - `ConnectorOpenApiPlugin`. - -Open tier: static auth (`none`/`api-key`/`basic`/`bearer`) with `credentialRef` -resolved from env vars. Managed vaulting, OAuth2 refresh, and per-tenant -connection lifecycle remain the enterprise tier (ADR-0015) — an enterprise host -injects a vault-backed `CredentialResolver` with no change to the materialization -path. diff --git a/.changeset/adr-0096-openapi-spec-file-path.md b/.changeset/adr-0096-openapi-spec-file-path.md deleted file mode 100644 index fcc5faa43c..0000000000 --- a/.changeset/adr-0096-openapi-spec-file-path.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -'@objectstack/spec': minor -'@objectstack/service-automation': minor -'@objectstack/connector-openapi': minor -'@objectstack/cli': minor ---- - -feat(connector-openapi): resolve `providerConfig.spec` from a package-relative file path (#3016, ADR-0096 follow-up) - -ADR-0096's canonical example authors an OpenAPI-backed instance as -`providerConfig: { spec: './billing-openapi.json' }`, but the landed `openapi` -provider factory only accepted an inline document object or an http(s) URL. -The spec union is now complete: **inline object | file path | remote URL**. - -- **`@objectstack/spec`.** `ConnectorProviderContext` gains an optional - host-injected `loadPackageFile(relativePath)` capability (pure type): reads a - UTF-8 file resolved against the declaring stack/package root, confined to - that root. `undefined` on hosts without a filesystem. - -- **`@objectstack/service-automation`.** New `packageRoot` plugin option (the - base for relative file refs; defaults to `process.cwd()`) and an exported - `createPackageFileLoader(packageRoot)` that implements the confinement - guard — absolute paths and `..`-escaping paths are rejected — with lazy - `node:fs`/`node:path` imports so non-Node hosts only fail if a file ref is - actually dereferenced. The materializer injects the capability into every - provider factory's context. Failures follow the existing reconcile policy: - **fatal at boot, entry skipped on reload**. - -- **`@objectstack/connector-openapi`.** A string `providerConfig.spec` that is - not an http(s) URL is now read via `ctx.loadPackageFile` and parsed as an - OpenAPI JSON document (clear errors for missing/unreadable files, unparseable - JSON, and hosts without package file access). - -- **`@objectstack/cli`.** `serve`/`dev` pass the project folder (the - `objectstack.config.ts` directory) as the automation service's `packageRoot`, - mirroring how the standalone sqlite default is anchored. diff --git a/.changeset/adr-0096-runtime-rematerialization.md b/.changeset/adr-0096-runtime-rematerialization.md deleted file mode 100644 index 79e8a25601..0000000000 --- a/.changeset/adr-0096-runtime-rematerialization.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -'@objectstack/service-automation': minor ---- - -feat(connectors): ADR-0096 runtime re-materialization of declarative connectors (#2977 follow-up) - -Provider-bound declarative `connectors:` instances (ADR-0096) previously -materialized only at boot — a connector published from Studio while the server -ran did not become dispatchable until a restart. `materializeDeclaredConnectors` -is now a **reconcile** run both at boot and on `metadata:reloaded`: - -- **Add** newly-declared instances, **tear down** removed / newly-`enabled:false` - ones (calling their `close`, e.g. an MCP connection), and **re-materialize** - only instances whose signature — a stable hash of `provider` + `providerConfig` - + `auth` + identity — changed. An unchanged MCP instance is never needlessly - reconnected on an unrelated metadata reload. -- **Boot stays fatal** ("fail loudly"): unknown provider / invalid providerConfig - / unresolvable credentialRef / name conflict aborts startup. **Reload is soft**: - the same problems are logged and the offending entry skipped, so a bad publish - never crashes a running server; a changed instance's old connector keeps - serving until its replacement materializes successfully. - -Also: `ConnectorDescriptor` (served by `GET /api/v1/automation/connectors`) now -carries an `origin` field (`'plugin' | 'declarative'`), so a designer can -distinguish a materialized declarative instance from a plugin-registered -connector. diff --git a/.changeset/adr-0097-mcp-nonfatal-boot.md b/.changeset/adr-0097-mcp-nonfatal-boot.md deleted file mode 100644 index 9ff9f71ea6..0000000000 --- a/.changeset/adr-0097-mcp-nonfatal-boot.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -'@objectstack/spec': minor -'@objectstack/service-automation': minor -'@objectstack/connector-mcp': minor ---- - -feat(connectors): degrade + retry declarative instances whose upstream is unreachable (#3017) - -ADR-0097 kept every declarative-connector materialization failure fatal at -boot. That is right for configuration faults (unknown provider, invalid -`providerConfig`, unresolvable `credentialRef`, name conflict) but wrong for -*operational* ones: a `provider: 'mcp'` instance must contact its MCP server -(`tools/list`) to materialize, and a transient network blip aborted the whole -app boot. - -- **spec**: a provider factory can now throw - `ConnectorUpstreamUnavailableError` (code `CONNECTOR_UPSTREAM_UNAVAILABLE`, - structural guard `isConnectorUpstreamUnavailable`) to mark a failure as - "upstream temporarily unreachable — degrade and retry" instead of fatal. -- **service-automation**: the reconcile degrades such an instance in both boot - and reload modes: it registers an action-less husk (`state: 'degraded'` + - `degradedReason` on the `GET /connectors` descriptor) so the instance is - visible instead of silently missing — or, on a changed-config - re-materialization, keeps the old connector serving. A `connector_action` - against a degraded instance fails with the reason and a "retries - automatically" pointer. Degraded instances retry on an exponential backoff - (5s → 5min, reset by config edits) and on every `metadata:reloaded` - reconcile; recovery swaps the husk for the live connector atomically. - Reconcile runs (boot / reload / retry timer) are now serialized. -- **connector-mcp**: the `mcp` provider classifies connect / `tools/list` - failures as upstream-unavailable; transport-shape validation stays a plain - (fatal) throw. - -Configuration faults remain loud boot failures — the carve-out is only for the -unavailable marker. diff --git a/.changeset/adr-0097-stdio-policy-gate.md b/.changeset/adr-0097-stdio-policy-gate.md deleted file mode 100644 index 25e54ec108..0000000000 --- a/.changeset/adr-0097-stdio-policy-gate.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -'@objectstack/connector-mcp': minor ---- - -feat(connector-mcp)!: policy-gate declarative stdio transports — default-deny + host allowlist (#3055) - -A declarative `provider: 'mcp'` entry with a stdio transport spawns a local -child process **from metadata** — which a runtime Studio publish can introduce. -Declarative stdio is now **denied by default**; hosts opt in deliberately: - -```ts -new ConnectorMcpPlugin({ declarativeStdio: ['my-mcp-server'] }) // command allowlist -new ConnectorMcpPlugin({ declarativeStdio: true }) // allow any (full trust) -``` - -Behavior change: a declarative stdio instance that materialized before now -fails as a **configuration fault** (fatal at boot / skipped on reload) with an -actionable opt-in message, and is never classified upstream-unavailable — a -security rejection must not be retried into existence. `http` transports and -**hand-wired** connectors (plugin instance options / `createMcpConnector`) are -unaffected. This is the security precondition for shipping `connector-mcp` in -default presets (#3056); see ADR-0097 §"Declarative stdio policy" and -ADR-0024 §4. diff --git a/.changeset/attachment-authenticated-downloads.md b/.changeset/attachment-authenticated-downloads.md deleted file mode 100644 index f184e45e0a..0000000000 --- a/.changeset/attachment-authenticated-downloads.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -'@objectstack/service-storage': minor ---- - -feat(attachments): authenticated, parent-scoped downloads for attachments files (#2970) - -Closes item 2 of #2970. The storage download endpoints (`GET /storage/files/:fileId` -and `/files/:fileId/url`) were anonymous capability URLs — anyone holding a -`fileId` could mint a download without a session or any access check. - -For `scope === 'attachments'`, non-`public_read` files, both endpoints now gate -on a new `authorizeFileRead` seam: `401 AUTH_REQUIRED` without a session, `403 -ATTACHMENT_DOWNLOAD_DENIED` when the caller is neither the file's owner nor able -to READ a record the file is attached to (parent-derived, resolved through the -full caller context via `resolveAuthzContext`), and otherwise a **short-lived** -signed URL (`downloadTtl`, default 300s). Non-attachments files (field files, -avatars, org logos — embedded in `` which cannot carry a bearer token) -keep the stable anonymous capability URL, and bare kernels/tests without the -seam wired stay open (back-compat). diff --git a/.changeset/attachment-followups-rest.md b/.changeset/attachment-followups-rest.md deleted file mode 100644 index 868fcd71bc..0000000000 --- a/.changeset/attachment-followups-rest.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -'@objectstack/service-storage': minor -'@objectstack/platform-objects': patch ---- - -feat(attachments): edit-on-parent attach, upload-session lifecycle, trash=false (#2970 items 3-5) - -Closes the remaining enforce-or-remove / lifecycle items of #2970: - -- **Edit-on-parent for attach (item 3, Salesforce parity).** Creating a - `sys_attachment` now requires EDIT access to the parent record (via the - sharing service's `canEdit`), not merely read — public-model parents are - unchanged (canEdit is true for any member), private/owner-scoped parents - require the caller to own/edit them. Degrades to read visibility when no - sharing service is present. -- **`sys_upload_session` lifecycle (item 4).** Abandoned / terminal chunked - upload sessions are reaped by the platform LifecycleService (`transient`; - TTL 1d past `expires_at`; retention 7d for terminal statuses). Row reap - only — a reap guard that aborts backend multipart uploads for partial S3 - sessions is a filed follow-up. -- **`sys_attachment.enable.trash` → `false` (item 5, ADR-0049).** The flag is - `dead` in the liveness ledger (no engine soft-delete reader) and attachment - deletes are hard (the reap guard reclaims a file's bytes once its last join - row is gone, so a restore would dangle) — declare the honest state rather - than claim a restore capability the runtime does not provide. diff --git a/.changeset/attachment-read-visibility.md b/.changeset/attachment-read-visibility.md deleted file mode 100644 index 514769bfa3..0000000000 --- a/.changeset/attachment-read-visibility.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -'@objectstack/service-storage': minor ---- - -feat(attachments): sys_attachment read inherits parent-record visibility (#2970) - -Follow-up to #2755. The create/delete gates landed, but a member could still -LIST `sys_attachment` rows (file_name, size, parent_id) pointing at records -they cannot read — an information leak, since attachment access derives from -the PARENT record (Salesforce ContentDocumentLink semantics). `sys_attachment` -is a public system object with no owner field, so the sharing/RLS static -predicates never narrowed it. - -`installAttachmentReadVisibility` registers a `sys_attachment`-scoped engine -**middleware** (not a find-hook) so it filters `find`, `findOne`, `count`, and -`aggregate` identically — critically, the list `total` (which comes from -`engine.count()`, never the find path) is filtered too, so it cannot leak the -count of hidden rows. Generalizing ADR-0055 `controlled_by_parent` to the -polymorphic parent, each read resolves the visible parent ids per -`parent_object` through the caller-scoped engine (the parent's own RLS/OWD/ -sharing apply) and ANDs a `$or` of `{ parent_object, parent_id: { $in } }` -into the query; no visible parent ⇒ a deny-all sentinel. Fails closed on any -compute error. System / context-less internal reads are not narrowed. diff --git a/.changeset/attachments-2755-followups.md b/.changeset/attachments-2755-followups.md deleted file mode 100644 index dab85f46db..0000000000 --- a/.changeset/attachments-2755-followups.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -'@objectstack/objectql': minor -'@objectstack/service-storage': minor -'@objectstack/platform-objects': minor -'@objectstack/verify': minor -'@objectstack/rest': patch ---- - -feat(attachments): sys_file orphan lifecycle + parent-derived attachment access (#2755) - -**Orphan lifecycle (ADR-0057).** Deleting a `sys_attachment` join row used to -orphan the backing `sys_file` row and its storage bytes forever. `sys_file` -now declares a lifecycle (`ttl 30d` on a new `deleted_at` tombstone for -orphans; `retention 7d onlyWhen status=pending` for abandoned uploads), the -storage plugin's new hooks tombstone a file when its LAST join row is deleted -(attachments scope only — `Field.file`/`Field.image`/avatar scopes are never -touched) and un-tombstone on re-attach, and a new LifecycleService **reap -guard** seam (`registerReapGuard`) re-verifies zero references at sweep time -and deletes the storage bytes before confirming each row reap. A guarded -object is never blind-deleted; an erroring guard fails safe (rows retained). - -**Attachment access (ADR-0049, Salesforce parent-derived semantics).** -`sys_attachment` create now requires caller READ visibility of the parent -record (403 `ATTACHMENT_PARENT_ACCESS`) and server-stamps `uploaded_by` from -the session (client value ignored); delete requires uploader-or-parent-editor -(403 `ATTACHMENT_DELETE_DENIED`). The storage upload routes require an -authenticated session when an auth service is wired (401 `AUTH_REQUIRED`; -bare kernels stay open) and stamp `owner_id` on new files. - -**REMOVED — `sys_attachment.share_type` / `sys_attachment.visibility`.** -Both fields were modeled in v1 with zero runtime consumers (ADR-0049 -parsed-but-unenforced). There is no replacement key: attachment access is -derived from the parent record by the hooks above. Writers of these fields -should simply stop sending them (unknown-field validation will reject them); -existing DB columns are left as unmanaged leftovers, no migration needed. - -`@objectstack/verify` gains `BootOptions.extraPlugins` for booting optional -service pairs (e.g. storage + audit) in dogfood fixtures. diff --git a/.changeset/authz-2567-anonymous-posture-surfaces.md b/.changeset/authz-2567-anonymous-posture-surfaces.md deleted file mode 100644 index 46650de61e..0000000000 --- a/.changeset/authz-2567-anonymous-posture-surfaces.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -'@objectstack/runtime': minor -'@objectstack/plugin-hono-server': minor ---- - -fix(security): enforce the anonymous-deny posture uniformly across HTTP surfaces (#2567) - -The ADR-0056 D2 `requireAuth` flip made REST `/data/*` deny-anonymous by -default, but three sibling surfaces reached ObjectQL without passing through the -gate — so the platform's anonymous posture was **inconsistent by surface**: an -anonymous caller denied on `/data` could read the same object data through a -different door. This closes the remaining two gaps (the `/meta` gate had already -landed) and pins every surface with a conformance row. - -- **Dispatcher GraphQL** (`runtime/http-dispatcher.ts`, `dispatcher-plugin.ts`): - `POST /graphql` reached `kernel.graphql`, whose security middleware falls - **open** for an anonymous context. `handleGraphQL` now applies the same - `requireAuth` gate as `/data` and `/meta`, resolving identity for the direct - route that does not flow through `dispatch()`. The dispatcher's `requireAuth` - default is aligned with the REST plugin's (`?? true`) so a bare host no longer - denies anonymous `/data` while serving the same rows over `/graphql`; an - explicit `requireAuth: false` opt-out is honoured and logs a boot warning. - -- **Raw-hono standard `/data` routes** (`plugin-hono-server/hono-plugin.ts`): - these delegate straight to ObjectQL and were only *shadowed* when the REST - plugin registered the same paths first — so secure-by-default depended on - plugin registration order. Each route now consults `requireAuth` (secure by - default, mirroring `rest-server.ts`), making the deny decision a property of - this entry point too. Order no longer affects the anonymous posture. - -**Behaviour change:** on a `requireAuth` deployment (the secure default), -anonymous `POST /graphql` and anonymous raw-hono `/data` now return 401. -Deployments that intentionally serve these surfaces publicly set -`requireAuth: false` (a boot warning is logged). Proven end-to-end on the -platform default in `showcase-anonymous-deny-surfaces.dogfood.test.ts`, with -handler-level regression coverage in `http-dispatcher.requireauth.test.ts` and -`hono-anonymous-deny.test.ts`, and pinned by three new authz-conformance rows. diff --git a/.changeset/authz-2567-handlesecurity-seam.md b/.changeset/authz-2567-handlesecurity-seam.md deleted file mode 100644 index 7bb1aba435..0000000000 --- a/.changeset/authz-2567-handlesecurity-seam.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -'@objectstack/runtime': patch ---- - -refactor(security): migrate the handleSecurity admin gate to shouldDenyAnonymous (#2567 follow-up) - -The dispatcher's `/security/suggested-bindings` admin surface was the last HTTP -seam still hand-rolling the `!userId && !isSystem → 401` check. It now delegates -to the shared `shouldDenyAnonymous` decision like every other seam — with -`requireAuth: true` hardcoded, preserving its UNCONDITIONAL semantics (an admin -surface denies anonymous callers even on a `requireAuth: false` demo deployment). -The 401 body adopts the shared shape (`code: 'unauthenticated'`). - -Deliberately NOT migrated: `handleNotification`'s `!userId` check — that is a -"needs a user identity" predicate (the inbox is keyed by userId; a system -context has no inbox), not an anonymous-posture decision; migrating it would -change semantics. diff --git a/.changeset/authz-2567-shared-anonymous-deny.md b/.changeset/authz-2567-shared-anonymous-deny.md deleted file mode 100644 index d4c05181b1..0000000000 --- a/.changeset/authz-2567-shared-anonymous-deny.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -'@objectstack/core': minor -'@objectstack/runtime': patch -'@objectstack/rest': patch -'@objectstack/plugin-hono-server': patch ---- - -refactor(security): converge the anonymous-deny decision into one shared function + a source-enumerating ratchet (#2567 Phase 2) - -Phase 1 gated every HTTP surface (REST `/data`, dispatcher `/graphql` + `/meta`, -raw-hono `/data`) against the secure-by-default `requireAuth` posture, but each -seam hand-rolled the same `!userId && !isSystem → 401` check. Phase 2 removes -that duplication and pins the surfaces so a new ungated entry point fails CI. - -- **New `shouldDenyAnonymous` in `@objectstack/core`** (`security/anonymous-deny.ts`) - — the single anonymous-deny decision + shared 401 body/constants, mirroring the - `auth-gate.ts` pattern (pure function so the seams can never drift). All five - seams — REST `enforceAuth`, dispatcher `handleGraphQL` / `handleMetadata` / - `handleAI`, hono `denyAnonymous` — now delegate to it. **Pure refactor: no - runtime behavior change** (verified by the unchanged Phase-1 handler + e2e - proofs). Identity resolution and the dynamic exemptions (public-form grants, - share-link tokens) are untouched — they run upstream and only ever hand the - seam an already-resolved context. -- **A `discover()` ratchet on the authz-conformance matrix** — it statically - enumerates the data/meta/graphql HTTP entry points from source (curated - per-file probes, control-plane routes excluded) and asserts each is classified - by a matrix `covers` key. A new `/data`/`/meta`/`/graphql` route (or a - removed/stale `covers`) now fails CI as UNCLASSIFIED / STALE, not in review. A - companion negative test proves the ratchet bites. - -A design trap is guarded: `isAuthGateAllowlisted(undefined)` returns `true`, so a -body-routed seam (GraphQL, which has no request path) must pass no path — the -shared function's non-empty-path guard denies anonymous unconditionally there, -never falling through to the control-plane allowlist. diff --git a/.changeset/authz-2852-readfilter-onbehalfof-sentinel.md b/.changeset/authz-2852-readfilter-onbehalfof-sentinel.md deleted file mode 100644 index e27fe58b6c..0000000000 --- a/.changeset/authz-2852-readfilter-onbehalfof-sentinel.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -'@objectstack/plugin-security': patch ---- - -fix(security): fail-closed sentinel for on-behalf-of reads on getReadFilter (#2852) - -`getReadFilter` (the read-scope provider the analytics/raw-SQL path binds to) -resolves only the caller's own ceiling — the ADR-0090 D10 delegator RLS -intersection that the engine middleware applies to find/count/aggregate is not -implemented on this path. Computing a filter here for a delegated (on-behalf-of) -context would therefore silently widen the read past the delegator's scope. - -Until the intersection is threaded through `computeRlsFilter` (tracked with -#2920 B1 / ADR-0095 D1), `getReadFilter` now denies fail-closed (deny sentinel + -error log) when `context.onBehalfOf.userId` is set. System on-behalf-of bypasses -ahead of the guard, and no agent surface reaches analytics today, so this is a -latent-invariant guard rather than a live-traffic behavior change. diff --git a/.changeset/authz-2947-posture-plumb.md b/.changeset/authz-2947-posture-plumb.md deleted file mode 100644 index 3999d21770..0000000000 --- a/.changeset/authz-2947-posture-plumb.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -'@objectstack/spec': minor -'@objectstack/runtime': patch -'@objectstack/rest': patch ---- - -fix(authz): carry the derived posture rung on ExecutionContext (#2947) - -The ADR-0095 D2 posture ladder (`PLATFORM_ADMIN > TENANT_ADMIN > MEMBER > -EXTERNAL`) is derived once by the shared authz resolver from capability grants, -but both HTTP/MCP entry points that build the `ExecutionContext` dropped it — -so any enforcement-side reader of `context.posture` always saw `undefined` -(the same drop that forced the explain layer to re-derive it, #2949). - -`ExecutionContextSchema` now carries an optional `posture` field, and both -`rest-server` and the runtime `resolveExecutionContext` plumb the resolver's -value through. Additive and **behavior-preserving**: no enforcement decision -consumes `posture` yet — whether the hot path evaluates *by* posture remains a -larger ADR-level decision — this only stops the already-computed value from -being discarded, so enforcement and explain read the same derived rung. diff --git a/.changeset/authz-2948-readonly-update.md b/.changeset/authz-2948-readonly-update.md deleted file mode 100644 index b26754b1f1..0000000000 --- a/.changeset/authz-2948-readonly-update.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -'@objectstack/objectql': minor ---- - -fix(security): enforce static `readonly` fields on the UPDATE write path (#2948) - -A field's static `readonly: true` was never enforced server-side on update: the -record validator only *skipped* read-only columns from validation, and only the -conditional `readonlyWhen` variant was stripped from the write payload. A -non-system (user-context) update could therefore overwrite any `readonly` -column — audit stamps, provenance (`managed_by`), or other system-computed -values — unless a field-level permission happened to guard it. (The -cross-tenant `organization_id` face was already closed by #2946; this is the -broader in-tenant integrity face.) - -`engine.update` now strips **caller-supplied** writes to statically-`readonly` -fields for non-system contexts, on both the single-id and multi-row paths -(symmetric with `readonlyWhen` — it strips, does not reject). Two guards keep -every legitimate write intact: - -- **caller-supplied only** — the strip runs against a snapshot of the keys the - caller sent *before* hooks/middleware ran, so server stamps applied by the - audit hook (`updated_by`/`updated_at`) and write middleware survive; only a - client that explicitly forged a read-only field has it dropped. -- **system-context exempt** — `isSystem` writes (import, seed replay, approvals, - lifecycle hooks) legitimately set read-only columns and skip the strip. - -No change for single-org or any write that does not forge a read-only column. diff --git a/.changeset/authz-3042-readonlywhen-multi.md b/.changeset/authz-3042-readonlywhen-multi.md deleted file mode 100644 index 7959a92c70..0000000000 --- a/.changeset/authz-3042-readonlywhen-multi.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -'@objectstack/objectql': patch ---- - -fix(security): enforce `readonlyWhen` on the multi-row UPDATE path (#3042) - -Conditional `readonlyWhen` field locks were stripped only on the single-id -UPDATE path; the bulk `update({ multi: true, where })` path enforced static -`readonly` (#2948) but never `readonlyWhen`. A programmatic/embedded caller (or -a plugin) issuing a multi-row update in a user context could therefore write a -field its own `readonlyWhen` predicate should have locked — the conditional -lock held for a `PATCH /data/:object/:id` but not for a bulk where-predicate -update. (The external REST/SDK `updateMany` endpoint was unaffected: it loops -single-id `engine.update` calls, which already strip `readonlyWhen`.) - -`engine.update` now, on the multi-row path and only when the payload actually -writes a `readonlyWhen` field, reads the row-scoped match set with the same -composed AST the write binds (one query) and drops any field whose predicate is -TRUE for at least one matched row — a single bulk payload cannot keep a field -for some rows and drop it for others, so a field locked in any target row is -fail-safe-dropped for the batch (narrow the `where` to reach the rows where it -is unlocked). A conditional field NO matched row locks is written normally, so a -legitimate bulk edit is unaffected. Symmetric with the single-id -`stripReadonlyWhenFields` and with the static-`readonly` bulk strip; INSERT -stays exempt. No change for any single-id update or any object without -`readonlyWhen` fields. diff --git a/.changeset/console-092bd859934f.md b/.changeset/console-092bd859934f.md deleted file mode 100644 index ddc31dbb84..0000000000 --- a/.changeset/console-092bd859934f.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -"@objectstack/console": patch ---- - -Console (objectui) refreshed to `092bd859934f`. Frontend changes in this range: - -- fix(app-shell): bind current_user.positions into the client predicate scope; align role-gating examples (#1583 / ADR-0058) (#2573) -- feat(app-shell): CEL lint + field autocomplete for condition predicates (#1582) (#2567) -- fix(detail): gate related lists on the current user's child-object read permission (#2359) (#2565) -- feat(flow-designer): connector picker lists dispatchable connectors + marks declarative instances (#2563) -- feat(app-shell): Studio CEL editor for list-view conditional formatting (#1584 / #1582) (#2558) -- fix(fields): resolve lookup chip display name via referenced object schema, not the autonumber-prone key heuristic (#2357) (#2551) -- feat(kanban): accept CEL { condition, style } conditional-formatting rules (#1584 follow-up) (#2550) -- fix(plugin-grid): sniff CSV encoding in import wizard (GB18030 fallback) (#2557) -- feat(detail): editable record highlights on the shared inline-edit draft (#2549) -- fix(spec-bridge/form): #2545 stop dropping spec FormViewSchema keys; normalize legacy groups → sections (#2552) -- feat(flow-designer): localize palette headings + cloud-sync recents + guide (#2553) -- fix(studio): refresh builder top-bar name after a package rename (#2554) -- feat(core): B3 cascading-option guardrail, role-gated demo, ADR + browser e2e (#1583) (#2547) -- feat(list): unify conditional formatting + row-action visibility onto the CEL engine (#1584) (#2544) -- feat(detail): record-level inline edit — shared InlineEditContext + one atomic Save (#2542) -- feat(flow-designer): search box + keyboard nav + recents in the add-node palette (#2543) -- feat(kanban): default card fields to object highlightFields (ADR-0085, #2162) (#2541) -- fix(types/plugin-grid): #1763 declare spec-canonical bulkActions on ObjectGridSchema (#2539) -- fix(attachments): download attachments via authenticated signed URL (framework #2970) -- feat(studio): spec-driven package create/edit/view form in a modal (#2535) -- fix(permissions/fields): #2926 ④⑧ — FLS fail-open + lookup display_field (#2537) -- feat(app-shell): CEL authoring safety for RLS policies — lint, field autocomplete, test-run (#2533) -- fix(auth): gate DeviceAuthPage on features.deviceAuthorization (framework#2874 / #2513) (#2536) -- fix(app-shell): close view config panel on discard in edit mode (#2320) -- feat(metadata-admin): create form-family views through the View create UI (#2531) -- fix(app-shell): render action's objectName as an object selector (#2325) -- fix(components): exit inline edit mode for injected cell editors (#2534) -- fix(attachments): authenticated uploads + friendly denial copy in RecordAttachmentsPanel (#2755) (#2532) -- feat(components): page:tabs honors item-level visibleWhen — conditional tabs (framework#2606) (#2516) -- feat(metadata-admin): page variable source is a component picker (#2328) (#2523) -- fix(studio-design): make object canvas overridable via studio-canvas-preview registry (#2337) (#2527) -- fix(metadata-admin): seed flow createDefaults with required `type` (#2525) -- fix(metadata-admin): give hook create form a createSchema so object renders as a ref:object picker (#2521) -- feat(studio): enforce package namespace prefix at authoring time (framework#2694) (#2524) -- fix(app-shell): render View create-form Object field as ref:object picker (#2526) -- feat(components): add `variant === 'primary'` tie-break to action:bar ordering (#2339) (#2519) -- fix(build): stop TS6059 rootDir errors in dts build across 21 packages (#2520) -- fix(app-nav): exclude record-detail pages from the 'page' nav picker (#2333) (#2517) -- fix(plugin-report): stop TS6059 rootDir errors in dts build (#2334) (#2518) -- feat(flow-designer): add 'position' xRef picker kind for approval approvers / escalateTo (#2778) (#2515) - -objectui range: `cc2156841787...092bd859934f` diff --git a/.changeset/console-23d65c396b8c.md b/.changeset/console-23d65c396b8c.md deleted file mode 100644 index 57304d2bd4..0000000000 --- a/.changeset/console-23d65c396b8c.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -"@objectstack/console": patch ---- - -Console (objectui) refreshed to `23d65c396b8c`. Frontend changes in this range: - -- fix(i18n): drop try/catch-around-hook in createSafeTranslation / useSafeTranslate (#2605) -- fix(app-shell): Studio Access matrix — history opens in-place sheet, breadcrumb stops escaping the pillar (#2599) -- fix(data-objectstack): emit mutation events from batchTransaction/bulk so related lists refresh after master-detail saves (#2607) -- fix(metadata-admin): follow the live app locale, not just navigator.language (#2602) -- feat(detail+fields+components+app-shell): record inline-edit polish (#2572) (#2604) -- fix(app-shell+kanban+list): row-predicate CEL authoring advertises runtime-bound roots; kanban binds host scope (#2571 follow-up) (#2603) -- fix(plugin-list): spec bare-string sort form crashed ListView (#2578 shape-mismatch audit) (#2601) -- fix(app-shell): lock the Access pillar permission matrix in read-only packages (#2570) -- fix(fields): localize relative-date humanize via Intl.RelativeTimeFormat (framework#3040) (#2593) -- fix(components): pin sticky leading cells at measured header widths (#2592) -- fix(app-shell,core): keep error-envelope objects out of toast.error — React #31 page crash (#2579) (#2580) -- feat(flow-designer): pick the target node per branch in the Decision Branches editor (#1942) (#2568) -- fix(core+data-objectstack+app-shell): canonicalize reference/reference_to at the schema chokepoints (#2407) (#2598) -- fix(dashboard-filters): spec-form filter options crashed the dashboard; add guide screenshots (#2578) (#2597) -- fix(fields): PeoplePicker cursor resets only on real result changes (de-flakes keyboard test) (#2594) -- fix(studio): stop force-opening the new-object dialog on empty packages (#2569) -- feat(studio): CEL editor with validate + autocomplete for field conditional rules (#1582) (#2571) -- feat(kanban): default lane field honours the ADR-0085 stageField role (#2596) -- fix(fields+detail): resolve pre-existing rules-of-hooks violations in cell renderers (#2595) - -objectui range: `fb35e4828fdb...23d65c396b8c` diff --git a/.changeset/console-b8967495be73.md b/.changeset/console-b8967495be73.md deleted file mode 100644 index 9ca17d1eb0..0000000000 --- a/.changeset/console-b8967495be73.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -"@objectstack/console": minor ---- - -Console (objectui) refreshed to `b8967495be73`. Frontend changes in this range: - -- fix(app-shell): guard unsaved OWD overview rows in the Access rail and Studio header nav (#2600 follow-up) (#2610) -- fix(actions): defuse the three action-visibility traps (#2358) (#2611) -- fix(studio): confirm before header SPA nav discards unsaved pillar edits (#2600) (#2606) -- feat(studio): CEL formula editor with inferred result type; structured summary roll-up editor (#1582) (#2609) - -objectui range: `23d65c396b8c...b8967495be73` diff --git a/.changeset/console-fb35e4828fdb.md b/.changeset/console-fb35e4828fdb.md deleted file mode 100644 index 5025294204..0000000000 --- a/.changeset/console-fb35e4828fdb.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -"@objectstack/console": minor ---- - -Console (objectui) refreshed to `fb35e4828fdb`. Frontend changes in this range: - -- fix(data-objectstack): emit MutationEvents from batchTransaction and bulk so master-detail saves refresh bound views (#2584) -- feat(dashboard-filters): #2578 item-5 enhancements — nested variable merging, metadata-aware default bindings, server-side optionsFrom distinct (#2590) -- feat(fields+form+detail): file/image upload cells in inline line-item grids (#2360) (#2585) -- feat(app-shell): visual filterBindings editor in the dashboard widget inspector (#2578) (#2586) -- fix(detail): highlight strip lookup editor honors ObjectStack `reference` key (#2407) (#2587) -- fix(app-shell): guard Studio Access pillar against silently discarding unsaved matrix edits (#2588) -- feat(dashboard-filters): #2578 follow-ups — catalog examples, guide tutorial, i18n entries, spec-alignment cleanup (#2581) -- fix(detail+fields+app-shell): ADR-0085 #2548 follow-ups — strip title dedupe, group icon/description, currency channel, approvals Bearer (#2577) -- feat(dashboard): dashboard-level filters driving multiple charts (framework#2501) (#2576) -- feat(page-header): metadata-driven multi-button record header (#2361) (#2574) - -objectui range: `092bd859934f...fb35e4828fdb` diff --git a/.changeset/cors-allow-if-match.md b/.changeset/cors-allow-if-match.md deleted file mode 100644 index ad84a7e414..0000000000 --- a/.changeset/cors-allow-if-match.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -'@objectstack/plugin-hono-server': patch -'@objectstack/hono': patch ---- - -CORS default `allowHeaders` now includes `If-Match`. The REST record update -accepts the OCC token as an `If-Match` header (objectui's record-level inline -edit sends it on every save), but the preflight allow-list omitted it — so on -any split-origin deployment (console dev server against a backend on another -origin) the browser failed the preflight and every inline-edit save died with -"Failed to fetch". Found live while dogfooding objectui#2572; same -split-origin failure class as the #2548 Bearer fixes. Explicit user-supplied -`allowHeaders` still win unchanged. diff --git a/.changeset/dashboard-filter-spec-pairing.md b/.changeset/dashboard-filter-spec-pairing.md deleted file mode 100644 index 9ee02009ee..0000000000 --- a/.changeset/dashboard-filter-spec-pairing.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -'@objectstack/spec': minor ---- - -Dashboard-level filters spec pairing (framework#2501, objectui#2578) — land the -two properties the objectui runtime already ships (objectui#2576) so the -protocol and the renderer agree: - -- **`GlobalFilterSchema.name`** (optional string) — stable filter name used as - the dashboard-variable key (readable in widget expressions as `page.`) - and as the key widgets reference in `filterBindings`. Defaults to `field`; - `"dateRange"` is reserved for the built-in dashboard date range. -- **`DashboardWidgetSchema.filterBindings`** (optional - `Record`) — per-widget binding from a dashboard - filter name to one of THIS widget's fields: a string re-targets the filter to - that field, `false` opts the widget out, absent falls back to the filter's - own `field`. - -Purely additive — existing dashboards parse unchanged. The metadata-admin -dashboard inspector (objectui `dashboard-schema.ts`) derives its form from this -schema via `z.toJSONSchema`, so both properties surface there automatically -once objectui picks up this spec version. diff --git a/.changeset/declarative-connectors-2612-descriptor-contract.md b/.changeset/declarative-connectors-2612-descriptor-contract.md deleted file mode 100644 index 01ad8d4b76..0000000000 --- a/.changeset/declarative-connectors-2612-descriptor-contract.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -'@objectstack/service-automation': minor -'@objectstack/spec': patch ---- - -feat(automation): descriptor-only contract + boot audit for declarative `connectors:` (#2612) - -Declarative `connectors:` stack entries never reach the automation engine's -connector registry — only plugins populate it via -`engine.registerConnector(def, handlers)` (ADR-0018 §Addendum) — so a declared -connector with actions and no plugin behind it *looked* dispatchable but was -silently inert. - -The contract is now explicit and audited: - -- **Boot audit (service-automation).** At `kernel:ready` (and again on - `metadata:reloaded`), declared connectors with `actions` but no same-name - runtime registration log a loud warning naming each inert entry and - pointing at the fix (install the matching connector plugin, or mark a - deliberate catalog entry). Nothing is registered on your behalf — the - warning surfaces the gap `connector_action` would otherwise hit at - dispatch time. -- **`enabled: false` = deliberate catalog descriptor (spec).** Setting it on - a declarative entry documents "descriptor-only on purpose" and silences the - audit. Schema docs on `stack.zod.ts` (`connectors:`) and - `integration/connector.zod.ts` now state the descriptor-vs-registered - contract explicitly (including for AI stack authoring via `.describe()`). - -Declarative provider-bound connector *instances* — entries a generic executor -(connector-openapi / connector-mcp) materializes into live connectors at boot, -upgrading this warning to a hard error — are specified in ADR-0096 and tracked -in #2977. diff --git a/.changeset/docs-2567-anonymous-posture-surfaces.md b/.changeset/docs-2567-anonymous-posture-surfaces.md deleted file mode 100644 index a4ac6f2c62..0000000000 --- a/.changeset/docs-2567-anonymous-posture-surfaces.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -'@objectstack/spec': patch ---- - -docs(security): document that `requireAuth` denies anonymous across ALL HTTP surfaces (#2567) - -The `api.requireAuth` schema description and JSDoc said the anonymous-deny -posture applied to REST `/data/*` only. Post-#2567 the same value is threaded to -every entry point that reaches object data — REST `/data`, the metadata -endpoints (`/meta`), the dispatcher GraphQL endpoint (`/graphql`), and the -raw-hono standard `/data` routes — sharing one decision (`shouldDenyAnonymous`). -The description now reflects the uniform, by-surface posture and the single -opt-out (`requireAuth: false`). Doc-only; no behavior change. - -(Accompanying hand-written docs — `permissions/authorization.mdx` and the -regenerated `references/api/rest-server.mdx` — are updated to match.) diff --git a/.changeset/docs-3003-readonly-server-enforced.md b/.changeset/docs-3003-readonly-server-enforced.md deleted file mode 100644 index ae46cf9667..0000000000 --- a/.changeset/docs-3003-readonly-server-enforced.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -'@objectstack/spec': patch ---- - -docs(spec): `readonly` is server-enforced on UPDATE, not a UI-only affordance (#3003) - -The `readonly` field property was described as "Read-only in UI", which #3003 -proved to be exactly how integrators read it — approval/status/amount columns -protected only by `readonly: true` were forged with a direct REST `PATCH`, -self-approving a multi-stage approval on the released 15.0.0. Since #2948 the -engine strips caller-supplied writes to statically-readonly fields from every -non-system UPDATE (single-id and multi-row, symmetric with `readonlyWhen`; -INSERT may still seed the column). The schema description and the field -liveness ledger now state the server-side contract, and a dogfood conformance -proof (`showcase-static-readonly.dogfood.test.ts` + an authz-matrix row) pins -it end-to-end so it cannot silently regress to renderer-only. diff --git a/.changeset/feature-gate-registry-and-requires-feature.md b/.changeset/feature-gate-registry-and-requires-feature.md deleted file mode 100644 index 0a3cfba8c5..0000000000 --- a/.changeset/feature-gate-registry-and-requires-feature.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -'@objectstack/spec': patch -'@objectstack/platform-objects': patch ---- - -**Every feature-gated capability is now UI-gated, guardrailed by a flag registry and a declarative `requiresFeature` annotation (#2874, generalizing the create-user phone fix #2871).** - -`@objectstack/spec/kernel` gains `PUBLIC_AUTH_FEATURES` — a classification registry for all 13 boolean flags served at `/api/v1/auth/config`: consumption surface (crud/login/status), default semantics (opt-in `== true` vs default-on `!= false`), and the gated spec inputs or an exemption reason. A plugin-auth drift test pins the served key set to the registry, and a platform-objects completeness guard pins the registry to the actual gates in both directions. - -`ActionSchema`/`ActionParamSchema` gain `requiresFeature: ''` (enum-checked), lowered at parse time into the canonical `visible` CEL predicate per the flag's registered semantics, AND-composed with any explicit `visible`, and stripped from the output — renderers and lint see only `visible`, so objectui needs no changes. All 22 hand-written `features.*` gates migrated (behavior-locked by an exact-string matrix test), and the audit gated 17 previously naked capability-dependent actions: the six `sys_user` platform-admin actions, six 2FA actions, and five `sys_oauth_application` actions now hide when their plugin is off instead of rendering buttons that 404. diff --git a/.changeset/fix-async-import-cancel-2824.md b/.changeset/fix-async-import-cancel-2824.md deleted file mode 100644 index a8eaff02fb..0000000000 --- a/.changeset/fix-async-import-cancel-2824.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -'@objectstack/rest': patch ---- - -fix(import): make async import-job cancellation actually stop the worker (#2824) - -Cancelling a running async import used to have no effect on a synchronous -storage driver (better-sqlite3 / wasm fallback): every `await` in the row -loop resolved as a microtask, so a 50k-row import monopolized the Node event -loop for minutes — the cancel route's HTTP handler (and every progress poll) -could never run, so the in-memory flag `shouldCancel` polls was never set. -The job then finished `succeeded` with all rows written despite the user's -cancel. - -Three-part fix: - -- **`runImport` yields one macrotask at every progress boundary** (every - `progressEvery` rows), so pending I/O — the cancel request, progress - polls, any other traffic — gets serviced during a large import. This is - the root-cause fix; it also unblocks progress polling for the wizard. -- **The worker's `shouldCancel` now also reads the durable job row** as a - fallback: a cancel accepted by another process (or after a restart - dropped the in-memory flag) still stops the worker. -- **A late cancel wins the terminal state**: the worker's final patch no - longer overwrites the cancel route's durable `cancelled` with - `succeeded`, and a job cancelled while still `pending` doesn't start at - all. Counts stay truthful — they reflect what was actually written. diff --git a/.changeset/formview-buttons-defaults.md b/.changeset/formview-buttons-defaults.md deleted file mode 100644 index 482e77743c..0000000000 --- a/.changeset/formview-buttons-defaults.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -'@objectstack/spec': minor ---- - -feat(spec): structured `buttons` + `defaults` config on `FormViewSchema` (#2998) - -`FormViewSchema` gains two optional top-level keys — the spec home for the flat -renderer-invented form config ObjectUI's `ObjectForm` reads today -(`showSubmit`/`submitText`/`showCancel`/`cancelText`/`showReset`/`initialValues`, -objectui#2545), which the strip-mode container silently discards: - -- **`buttons`** — structured action-button config: per-button `{ show, label }` - for `submit` / `cancel` / `reset` (new exported leaf `FormButtonConfigSchema`, - `.strict()` per ADR-0089 D3a so typo'd keys error loudly). -- **`defaults`** — initial field values for create-mode forms, keyed by field - machine name (absorbs ObjectUI's `initialValues`). - -Both are marked `[EXPERIMENTAL — NOT ENFORCED]` per ADR-0078's escape hatch -until the ObjectUI renderer reads them (tracked in objectui#2545); authoring -them today is declared, not yet honored. Purely additive — no existing key -changes shape, no tombstone needed. diff --git a/.changeset/graphql-realtime-identity-2992.md b/.changeset/graphql-realtime-identity-2992.md deleted file mode 100644 index df55bd4adf..0000000000 --- a/.changeset/graphql-realtime-identity-2992.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -'@objectstack/runtime': patch -'@objectstack/spec': patch -'@objectstack/service-realtime': patch ---- - -fix(security): pre-wiring identity admission for the GraphQL and realtime surfaces (#2992, ADR-0096 D4) - -Two latent execution surfaces — neither reachable by a client today — would -have fallen open the instant a real transport was wired, because both drop or -lack the caller's identity. Per ADR-0096, the identity story is fixed and -pinned in CI *before* wiring, not after an adversarial review: - -- **GraphQL (surface 1 — latent context-drop, now threaded).** - `handleGraphQL` passed only `{ request }` to `kernel.graphql`, dropping the - resolved `ExecutionContext` — the moment a real engine resolved objects - through ObjectQL it would have run context-less (security middleware falls - OPEN on a missing principal = full authority). The entry point now resolves - the caller identity even on the direct dispatcher-plugin route and even when - `requireAuth` is off, and threads it as `options.context`; - `IGraphQLService.execute` documents that implementations MUST forward it to - every data-engine call. Unit-proven; the authz conformance matrix pins the - threading (`graphql-identity-thread` row) so removing it goes STALE and - fails CI. - -- **realtime (surface 2 — no per-recipient authz seam, posture registered).** - Delivery is a pure fan-out (subscriptions carry no principal, - `matchesSubscription` filters only by object+eventTypes, the engine - publishes the full `after` row), safe only while every subscriber is - server-internal. The posture is now registered as an `experimental` matrix - row (`realtime-delivery-authz`) stating the admission requirement - (per-recipient RLS/FLS/tenant re-check on delivery, or id-only payload + - client re-fetch), and transport TRIPWIRE probes turn any newly wired - WebSocket/SSE/subscribe/client transport into an UNCLASSIFIED surface → red - CI until the identity story ships with it. The `service-realtime` README — - which advertised `authorizeChannel`/`broadcastToUser`/presence auth that do - not exist — is rewritten to describe the real, trusted-internal-only - surface, and the contract docs carry the admission requirement at the seam. diff --git a/.changeset/i18n-extract-action-params.md b/.changeset/i18n-extract-action-params.md deleted file mode 100644 index 1497fab834..0000000000 --- a/.changeset/i18n-extract-action-params.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -'@objectstack/cli': minor -'@objectstack/platform-objects': patch ---- - -feat(cli): `os i18n extract` now emits action param keys (`o.._actions..params..*`) so action-dialog forms are translatable (#3030) - -The console client already resolves param labels, help text, placeholders and -option labels from `o.._actions..params.*`, but the extractor -never walked `actions[].params`, so those keys were absent from generated -bundles and dialogs like Setup → Create User rendered raw English under any -locale. The extractor now emits: - -- inline params → `label` / `helpText` / `placeholder` / `options.`; -- field-backed params (`{ field: '…' }`) → only when they carry a literal - override (field translations already cover them at runtime); -- both object actions and top-level (global) actions. - -`@objectstack/platform-objects` regenerates its en/zh-CN/ja-JP/es-ES bundles -with the new keys filled (user admin actions, sys_jwks fields, page variable -forms). Re-running extract with `--merge` stays idempotent. diff --git a/.changeset/import-multivalue-reference-split.md b/.changeset/import-multivalue-reference-split.md deleted file mode 100644 index 8592624b9d..0000000000 --- a/.changeset/import-multivalue-reference-split.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -'@objectstack/rest': patch ---- - -fix(rest): split multi-value fields on import so `multiple: true` columns resolve per-token (#3063) - -The bulk-import coercion (`import-coerce.ts`) resolved a reference cell as a -single value regardless of the field's `multiple` flag: a `multiple: true` -lookup/user cell like `张焊工;李质检` was passed whole to name resolution and -always failed with `no matches "张焊工;李质检"`, so every multi-value -association had to be back-filled by hand in the record UI after import. - -Coercion now mirrors objectql's `isMultiValueField` predicate. A field whose -stored value is an array — an inherently-multi type (multiselect/checkboxes/tags) -or a multi-capable type flagged `multiple: true` (per the spec: select, lookup, -file, image; `radio` shares select's branch and `user` shares lookup's) — has -its cell split on the export separator (`, ` / `;` / `、` / newline) and each -token coerced individually: - -- **lookup / user (`multiple: true`)** — resolve each name token to an id, store - the id array; an unmatched/ambiguous token reports the **specific token** - (`no sys_user matches "查无此人"`) instead of the whole string. -- **select / radio (`multiple: true`)** — match each token against the options, - store the option-value array. -- **file / image (`multiple: true`)** — split into an id/url array. - -Single-value fields and the non-multi-capable reference types (master_detail / -reference / tree) are unchanged — a stray `multiple: true` on them stays a -single resolved value, matching the engine. diff --git a/.changeset/kernel-bootstrapped-anchor.md b/.changeset/kernel-bootstrapped-anchor.md deleted file mode 100644 index 91e90294bf..0000000000 --- a/.changeset/kernel-bootstrapped-anchor.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@objectstack/spec': minor -'@objectstack/core': minor -'@objectstack/plugin-sharing': patch ---- - -feat(kernel): add `kernel:bootstrapped` lifecycle anchor — the phase that fires after every `kernel:ready` handler has settled but before `kernel:listening` (HTTP socket open). `kernel:ready` handlers run sequentially in plugin-registration order, so a handler that consumes data produced by a later-starting plugin (e.g. the security bootstrap seeds `sys_position`; the app plugin's seed loader inserts records) would race the very rows it needs. `kernel:bootstrapped` is the correct anchor for reconcile/backfill work: every producer's ready handler has finished by the time it fires. Both `ObjectKernel` and `LiteKernel` trigger it. The sharing-rule boot backfill moves from `kernel:listening` to `kernel:bootstrapped` (semantics-only; behaviour unchanged). diff --git a/.changeset/lazy-schema-tojsonschema-identity.md b/.changeset/lazy-schema-tojsonschema-identity.md deleted file mode 100644 index fe906d21bd..0000000000 --- a/.changeset/lazy-schema-tojsonschema-identity.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -'@objectstack/spec': patch ---- - -fix(spec): keep `lazySchema` proxies identity-compatible with `z.toJSONSchema` (objectui#2561) - -zod's `toJSONSchema` keys its `seen` map on the node object it traverses — the `lazySchema` Proxy wherever a schema is referenced lazily (`z.lazy(() => X)` recursion getters, direct conversion roots) — while its wrapper-type processors (pipe/lazy/optional/default/…) look themselves up via the REAL instance captured at construction (`inst._zod.processJSONSchema = (ctx, …) => pipeProcessor(inst, …)`). The identity mismatch crashed conversion with `Cannot set properties of undefined (setting 'ref')`. - -This stayed latent while lazy-referenced schemas were plain objects (the object processor never looks itself up); ADR-0089 D3a turned `PageComponentSchema` / `FormFieldSchema` into `.strict().transform(…)` **pipes**, which broke ObjectUI Studio's spec-derived Page/View inspector JSONSchema derivation under spec 15. - -Fix: the proxy now serves a memoised `_zod` facade that prototype-delegates to the real internals and wraps only `processJSONSchema` to alias the proxy's `seen` entry onto the real instance before delegating. Parse behavior is unchanged; `OS_EAGER_SCHEMAS=1` remains the bypass. Regression tests cover the D3a pipe shape, recursion through `z.lazy(() => proxy)`, mixed proxy+real traversal, and the full `PageSchema` / `ViewSchema` Studio derivation paths. diff --git a/.changeset/mcp-aggregate-records.md b/.changeset/mcp-aggregate-records.md deleted file mode 100644 index f904a4e9f0..0000000000 --- a/.changeset/mcp-aggregate-records.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -'@objectstack/mcp': minor -'@objectstack/runtime': minor -'@objectstack/plugin-security': patch ---- - -feat(mcp): `aggregate_records` tool — GROUP BY aggregation over the engine read path - -New MCP tool `aggregate_records` (count/sum/avg/min/max/count_distinct, optional -groupBy incl. date bucketing, where filter, IANA timezone) in the `data:read` -family. Execution routes through the ObjectQL ENGINE (`callData('aggregate')` -deliberately never uses the raw per-env driver), so RLS/tenant scoping and the -D10 delegator intersection apply exactly as on find. - -Security hardening shipped with it: - -- plugin-security: new FLS aggregate-INPUT gate — result masking never runs for - `aggregate` (output rows carry only aliases), so any groupBy / aggregation - reference to an FLS-unreadable field is now rejected fail-closed with the - offending field names (mirrors the FLS write gate). -- runtime: `aggregate` maps to the `list` ApiMethod in the object exposure gate - (an object whose `apiMethods` whitelist excludes `list` cannot leak row - statistics through GROUP BY), and the aggregate action requires at least one - aggregation (the engine's in-memory path would otherwise degrade to raw rows - that the FLS masker does not cover). - -The bridge seam is optional: a runtime that does not implement -`McpDataBridge.aggregate` simply does not register the tool (graceful -degradation, same contract as the action tools). diff --git a/.changeset/mcp-standalone-authored-actions.md b/.changeset/mcp-standalone-authored-actions.md deleted file mode 100644 index cc11c5e4c0..0000000000 --- a/.changeset/mcp-standalone-authored-actions.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@objectstack/runtime': patch ---- - -Surface standalone authored `action` metadata rows on the MCP action bridge (#3010). `list_actions` and `run_action` now resolve declarations from `object.actions` unioned with standalone `action` items, keyed the same way the engine registers their handlers (`objectName` → legacy `object` → `'global'`), with object-embedded declarations winning on a key clash. Previously a Studio-authored standalone action executed via REST but was invisible and uninvokable on the MCP/AI surface, even with `ai.exposed: true`. All invoke-time gates (`ai.exposed` fail-closed, ADR-0066 D4 capability gate, sys_* fail-closed) are unchanged. diff --git a/.changeset/membership-backfill-app-seeded.md b/.changeset/membership-backfill-app-seeded.md deleted file mode 100644 index 1f070fb7d8..0000000000 --- a/.changeset/membership-backfill-app-seeded.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -'@objectstack/spec': minor -'@objectstack/runtime': patch -'@objectstack/plugin-auth': patch ---- - -fix(plugin-auth): re-run membership backfill when app seeding settles (#2996) - -The ADR-0093 D6 membership backfill — the only safety net for users created -by app seeds (raw `engine.insert` into `sys_user` bypasses better-auth's -`user.create.after` reconciler) — ran only once on `kernel:ready`. When a seed -bundle overruns its inline budget (`OS_INLINE_SEED_BUDGET_MS`, default 8s) it -finishes in the background *after* `kernel:ready`, so its users stayed -member-less in single-org `auto` mode until the next restart re-ran the backfill. - -`AppPlugin` now emits a new **`app:seeded`** lifecycle event when an app's inline -seed settles (success, partial, or fallback) — carrying `{ appId, overBudget }`, -where `overBudget: true` marks the post-`kernel:ready` background case. plugin-auth -subscribes and re-runs the (idempotent, self-guarding, opt-out-able) -`backfillMemberships` on that signal, closing the window without waiting for a -restart. No behavior change when a seed completes within budget, in multi-tenant -mode, or under `invite-only` policy; `OS_SKIP_MEMBERSHIP_BACKFILL=1` still opts out. diff --git a/.changeset/official-docker-runtime-image.md b/.changeset/official-docker-runtime-image.md deleted file mode 100644 index 3a2aeebbc1..0000000000 --- a/.changeset/official-docker-runtime-image.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -'@objectstack/cli': minor ---- - -feat(docker): official runtime image `ghcr.io/objectstack-ai/objectstack` - -ObjectStack now ships an official, versioned Docker runtime image instead of -only a copy-me example. The image packages Node 22 + a pinned -`@objectstack/cli` + `os start` (non-root `node` user, `/api/v1/health` -HEALTHCHECK, `OS_ARTIFACT_PATH` / `OS_PORT=8080` preset), published -multi-arch (amd64/arm64) on every release with tags mirroring the CLI -version (`X.Y.Z` / `X.Y` / `X` / `latest`). - -Deploying an app is now: - -```dockerfile -FROM ghcr.io/objectstack-ai/objectstack: -COPY --chown=node:node dist/objectstack.json /srv/app/objectstack.json -``` - -or, with no image build at all, `docker run` the official image with the -artifact mounted (or `OS_ARTIFACT_PATH` pointing at an `https://` URL). -`examples/docker` and the Self-Hosted Deployment docs now build on the -official image; the self-built runtime Dockerfile remains documented for -air-gapped registries. diff --git a/.changeset/owd-posture-authoring-gate.md b/.changeset/owd-posture-authoring-gate.md deleted file mode 100644 index cfeb7d2279..0000000000 --- a/.changeset/owd-posture-authoring-gate.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@objectstack/metadata-protocol': minor -'@objectstack/plugin-security': minor ---- - -OWD posture is now enforced on the runtime write path (#3050). `metadata-protocol` gains the ADR-0094-addendum `registerAuthoringGate(type, gate)` seam — an awaited, throwing pre-persistence hook inside `saveMetaItem` (draft and publish-mode saves; environment writes only). `plugin-security` registers the `object` posture gate on it: an environment overlay of a packaged object may only TIGHTEN `sharingModel`/`externalSharingModel` (ADR-0086 D1 — closes the `OS_METADATA_WRITABLE=object` unvalidated-widening hole), and `externalSharingModel ≤ sharingModel` (ADR-0090 D11) is now rejected at save time instead of only by CLI lint. Write-path only — stored metadata keeps loading unchanged. diff --git a/.changeset/owner-guard-referential-cascade-exemption.md b/.changeset/owner-guard-referential-cascade-exemption.md deleted file mode 100644 index e02aae1c90..0000000000 --- a/.changeset/owner-guard-referential-cascade-exemption.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -'@objectstack/plugin-security': patch -'@objectstack/objectql': patch ---- - -fix(security): exempt engine referential FK clears from the owner_id transfer guard (#3023) - -Follow-up to the #3004 ownership-anchor guard. `owner_id` is a lookup to `sys_user` -with the default `deleteBehavior: 'set_null'`, so deleting a `sys_user` makes -`cascadeDeleteRelations` null `owner_id` on every dependent row. That cascade write -re-entered the write middleware under the deleter's context, where the #3004 guard -read the `owner_id = null` as a user-initiated disown and denied it — aborting the -cascade mid-way (no transaction, so partial state) for any deleter without the -transfer grant on the child object (e.g. a member clearing a `public_read_write` -child that RLS would otherwise have allowed). - -The cascade FK clear is engine-mandated referential integrity consequent to an -already-authorized parent delete, not a user ownership change. `cascadeDeleteRelations` -now tags the `set_null` write with a server-derived `__referentialFieldClear` context -marker (set by the engine, never built from a request — same trust model as -`__expandRead`), and the ownership-anchor guard skips when that marker is present. -Ordinary user writes are unaffected; the marker cannot be forged from client input, -so it can never slip a real ownership transfer past the guard. diff --git a/.changeset/owner-id-anchor-and-bulk-write-scoping.md b/.changeset/owner-id-anchor-and-bulk-write-scoping.md deleted file mode 100644 index f3353867a6..0000000000 --- a/.changeset/owner-id-anchor-and-bulk-write-scoping.md +++ /dev/null @@ -1,60 +0,0 @@ ---- -'@objectstack/plugin-security': patch -'@objectstack/objectql': patch -'@objectstack/plugin-sharing': patch ---- - -fix(security): guard the `owner_id` ownership anchor and scope bulk writes to owner-visible rows (#3004, #2982) - -Two write-path holes on the row-ownership anchor (`owner_id`), the column OWD -row-level scoping keys off to decide who may update/delete a record. - -- **#3004 — client-writable, unguarded `owner_id`.** The anchor is deliberately - not `readonly` (ownership is transferable), so the static-readonly strip never - covered it and FLS doesn't gate it by default. A non-privileged writer could - therefore `insert` a record under someone else's name (forge) or `update` one - to a new owner (transfer / disown), evading the owner gate that governs - update/delete. The security middleware (plugin-security step 3.5) now treats - `owner_id` as system-managed for non-privileged writers: on insert an empty - value is auto-stamped to the acting user (batch rows too — previously only the - single-record path stamped, leaving bulk-inserted rows NULL-owned and - invisible to their creator), and a supplied foreign owner is denied; on update - a supplied `owner_id` is a transfer/disown and is denied — the unchanged no-op - echo of a form save is tolerated via a pre-image compare, and a bulk - change-set carrying `owner_id` fails closed. A non-scalar `owner_id` - (array/object) is rejected outright rather than string-coerced, and the - change-set membership test uses own-property semantics so a polluted - prototype cannot spoof an ownership write. Both require the transfer grant - (`allowTransfer`, or `modifyAllRecords` which implies it) to proceed. System - context (`ctx.isSystem`) stays fully exempt (OAuth provisioning / cron - snapshots / seed claims / migrations), and under delegation both principals - must hold the grant (ADR-0090 D10 intersection). Note a REST **import** runs - under the importer's own context (not `isSystem`), so a non-privileged user - importing a CSV whose `owner_id` column names other users is correctly denied - unless they hold the transfer grant — administrators (who carry - `modifyAllRecords`) are unaffected. - -- **#2982 — bulk writes skipped owner scoping on OWD-`private` objects.** A - `update({ multi: true })` / bulk delete rebuilt the driver AST from - `options.where` AFTER the middleware chain, discarding the owner/RLS write - filter that plugin-sharing (`buildWriteFilter`) and plugin-security compose - onto `opCtx.ast` — so a member's bulk write hit every matching row, including - peers'. The engine now seeds `opCtx.ast` from the caller's predicate BEFORE the - chain (the same seam reads use) and hands the middleware-composed AST to - `driver.updateMany` / `driver.deleteMany`, so bulk writes are constrained to the - rows the caller may edit — matching single-id write behavior. `delete` now - applies the same scalar-`id` guard `update` already had, so an id-list bulk - delete (`where: { id: { $in: […] } }, multi: true`) is owner-scoped too, and - both multi branches fail CLOSED (throw) rather than silently rebuilding an - unscoped predicate if the row-scoping AST is ever absent. - - Consequences of routing bulk writes through the AST: the anti-oracle - predicate guard now also applies to bulk `update`/`delete` (a bulk write - filtering on an FLS-unreadable field is rejected, as reads already are), and a - principal-less (no-`userId`, non-system) bulk write on an owner-scoped object - now correctly affects zero rows instead of all of them. - -Proven end-to-end on the real showcase app -(`packages/qa/dogfood/test/owner-anchor-and-bulk-writes.dogfood.test.ts`) and pinned -in the ADR-0096 authz-conformance ledger (`ownership-anchor-guard`, -`bulk-write-owner-scoping`). diff --git a/.changeset/page-tabs-item-visible-when.md b/.changeset/page-tabs-item-visible-when.md deleted file mode 100644 index 29566832f5..0000000000 --- a/.changeset/page-tabs-item-visible-when.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -"@objectstack/spec": minor ---- - -Conditional tabs (#2606): `page:tabs` items accept an optional `visibleWhen` CEL predicate. When it evaluates FALSE the whole tab — header **and** panel — is omitted from the tab strip, unlike a child component's own `visibleWhen`, which hides only the panel content and leaves an empty tab header behind. The predicate binds the same environment as page-component `visibleWhen` (`record` + `current_user`, plus page state as `page.`) and is re-evaluated live when page variables change. - -Per ADR-0089 the key uses the canonical `*When` name from day one — the deprecated `visibility` / `visibleOn` aliases are **not** accepted on tab items (this surface is new; there is no legacy metadata to alias for). - -Additive and back-compatible: items without `visibleWhen` behave exactly as before. diff --git a/.changeset/page-var-source-component-picker.md b/.changeset/page-var-source-component-picker.md deleted file mode 100644 index 6c40914960..0000000000 --- a/.changeset/page-var-source-component-picker.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -'@objectstack/spec': minor ---- - -feat(spec): page variable `source` renders as a component picker (objectui#2328) - -The page metadata form's `variables` repeater now declares explicit sub-fields -and pins `{ field: 'source', widget: 'ref:component' }`. A page variable's -`source` names the component (by `id`) that writes it, so Studio can offer it as -a dropdown of the components actually placed on the page — mirroring how the -sibling `object` field uses `ref:object` — instead of a free-text input the -author has to type an id into by hand. The `ref:component` widget itself lives -in objectui (app-shell metadata-admin); this change is the form-spec trigger. diff --git a/.changeset/pinyin-search-companion.md b/.changeset/pinyin-search-companion.md deleted file mode 100644 index 3ca6f9e955..0000000000 --- a/.changeset/pinyin-search-companion.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -'@objectstack/objectql': minor -'@objectstack/plugin-pinyin-search': minor -'@objectstack/types': minor -'@objectstack/cli': minor ---- - -Generic pinyin search recall (#2486, ADR-0098): a locale-gated -`OS_SEARCH_PINYIN_ENABLED` switch (auto-on when the stack configures any -`zh-*` locale) provisions a hidden `__search` companion column for each -object's display/name field at compile time, the new -`@objectstack/plugin-pinyin-search` fills it with full pinyin + initials -("张伟" → "zhangwei zw") on before-save (plus boot backfill and a -`rebuildSearchCompanion` reconcile entry), and `$search` ORs the column in at -query time — so lookup pickers, list quick-search and ⌘K transparently match -`zhangwei` / `zw` against CJK names. Purely additive: `resolveSearchFields`, -`searchableFields`, drivers and non-Chinese deployments are untouched; FLS -restricted / secret / PII fields never feed the companion. diff --git a/.changeset/plugin-audit-summary-i18n-3039.md b/.changeset/plugin-audit-summary-i18n-3039.md deleted file mode 100644 index 40d69d2513..0000000000 --- a/.changeset/plugin-audit-summary-i18n-3039.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -'@objectstack/plugin-audit': patch ---- - -Localize activity summaries to the workspace default locale (#3039). Activity -writers previously hardcoded English verbs and the object API name -(`Created person_qualification "OC-00001"`). The writer now resolves the -ADR-0053 `localization.locale` setting per write (memoized per tenant/user -scope), renders the verb through new `messages.activityCreated/Updated/Deleted` -i18n templates (en, zh-CN, ja-JP, es-ES shipped), and names the object by its -localized label (`objects.{name}.label`) with fallback to the authored def -label, then the API name. Missing i18n/settings services or bundle keys -degrade to the previous English summaries. diff --git a/.changeset/plugin-auth-better-auth-17-adapter.md b/.changeset/plugin-auth-better-auth-17-adapter.md deleted file mode 100644 index a68a32463c..0000000000 --- a/.changeset/plugin-auth-better-auth-17-adapter.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -'@objectstack/plugin-auth': patch -'@objectstack/platform-objects': patch ---- - -fix(auth): align the better-auth family on 1.7.0-rc.1, implement the new adapter methods, and add the new sys_jwks columns (#2974) - -Remediating GHSA-p2fr-6hmx-4528 (`@better-auth/oauth-provider`) requires the -1.7 plugin line, which imports `CLIENT_ASSERTION_TYPE` and other symbols that -only exist in `@better-auth/core` 1.7.x — so the whole better-auth family is -pinned to `1.7.0-rc.1` together (mixing a 1.7 plugin with 1.6.23 core 500s on -sign-in). better-auth 1.7 also extends its `CustomAdapter` contract with two -new methods, which the ObjectQL adapter now implements: - -- `consumeOne` — atomic single-row consume (find the guarded row, delete it, - return it), used by better-auth for single-use credential consumption - (e.g. verification tokens on the sign-in path). -- `incrementOne` — guarded counter mutation (`field = field + delta` per - `increment` entry plus any absolute `set` values), returning the updated row - or `null` when the guard matches nothing. - -Both are find-then-write mirrors of the existing `delete` / `update` methods -(ObjectQL exposes no native atomic primitive) and honour the same core/plugin -field-name bridging. - -better-auth 1.7 also extends its `jwks` model with two new optional columns, -`alg` (signing algorithm, e.g. `EdDSA`) and `crv` (curve, e.g. `Ed25519`), and -writes them when minting signing keys. The `sys_jwks` platform object gains the -matching fields — without them every JWKS write failed (`table sys_jwks has no -column named alg`), 500ing token signing and breaking session validation -(sign-in succeeded but every authenticated request 401'd). diff --git a/.changeset/predicate-guard-caller-scope-and-preimage-dedup.md b/.changeset/predicate-guard-caller-scope-and-preimage-dedup.md deleted file mode 100644 index c0f134e80f..0000000000 --- a/.changeset/predicate-guard-caller-scope-and-preimage-dedup.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -'@objectstack/plugin-security': patch ---- - -fix(security): scope the bulk-write predicate guard to the caller's own filter, and dedupe pre-image reads (#3018 review follow-ups) - -Two hardening follow-ups from the #3018 adversarial review. - -**Predicate guard is now middleware-order-independent for writes.** #2982 made bulk -`update`/`delete` carry an `opCtx.ast`, which brought them under the step-2.9 -anti-oracle predicate guard for the first time. That guard is documented to run -against the *caller's own* predicate — RLS / sharing filters legitimately reference -fields the caller cannot read (e.g. `owner_id`). But for a bulk write it inspected -`opCtx.ast.where`, which a sibling middleware (`plugin-sharing`) may have already had -an `owner_id` owner-match composed into — and the two middlewares' registration order -is not contractually guaranteed. On an object whose `owner_id` is FLS-hidden, that -could 403 a legitimate bulk write purely because the injected filter named the field. -The guard now inspects `opCtx.options.where` (the caller's untouched predicate) for -`update`/`delete`, so it can never mistake an injected owner/RLS filter for a caller -probe, independent of middleware order. Reads are unchanged (the read seed is the -caller's query verbatim and the guard runs before this middleware's own injection). - -**Pre-image reads deduplicated.** The by-id "read the target row" pattern was inlined -at ~5 gates with slightly divergent shapes; a single `readRowById` helper (fail-closed: -missing engine / null id / thrown read → `null`, which always denies) now backs the -provenance gates, and a memoized `getCallerPreImage` collapses the owner-anchor echo -check (3.5) and the RLS `check` post-image (3.6) — which read the identical -`(object, id, caller-context)` row — into one read per operation. No behavior change; -the read shape can no longer drift across sites. diff --git a/.changeset/protocol-2926-unknown-dollar-params.md b/.changeset/protocol-2926-unknown-dollar-params.md deleted file mode 100644 index 4f21ae067c..0000000000 --- a/.changeset/protocol-2926-unknown-dollar-params.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@objectstack/metadata-protocol': patch ---- - -fix(metadata-protocol): findData now rejects unknown `$`-prefixed query parameters with 400 `UNSUPPORTED_QUERY_PARAM` instead of silently treating them as implicit field-equality filters that match zero rows (#2926 ⑩). A `$`-prefixed key can never be a field name, so this is loud-failure only for the unsupported-alias class; bare-key implicit equality filtering is unchanged. The error message lists the supported aliases ($top, $skip, $orderby, $select, $count, $search, $searchFields, $filter, $expand). diff --git a/.changeset/public-form-server-managed-anchors.md b/.changeset/public-form-server-managed-anchors.md deleted file mode 100644 index 44af3d122a..0000000000 --- a/.changeset/public-form-server-managed-anchors.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -'@objectstack/plugin-security': patch -'@objectstack/rest': patch -'@objectstack/spec': patch ---- - -fix(security): public-form submissions can no longer forge server-managed anchors (#3022) - -The anonymous public-form surface (ADR-0056 Option A, `POST /forms/:slug/submit`) -is authorized by the declaration-derived `publicFormGrant`, which short-circuits -the security middleware BEFORE every write gate (CRUD, FLS, the owner anchor -guard, the tenant CHECK). The only field-side defense was the route's -declared-field allow-list — and a FormView with zero declared section fields -fell back to merging the raw body wholesale, so an unauthenticated visitor -could `POST owner_id=` (or `organization_id`, audit columns, `id`) and -attach the record to another user or tenant — the #3004 insert-forge, with no -credentials at all. - -Server-managed anchors are now enforced on this surface at BOTH layers, from a -single shared definition (`PUBLIC_FORM_SERVER_MANAGED_FIELDS`, new in -`@objectstack/spec/security`): - -- **Data layer (authoritative)** — the `publicFormGrant` branch in - `@objectstack/plugin-security` strips `id` / `owner_id` / `organization_id` / - `tenant_id` / audit columns / soft-delete state / `__search` from every row - of a granted insert (batch included) before admitting the write, so the - boundary holds no matter what any route lets through. Ownership stays NULL - for object hooks / the first-admin bootstrap to assign, as for other - anonymous-seeded rows. -- **Route layer** — the submit allow-list excludes the same set - unconditionally: an explicitly declared `owner_id` section field no longer - passes, and the zero-declared-sections fallback keeps its documented - all-fields behavior for business columns while refusing the managed set. - The resolve route (`GET /forms/:slug`) drops the managed fields from the - rendered sections and the embedded object schema so a form never collects a - value the submit refuses, and `GET /forms/:slug/lookup/:field` refuses a - `publicPicker` declared on a managed anchor (which would have opened - anonymous `sys_user` search through `owner_id`). - -Authenticated writes are unaffected — this is the anonymous-surface rule only; -`owner_id` transfer semantics for signed-in callers stay governed by the -transfer grant (#3004 / PR #3018). diff --git a/.changeset/remove-tenancy-strategy-crosstenant.md b/.changeset/remove-tenancy-strategy-crosstenant.md deleted file mode 100644 index 1551283553..0000000000 --- a/.changeset/remove-tenancy-strategy-crosstenant.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -'@objectstack/spec': minor ---- - -feat(spec)!: remove `tenancy.strategy` + `tenancy.crossTenantAccess`; tenancy block is now strict (#2763) - -> ⚠️ RELEASE NOTE — breaking by strict semver, shipped as `minor` per the -> launch-window policy (owner decision on PR #2962): the fields had zero -> consumers, behavior is unchanged, and the parse error carries the -> migration. Fold into the v15 release page's "What's new in 15.x" section -> when versioning. - -BREAKING CHANGE: `TenancyConfigSchema` drops its two zero-consumer fields, and -the `tenancy` block is now `.strict()` — an unknown key is a loud parse error -with tombstone guidance instead of a silent zod strip (#1535; precedent -ADR-0056 D8 "compliance-grade config must never merely look live", ADR-0049 -enforce-or-remove). - -The platform has exactly two tenancy modes, and neither needs object-level -strategy config: database-per-tenant isolation is an environment/deployment -choice (each environment carries its own database URL), and shared-database -row isolation is `tenancy.enabled` + `tenancy.tenantField` (both stay, both -live: sql-driver row scoping, security-plugin org scoping). Cross-tenant -visibility is governed by sharing rules / OWD (ADR-0056), -`externalSharingModel` (ADR-0090 D11), and the object access posture — never -by a blanket boolean. - -Migration (delete the keys; nothing read them, so behavior is unchanged): - -- FROM `tenancy: { enabled: false, strategy: 'shared' }` → TO `tenancy: { enabled: false }` -- FROM `tenancy: { enabled: true, strategy: '...', tenantField: 'x', crossTenantAccess: false }` → TO `tenancy: { enabled: true, tenantField: 'x' }` -- Wanted per-tenant databases? Deploy per environment (EnvironmentKernelFactory) — not object metadata. -- Wanted cross-tenant visibility? Use sharing rules / OWD or `externalSharingModel`. - -The compile-time authorWarn for these fields (#2750) and their liveness-ledger -entries are retired with the removal; the schema itself now carries the -prescription. diff --git a/.changeset/rest-2926-status-passthrough.md b/.changeset/rest-2926-status-passthrough.md deleted file mode 100644 index 9b396f527b..0000000000 --- a/.changeset/rest-2926-status-passthrough.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@objectstack/rest': patch ---- - -fix(rest): mapDataError now honors an explicit 4xx `error.status`/`error.code` carried by domain errors (#2926 ⑦). Record-scope authorization denials from plugin-sharing (status 403, code FORBIDDEN) previously degraded to a bare 400 with no machine-readable code because the generic data routes bypass sendError's status passthrough. Structured 409 envelopes (CONCURRENT_UPDATE, DELETE_RESTRICTED) keep their dedicated branches; 5xx statuses still go through the message-sanitizing heuristics. diff --git a/.changeset/retire-objectos-layer-naming.md b/.changeset/retire-objectos-layer-naming.md deleted file mode 100644 index a3f53f6203..0000000000 --- a/.changeset/retire-objectos-layer-naming.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@objectstack/spec": patch ---- - -Retire "ObjectOS" as the control-layer name in the published agent prompts (`prompts/`): the open control layer is now called the **Kernel**; **ObjectOS** exclusively names the commercial runtime environment. Layer vocabulary is now ObjectQL (data) / Kernel (control) / ObjectUI (view). Prompt text only — no schema changes. diff --git a/.changeset/retire-objectos-spec-identifiers.md b/.changeset/retire-objectos-spec-identifiers.md deleted file mode 100644 index ba882a6d07..0000000000 --- a/.changeset/retire-objectos-spec-identifiers.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -"@objectstack/spec": minor ---- - -Retire the "ObjectOS" layer name from the spec's public surface — the control layer is the **Kernel**; ObjectOS now exclusively names the commercial runtime environment. - -Renames (deprecated aliases kept for one release, so existing imports keep compiling): - -- `ObjectOSCapabilitiesSchema` → `KernelCapabilitiesSchema` -- `ObjectOSCapabilities` (type) → `KernelCapabilities` -- `ObjectOSKernel` (interface) → `IKernel` (`PluginContext.os` is now typed as `IKernel`) - -Migration: replace the old names with the new ones — a find/replace of the three identifiers above is sufficient; runtime behavior, schema shapes, and JSON output are unchanged. TSDoc and generated reference docs now say "the ObjectStack runtime" / "Kernel" instead of "ObjectOS" (product mentions like ObjectOS Cloud in the Cloud protocol domain are unchanged). diff --git a/.changeset/scaffold-ships-docker.md b/.changeset/scaffold-ships-docker.md deleted file mode 100644 index 6a8be4146c..0000000000 --- a/.changeset/scaffold-ships-docker.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"create-objectstack": minor ---- - -Scaffolded projects are now container-ready out of the box: the `blank` template ships a `Dockerfile` (two-stage build onto the official `ghcr.io/objectstack-ai/objectstack` runtime image), a `docker-compose.yml` (app + Postgres single-host stack), and a `.dockerignore`, plus a Deploy section in the project README. `docker build -t my-app .` works immediately after `npm create objectstack`. diff --git a/.changeset/security-2849-action-ai-exposure-gate.md b/.changeset/security-2849-action-ai-exposure-gate.md deleted file mode 100644 index bfa75dd842..0000000000 --- a/.changeset/security-2849-action-ai-exposure-gate.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -'@objectstack/runtime': patch -'@objectstack/mcp': patch ---- - -fix(security): enforce the `ai.exposed` opt-in on the MCP action surface (#2849) - -Business-action bodies execute as trusted code: their engine facade carries no -`ExecutionContext`, so a body's internal reads/writes bypass RLS/FLS/CRUD and -tenant scoping — the caller's permissions and an agent's ADR-0090 D10 data -ceiling do NOT bound what an invoked action does. The MCP `run_action` bridge -nevertheless allowed invoking ANY headless action, ignoring the spec's -`ai.exposed` governance gate (ADR-0011) entirely. - -The MCP bridge now fail-closes on `ai.exposed`: `list_actions` only enumerates -— and `run_action` only dispatches — actions the app author explicitly opted -into the AI surface with `ai: { exposed: true, description }`. Flow-type -actions additionally receive the caller's identity (`userId` / `positions` / -`permissions` / `tenantId`) as a proper `AutomationContext` (replacing the -former `triggerData` envelope the engine never read), so a `runAs: 'user'` -flow enforces RLS as the invoker instead of running unscoped (ADR-0049). -Trusted body dispatches are now audit-logged on both the MCP and REST action -paths, and the MCP tool/README/docs wording no longer claims action bodies run -under the caller's RLS. - -Migration: actions that should stay invokable by AI agents through MCP must -declare `ai: { exposed: true, description: '…' }` (≥40-char description). All -other invocation surfaces (UI, REST `/actions/...`) are unchanged. diff --git a/.changeset/security-2850-expand-rls-fls.md b/.changeset/security-2850-expand-rls-fls.md deleted file mode 100644 index 952b988d63..0000000000 --- a/.changeset/security-2850-expand-rls-fls.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -'@objectstack/objectql': patch -'@objectstack/plugin-security': patch ---- - -fix(security): enforce referenced-object RLS/FLS on $expand (#2850) - -`expandRelatedRecords` resolved lookup/master_detail/user references via the -driver directly, so the referenced object's row- and field-level security never -ran — any API/session caller who could read a base row could `?expand=` a -foreign key and receive RLS-hidden rows and FLS-masked fields (tenant isolation -was the only surviving boundary). - -The expand batch now routes through the engine's own `find`, so the security -middleware applies the referenced object's RLS + FLS to the `id $in [...]` batch -(one query per level, no N+1). The sub-read carries a server-set `__expandRead` -marker: the middleware waives only the object-level CRUD / requiredPermissions -gate for PUBLIC referenced objects (already broadly readable — avoids -over-blocking common status/owner lookups), while PRIVATE referenced objects -keep the full gate. Covers the list and single-record REST/protocol surfaces. diff --git a/.changeset/security-2909-capability-scope.md b/.changeset/security-2909-capability-scope.md deleted file mode 100644 index 2ff3842b3f..0000000000 --- a/.changeset/security-2909-capability-scope.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@objectstack/plugin-security': patch ---- - -fix(plugin-security): stop clobbering admin-edited capability `scope` on boot (#2909 T3). `scope` is an admin-editable classification select on sys_capability, but the curated seeder refreshed it on every boot alongside label/description — silently reverting admin reclassifications. It is now seed-once: written on insert, never refreshed (a curated scope change in a new platform version requires a data migration; recorded in the ADR-0094 addendum). diff --git a/.changeset/security-2926-position-gate-vocab.md b/.changeset/security-2926-position-gate-vocab.md deleted file mode 100644 index c929138652..0000000000 --- a/.changeset/security-2926-position-gate-vocab.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@objectstack/plugin-security': patch ---- - -fix(plugin-security): re-arm the sys_position system-row write gate after the A4 managed_by rename (#2926 ①). The gate's provenance map still keyed on the legacy `system`/`config` values while rows are now stamped (and boot-normalized to) `platform`/`package`, so platform/package-managed positions — including the `everyone`/`guest` audience anchors — could be physically deleted through the data API once their bindings were removed. The map now guards both the canonical and legacy vocabularies, and the misleading "no runtime path branches on legacy values" safety notes were corrected. diff --git a/.changeset/security-class-2980-2981-2982.md b/.changeset/security-class-2980-2981-2982.md deleted file mode 100644 index 86571e4a78..0000000000 --- a/.changeset/security-class-2980-2981-2982.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -'@objectstack/plugin-reports': patch -'@objectstack/service-knowledge': patch -'@objectstack/plugin-sharing': patch ---- - -fix(security): close three execution-surface authz holes surfaced by the #2849 class sweep (#2980, #2981, #2982) - -Three independent, confirmed-exploitable defects where an execution surface -ignored the caller's identity or fell open on a missing one. Each is fixed at -its own enforcement point; none change behaviour for correctly-scoped callers. - -- **#2980 — reports IDOR + scheduled-report RLS bypass.** `ReportService` - discarded the caller's context and read/wrote `sys_saved_report` with a system - context, so any authenticated user could read, delete, or overwrite any saved - report by id (cross-owner / cross-tenant), and `listReports` enumerated all - owners. `getReport`/`deleteReport`/`saveReport`/`listReports` are now - owner-scoped (system read of the protection-locked metadata object, but - authorization enforced by owner match); create/overwrite can no longer spoof - ownership. Scheduled dispatch no longer runs `isSystem` (which emailed the - target object's entire table past the owner's RLS): it resolves the owner to a - real RLS-bearing context via a new `resolveOwnerContext` seam and **fails - closed** (skips + marks the schedule failed) when the owner can't be resolved, - rather than running elevated. Wiring that resolver is the reports-surface - consumer of ADR-0073's user-less identity resolution. - -- **#2981 — knowledge/RAG retrieval fall-open.** `applyPermissionFilter` returned - every hit when the context was missing *or* system. A missing identity is no - longer treated as a grant: object-backed hits fail closed (dropped, keeping - ACL-less file/http hits), and only an **explicit** system context passes - through. Closes the agent path where an omitted `ToolExecutionContext.actor` - yielded unfiltered semantic search over the whole corpus. - -- **#2982 — bulk-write OWD gap.** `update({multi:true})` / `deleteMany` had no - single id to `canEdit`-gate, so owner scoping was skipped on private (and - public_read) objects. A new `SharingService.buildWriteFilter` (the edit-set - analogue of `buildReadFilter`) is AND-ed into the write AST for multi writes, - constraining them to rows the caller may edit — including the on-behalf-of - delegator intersection. - -Tracked as the motivating evidence of ADR-0096 (execution-surface identity -admission); the mechanism that would prevent the class structurally is separate. diff --git a/.changeset/security-everyone-autobind-ordering.md b/.changeset/security-everyone-autobind-ordering.md deleted file mode 100644 index af1004c4e6..0000000000 --- a/.changeset/security-everyone-autobind-ordering.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@objectstack/plugin-security': patch ---- - -fix(plugin-security): bind the fallback permission set to the `everyone` anchor AFTER the anchor is seeded. The baseline auto-bind (ADR-0090 D5) ran earlier in `runBootstrap` than `bootstrapBuiltinRoles`, which creates the `everyone` position — so the `everyone` lookup returned nothing and the app's `isDefault` set was never bound, leaving a fresh deploy's `everyone` empty (personas silently degraded) and a redundant `sys_audience_binding_suggestion` filed for the same set. The auto-bind now runs after `bootstrapBuiltinRoles` and before `syncAudienceBindingSuggestions`, so the documented app-level auto-bind actually happens and the suggestion sync correctly skips the already-bound set. diff --git a/.changeset/sharing-2909-seed-not-clobber.md b/.changeset/sharing-2909-seed-not-clobber.md deleted file mode 100644 index 2aa48edfca..0000000000 --- a/.changeset/sharing-2909-seed-not-clobber.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@objectstack/plugin-sharing': minor -'@objectstack/spec': patch ---- - -feat(plugin-sharing): sys_sharing_rule provenance + seed-not-clobber (#2909 P0/T1). The object gains readonly `managed_by` (unified A4 tri-state platform/package/admin) and `customized` columns; declared rules seed with `managed_by: 'package'`. defineRule in seed mode adopts pristine/legacy rows (package upgrades stay deliverable) but never overwrites admin-authored or customized rows — an admin's `active: false` on an over-sharing rule now survives redeploys instead of being resurrected at boot. A beforeUpdate hook stamps `customized` on any non-system edit of a seeded rule. Deliberately NO write gate: sharing rules remain a first-class admin authoring surface (ADR-0094 addendum tradeoff). diff --git a/.changeset/sharing-2926-boot-backfill.md b/.changeset/sharing-2926-boot-backfill.md deleted file mode 100644 index 90e8c6beb9..0000000000 --- a/.changeset/sharing-2926-boot-backfill.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@objectstack/plugin-sharing': patch ---- - -fix(plugin-sharing): reconcile every active sharing rule once at boot (#2926 ③). Rule grants are materialized by write hooks, which deliberately skip `isSystem` writes — so seed-loader records never produced `sys_record_share` rows and demo data shipping with matching sharing rules was broken out of the box until each record was touched at runtime. The boot backfill runs on `kernel:listening` — the phase the kernel fires only after every `kernel:ready` handler has settled, including the AppPlugin seed loader — so the reconcile sees the seeded rows rather than racing them. It is idempotent (diff-based reconcile) and best-effort per rule so one broken rule cannot block startup. diff --git a/.changeset/spec-2926-isdefault-dualtrack.md b/.changeset/spec-2926-isdefault-dualtrack.md deleted file mode 100644 index 6c1dc01419..0000000000 --- a/.changeset/spec-2926-isdefault-dualtrack.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@objectstack/spec': patch ---- - -docs(spec): rewrite the `isDefault` permission-set docs to describe the actual dual-track behavior (#2926 ②): app-level `isDefault` sets are resolved as the SecurityPlugin's fallback and idempotently auto-bound to the `everyone` anchor at boot (guarded by the high-privilege-bits check), while package-level sets are never auto-bound and instead materialize a `sys_audience_binding_suggestion` an admin confirms. The previous "never auto-bound" wording contradicted the shipped app-level track. diff --git a/.changeset/upload-session-multipart-abort-guard.md b/.changeset/upload-session-multipart-abort-guard.md deleted file mode 100644 index 8b925a1132..0000000000 --- a/.changeset/upload-session-multipart-abort-guard.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -'@objectstack/service-storage': patch ---- - -fix(storage): abort the backend multipart upload when reaping an abandoned sys_upload_session (#2970) - -The `sys_upload_session` lifecycle (added in #2984) reaps abandoned/terminal -chunked-upload session ROWS, but not the underlying backend multipart upload — -on S3 an initiated-but-not-completed multipart keeps its already-uploaded parts -billable and invisible to normal listing until an explicit -`AbortMultipartUpload`, so reaping only the row stranded them (with -`backend_upload_id`, the sole pointer, gone). - -`createUploadSessionReapGuard` registers a `LifecycleReapGuard` on -`sys_upload_session` that aborts the backend multipart before the row is -deleted: it skips `completed` sessions (their multipart already became a real -object — an abort would `NoSuchUpload`-error), re-seeds the S3 adapter's -`uploadId → key` map from the row (a cold sweep lacks the live in-process map), -and vetoes (keeps the row for retry) on abort failure so the pointer survives. -The local adapter's parts directory is removed the same way. diff --git a/.changeset/view-liveness-enrollment.md b/.changeset/view-liveness-enrollment.md deleted file mode 100644 index a10a00be97..0000000000 --- a/.changeset/view-liveness-enrollment.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -'@objectstack/spec': minor -'@objectstack/cli': minor ---- - -feat(spec,cli): enroll `view` in the liveness ledger (#2998 Track B) - -`view` joins the `GOVERNED` set of the spec property-liveness gate — the -rollout gap that let the objectui#1763/#2545 class of renderer/spec key drift -survive undetected. New `packages/spec/liveness/view.json` classifies all 83 -walkable properties (75 ledger entries + framework overlay fields): the `list` -and `form` containers are drilled one level via `children`. - -Seeded from the 2026-06 viewschema audit and **re-verified against objectui -HEAD** — four audit-era DEAD findings had since gone live and are classified -from current reads (`form.submitBehavior`, `list.sharing.lockedBy`, list-path -`ViewData` providers, and the post-ADR-0021 `list.chart` dataset shape — the -audit's "chart renderers never migrated" headline is resolved). Final tally: -68 live, 2 experimental (`form.buttons`/`form.defaults`, #2998 Track A -awaiting objectui#2545), 5 dead (`list.responsive`, `list.performance`, -`form.data`, `form.defaultSort`, `form.aria`). All misleading dead props -carry `authorWarn` + `authorHint`. - -The CLI's compile-time liveness lint gains `view` coverage -(`TYPE_COLLECTIONS` + view containers labelled by `object`), so authoring a -dead prop — e.g. a spec-valid `chart` list view that renders empty — now warns -at `os build` with a corrective hint. diff --git a/examples/app-crm/CHANGELOG.md b/examples/app-crm/CHANGELOG.md index d752685fa3..cf60dc1ca4 100644 --- a/examples/app-crm/CHANGELOG.md +++ b/examples/app-crm/CHANGELOG.md @@ -1,5 +1,44 @@ # @objectstack/example-crm +## 4.0.88 + +### Patch Changes + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [541b96a] +- Updated dependencies [4c46ee0] +- Updated dependencies [99755b5] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [3dc9fce] +- Updated dependencies [fae5dd0] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [fdfe1d9] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/runtime@15.1.0 + ## 4.0.87 ### Patch Changes diff --git a/examples/app-crm/package.json b/examples/app-crm/package.json index 581dd23289..6c02f5105c 100644 --- a/examples/app-crm/package.json +++ b/examples/app-crm/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/example-crm", - "version": "4.0.87", + "version": "4.0.88", "description": "Minimal CRM example — a smoke-test workspace that exercises the metadata loading pipeline (objects → views → app → dashboard → hook → flow → seed). For a full-featured enterprise CRM see https://github.com/objectstack-ai/hotcrm.", "license": "Apache-2.0", "private": true, diff --git a/examples/app-showcase/CHANGELOG.md b/examples/app-showcase/CHANGELOG.md index fe2d5c6c5c..b3816b2aa4 100644 --- a/examples/app-showcase/CHANGELOG.md +++ b/examples/app-showcase/CHANGELOG.md @@ -1,5 +1,52 @@ # @objectstack/example-showcase +## 0.3.10 + +### Patch Changes + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [e97885b] +- Updated dependencies [541b96a] +- Updated dependencies [4c46ee0] +- Updated dependencies [99755b5] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [3dc9fce] +- Updated dependencies [fae5dd0] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [fdfe1d9] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/runtime@15.1.0 + - @objectstack/connector-rest@15.1.0 + - @objectstack/connector-openapi@15.1.0 + - @objectstack/connector-mcp@15.1.0 + - @objectstack/cloud-connection@15.1.0 + - @objectstack/connector-slack@15.1.0 + - @objectstack/driver-sql@15.1.0 + - @objectstack/service-datasource@15.1.0 + ## 0.3.9 ### Patch Changes diff --git a/examples/app-showcase/package.json b/examples/app-showcase/package.json index e92765c1ac..6dc02aff6b 100644 --- a/examples/app-showcase/package.json +++ b/examples/app-showcase/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/example-showcase", - "version": "0.3.9", + "version": "0.3.10", "description": "Kitchen-sink showcase workspace — exercises every metadata type, every view type, every chart type, and the major end-to-end capability chains (security, automation, analytics). Built for demonstration, debugging, and coverage-driven verification.", "license": "Apache-2.0", "private": true, diff --git a/examples/app-todo/CHANGELOG.md b/examples/app-todo/CHANGELOG.md index 9c469b1779..3d8f865ad8 100644 --- a/examples/app-todo/CHANGELOG.md +++ b/examples/app-todo/CHANGELOG.md @@ -1,5 +1,59 @@ # @objectstack/example-todo +## 4.0.88 + +### Patch Changes + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [86c0aea] +- Updated dependencies [541b96a] +- Updated dependencies [4c46ee0] +- Updated dependencies [99755b5] +- Updated dependencies [c11e24b] +- Updated dependencies [e0b049a] +- Updated dependencies [e9a2885] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [3dc9fce] +- Updated dependencies [fae5dd0] +- Updated dependencies [23925e9] +- Updated dependencies [6613ad0] +- Updated dependencies [a16972b] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [1c58abd] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [fdfe1d9] +- Updated dependencies [8b27dd7] +- Updated dependencies [e07645c] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/runtime@15.1.0 + - @objectstack/objectql@15.1.0 + - @objectstack/mcp@15.1.0 + - @objectstack/service-knowledge@15.1.0 + - @objectstack/client@15.1.0 + - @objectstack/metadata@15.1.0 + - @objectstack/driver-sqlite-wasm@15.1.0 + - @objectstack/knowledge-memory@15.1.0 + ## 4.0.87 ### Patch Changes diff --git a/examples/app-todo/package.json b/examples/app-todo/package.json index cc3f09bad7..f4a5e4552c 100644 --- a/examples/app-todo/package.json +++ b/examples/app-todo/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/example-todo", - "version": "4.0.87", + "version": "4.0.88", "description": "Example Todo App using ObjectStack Protocol", "license": "Apache-2.0", "private": true, diff --git a/examples/embed-objectql/CHANGELOG.md b/examples/embed-objectql/CHANGELOG.md index 987edbf401..c148189759 100644 --- a/examples/embed-objectql/CHANGELOG.md +++ b/examples/embed-objectql/CHANGELOG.md @@ -1,5 +1,46 @@ # @objectstack/example-embed-objectql +## 0.0.28 + +### Patch Changes + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [86c0aea] +- Updated dependencies [c11e24b] +- Updated dependencies [e0b049a] +- Updated dependencies [e9a2885] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [6613ad0] +- Updated dependencies [a16972b] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [1c58abd] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [8b27dd7] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/objectql@15.1.0 + - @objectstack/driver-memory@15.1.0 + ## 0.0.27 ### Patch Changes diff --git a/examples/embed-objectql/package.json b/examples/embed-objectql/package.json index 519c0ebbbe..bdb53c3f6a 100644 --- a/examples/embed-objectql/package.json +++ b/examples/embed-objectql/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/example-embed-objectql", - "version": "0.0.27", + "version": "0.0.28", "private": true, "description": "Embed the ObjectQL engine as a plain library via @objectstack/objectql/core — no kernel, no plugins, no metadata protocol (ADR-0076).", "type": "module", diff --git a/packages/adapters/hono/CHANGELOG.md b/packages/adapters/hono/CHANGELOG.md index 3022cd0762..47717721ec 100644 --- a/packages/adapters/hono/CHANGELOG.md +++ b/packages/adapters/hono/CHANGELOG.md @@ -1,5 +1,34 @@ # @objectstack/hono +## 15.1.0 + +### Patch Changes + +- ef50372: CORS default `allowHeaders` now includes `If-Match`. The REST record update + accepts the OCC token as an `If-Match` header (objectui's record-level inline + edit sends it on every save), but the preflight allow-list omitted it — so on + any split-origin deployment (console dev server against a backend on another + origin) the browser failed the preflight and every inline-edit save died with + "Failed to fetch". Found live while dogfooding objectui#2572; same + split-origin failure class as the #2548 Bearer fixes. Explicit user-supplied + `allowHeaders` still win unchanged. +- Updated dependencies [7f68068] +- Updated dependencies [8fc1208] +- Updated dependencies [541b96a] +- Updated dependencies [4c46ee0] +- Updated dependencies [99755b5] +- Updated dependencies [c11e24b] +- Updated dependencies [ef50372] +- Updated dependencies [59cd765] +- Updated dependencies [3dc9fce] +- Updated dependencies [fae5dd0] +- Updated dependencies [23925e9] +- Updated dependencies [1c58abd] +- Updated dependencies [fdfe1d9] + - @objectstack/runtime@15.1.0 + - @objectstack/plugin-hono-server@15.1.0 + - @objectstack/types@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/adapters/hono/package.json b/packages/adapters/hono/package.json index 39a5f52008..5cb02d7534 100644 --- a/packages/adapters/hono/package.json +++ b/packages/adapters/hono/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/hono", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/apps/account/CHANGELOG.md b/packages/apps/account/CHANGELOG.md index 4bdca27da2..16f8113c36 100644 --- a/packages/apps/account/CHANGELOG.md +++ b/packages/apps/account/CHANGELOG.md @@ -1,5 +1,42 @@ # @objectstack/account +## 15.1.0 + +### Patch Changes + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [94b8e44] +- Updated dependencies [86c0aea] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [eb89a8c] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [aead168] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/platform-objects@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/apps/account/package.json b/packages/apps/account/package.json index c30643ab39..5ed14bf90d 100644 --- a/packages/apps/account/package.json +++ b/packages/apps/account/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/account", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "ObjectStack Account — the end-user account/self-service console app, packaged as its own ObjectStack app package (ADR-0048: one app per package).", "main": "dist/index.js", diff --git a/packages/apps/setup/CHANGELOG.md b/packages/apps/setup/CHANGELOG.md index f83dc5e33b..65968fbdd8 100644 --- a/packages/apps/setup/CHANGELOG.md +++ b/packages/apps/setup/CHANGELOG.md @@ -1,5 +1,42 @@ # @objectstack/setup +## 15.1.0 + +### Patch Changes + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [94b8e44] +- Updated dependencies [86c0aea] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [eb89a8c] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [aead168] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/platform-objects@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/apps/setup/package.json b/packages/apps/setup/package.json index e3ae76fa79..86dd942316 100644 --- a/packages/apps/setup/package.json +++ b/packages/apps/setup/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/setup", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "ObjectStack Setup — the platform administration app, packaged as its own ObjectStack app package (ADR-0048: one app per package).", "main": "dist/index.js", diff --git a/packages/apps/studio/CHANGELOG.md b/packages/apps/studio/CHANGELOG.md index e6765feb5f..399d936fad 100644 --- a/packages/apps/studio/CHANGELOG.md +++ b/packages/apps/studio/CHANGELOG.md @@ -1,5 +1,42 @@ # @objectstack/studio +## 15.1.0 + +### Patch Changes + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [94b8e44] +- Updated dependencies [86c0aea] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [eb89a8c] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [aead168] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/platform-objects@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/apps/studio/package.json b/packages/apps/studio/package.json index 003560d66d..27fc287231 100644 --- a/packages/apps/studio/package.json +++ b/packages/apps/studio/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/studio", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "ObjectStack Studio — the metadata builder app, packaged as its own ObjectStack app package (ADR-0048: one app per package).", "main": "dist/index.js", diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 7c864b9f5a..02754a3709 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,256 @@ # @objectstack/cli +## 15.1.0 + +### Minor Changes + +- 8fc1208: feat(protocol): complete ADR-0087 — load-seam handshake, chain backfill 12–15, release artifacts (#2643) + + Closes the remaining ADR-0087 gaps (see the ADR's as-built Addendum): + + - **P0 load seams (D1).** The protocol handshake now runs on the boot-time + durable-package rehydration path (`@objectstack/service-package` refuses an + incompatible `sys_packages` row with the structured `OS_PROTOCOL_INCOMPATIBLE` + diagnostic and keeps booting) and on `AppPlugin` for code-defined stacks + (fail-fast before the manifest is decomposed). `objectstack lint` gains + `protocol/missing-engines-range` (warning + fix-it) and the + `create-objectstack` blank template stamps `engines: { protocol: '^' }` + (re-stamped at version time by `scripts/sync-template-versions.mjs`) — the + two ends of the grandfathering ratchet. + - **Chain backfill (D2/D3).** `MetadataConversion.retiredFromLoadPath` + implements the load-window's second half (retired entries replay only via + `migrate meta` / fixture CI). Steps 12–15 land: the `api.requireAuth` flip + (semantic), the ADR-0090 wave (3 retired conversions + 5 semantic TODOs), the + `BookAudience` rename (retired conversion), and the ADR-0089 visibility + unification (`visibleOn`/`visibility` → `visibleWhen` as LIVE load-window + conversions) + the `.strict()` flip (semantic). The protocol-11 + `compactLayout` → `highlightFields` rename is backfilled as a retired step-11 + conversion. `migrate meta --from 10` now reaches protocol 15. + - **Release artifacts (D4).** `spec-changes.json` is generated from the + registries (`gen:spec-changes`, CI drift-checked), ships in the npm artifact + together with `api-surface.json`, and is attached to each `@objectstack/spec` + GitHub Release with `added[]`/`removed[]` filled from the api-surface diff + against the previously published release. The upgrade guide + (`docs/protocol-upgrade-guide.md`) is generated from the same registries and + CI drift-checked — a projection that cannot drift. + +- 10a570a: feat(connector-openapi): resolve `providerConfig.spec` from a package-relative file path (#3016, ADR-0096 follow-up) + + ADR-0096's canonical example authors an OpenAPI-backed instance as + `providerConfig: { spec: './billing-openapi.json' }`, but the landed `openapi` + provider factory only accepted an inline document object or an http(s) URL. + The spec union is now complete: **inline object | file path | remote URL**. + + - **`@objectstack/spec`.** `ConnectorProviderContext` gains an optional + host-injected `loadPackageFile(relativePath)` capability (pure type): reads a + UTF-8 file resolved against the declaring stack/package root, confined to + that root. `undefined` on hosts without a filesystem. + + - **`@objectstack/service-automation`.** New `packageRoot` plugin option (the + base for relative file refs; defaults to `process.cwd()`) and an exported + `createPackageFileLoader(packageRoot)` that implements the confinement + guard — absolute paths and `..`-escaping paths are rejected — with lazy + `node:fs`/`node:path` imports so non-Node hosts only fail if a file ref is + actually dereferenced. The materializer injects the capability into every + provider factory's context. Failures follow the existing reconcile policy: + **fatal at boot, entry skipped on reload**. + + - **`@objectstack/connector-openapi`.** A string `providerConfig.spec` that is + not an http(s) URL is now read via `ctx.loadPackageFile` and parsed as an + OpenAPI JSON document (clear errors for missing/unreadable files, unparseable + JSON, and hosts without package file access). + + - **`@objectstack/cli`.** `serve`/`dev` pass the project folder (the + `objectstack.config.ts` directory) as the automation service's `packageRoot`, + mirroring how the standalone sqlite default is anchored. + +- eb89a8c: feat(cli): `os i18n extract` now emits action param keys (`o.._actions..params..*`) so action-dialog forms are translatable (#3030) + + The console client already resolves param labels, help text, placeholders and + option labels from `o.._actions..params.*`, but the extractor + never walked `actions[].params`, so those keys were absent from generated + bundles and dialogs like Setup → Create User rendered raw English under any + locale. The extractor now emits: + + - inline params → `label` / `helpText` / `placeholder` / `options.`; + - field-backed params (`{ field: '…' }`) → only when they carry a literal + override (field translations already cover them at runtime); + - both object actions and top-level (global) actions. + + `@objectstack/platform-objects` regenerates its en/zh-CN/ja-JP/es-ES bundles + with the new keys filled (user admin actions, sys_jwks fields, page variable + forms). Re-running extract with `--merge` stays idempotent. + +- df26c91: feat(docker): official runtime image `ghcr.io/objectstack-ai/objectstack` + + ObjectStack now ships an official, versioned Docker runtime image instead of + only a copy-me example. The image packages Node 22 + a pinned + `@objectstack/cli` + `os start` (non-root `node` user, `/api/v1/health` + HEALTHCHECK, `OS_ARTIFACT_PATH` / `OS_PORT=8080` preset), published + multi-arch (amd64/arm64) on every release with tags mirroring the CLI + version (`X.Y.Z` / `X.Y` / `X` / `latest`). + + Deploying an app is now: + + ```dockerfile + FROM ghcr.io/objectstack-ai/objectstack: + COPY --chown=node:node dist/objectstack.json /srv/app/objectstack.json + ``` + + or, with no image build at all, `docker run` the official image with the + artifact mounted (or `OS_ARTIFACT_PATH` pointing at an `https://` URL). + `examples/docker` and the Self-Hosted Deployment docs now build on the + official image; the self-built runtime Dockerfile remains documented for + air-gapped registries. + +- 1c58abd: Generic pinyin search recall (#2486, ADR-0098): a locale-gated + `OS_SEARCH_PINYIN_ENABLED` switch (auto-on when the stack configures any + `zh-*` locale) provisions a hidden `__search` companion column for each + object's display/name field at compile time, the new + `@objectstack/plugin-pinyin-search` fills it with full pinyin + initials + ("张伟" → "zhangwei zw") on before-save (plus boot backfill and a + `rebuildSearchCompanion` reconcile entry), and `$search` ORs the column in at + query time — so lookup pickers, list quick-search and ⌘K transparently match + `zhangwei` / `zw` against CJK names. Purely additive: `resolveSearchFields`, + `searchableFields`, drivers and non-Chinese deployments are untouched; FLS + restricted / secret / PII fields never feed the companion. +- 2973f7f: feat(spec,cli): enroll `view` in the liveness ledger (#2998 Track B) + + `view` joins the `GOVERNED` set of the spec property-liveness gate — the + rollout gap that let the objectui#1763/#2545 class of renderer/spec key drift + survive undetected. New `packages/spec/liveness/view.json` classifies all 83 + walkable properties (75 ledger entries + framework overlay fields): the `list` + and `form` containers are drilled one level via `children`. + + Seeded from the 2026-06 viewschema audit and **re-verified against objectui + HEAD** — four audit-era DEAD findings had since gone live and are classified + from current reads (`form.submitBehavior`, `list.sharing.lockedBy`, list-path + `ViewData` providers, and the post-ADR-0021 `list.chart` dataset shape — the + audit's "chart renderers never migrated" headline is resolved). Final tally: + 68 live, 2 experimental (`form.buttons`/`form.defaults`, #2998 Track A + awaiting objectui#2545), 5 dead (`list.responsive`, `list.performance`, + `form.data`, `form.defaultSort`, `form.aria`). All misleading dead props + carry `authorWarn` + `authorHint`. + + The CLI's compile-time liveness lint gains `view` coverage + (`TYPE_COLLECTIONS` + view containers labelled by `object`), so authoring a + dead prop — e.g. a spec-valid `chart` list view that renders empty — now warns + at `os build` with a corrective hint. + +### Patch Changes + +- Updated dependencies [7f68068] +- Updated dependencies [55bc68d] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [93fd58e] +- Updated dependencies [4f8c2d1] +- Updated dependencies [0289d5a] +- Updated dependencies [94b8e44] +- Updated dependencies [d49deb5] +- Updated dependencies [86c0aea] +- Updated dependencies [541b96a] +- Updated dependencies [4c46ee0] +- Updated dependencies [99755b5] +- Updated dependencies [93bb7b4] +- Updated dependencies [c11e24b] +- Updated dependencies [e0b049a] +- Updated dependencies [e9a2885] +- Updated dependencies [55bc68d] +- Updated dependencies [330ceba] +- Updated dependencies [330ceba] +- Updated dependencies [5286745] +- Updated dependencies [ef50372] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [7bc9e79] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [eb89a8c] +- Updated dependencies [aeb2110] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [3dc9fce] +- Updated dependencies [fae5dd0] +- Updated dependencies [23925e9] +- Updated dependencies [7f9a795] +- Updated dependencies [6613ad0] +- Updated dependencies [a16972b] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [1c58abd] +- Updated dependencies [21f75ce] +- Updated dependencies [aead168] +- Updated dependencies [dee7feb] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [28ba0c7] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [fdfe1d9] +- Updated dependencies [8b27dd7] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [e07645c] +- Updated dependencies [464418e] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [7c800c6] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/runtime@15.1.0 + - @objectstack/objectql@15.1.0 + - @objectstack/rest@15.1.0 + - @objectstack/plugin-hono-server@15.1.0 + - @objectstack/lint@15.1.0 + - @objectstack/plugin-audit@15.1.0 + - @objectstack/service-package@15.1.0 + - @objectstack/service-automation@15.1.0 + - @objectstack/service-storage@15.1.0 + - @objectstack/platform-objects@15.1.0 + - @objectstack/verify@15.1.0 + - @objectstack/core@15.1.0 + - @objectstack/plugin-security@15.1.0 + - @objectstack/console@15.1.0 + - @objectstack/service-realtime@15.1.0 + - @objectstack/plugin-sharing@15.1.0 + - @objectstack/mcp@15.1.0 + - @objectstack/plugin-auth@15.1.0 + - @objectstack/plugin-pinyin-search@15.1.0 + - @objectstack/types@15.1.0 + - @objectstack/plugin-reports@15.1.0 + - @objectstack/account@15.1.0 + - @objectstack/setup@15.1.0 + - @objectstack/client@15.1.0 + - @objectstack/cloud-connection@15.1.0 + - @objectstack/formula@15.1.0 + - @objectstack/metadata@15.1.0 + - @objectstack/observability@15.1.0 + - @objectstack/driver-memory@15.1.0 + - @objectstack/driver-mongodb@15.1.0 + - @objectstack/driver-sql@15.1.0 + - @objectstack/driver-sqlite-wasm@15.1.0 + - @objectstack/plugin-approvals@15.1.0 + - @objectstack/plugin-email@15.1.0 + - @objectstack/plugin-webhooks@15.1.0 + - @objectstack/service-analytics@15.1.0 + - @objectstack/service-cache@15.1.0 + - @objectstack/service-datasource@15.1.0 + - @objectstack/service-job@15.1.0 + - @objectstack/service-messaging@15.1.0 + - @objectstack/service-queue@15.1.0 + - @objectstack/service-settings@15.1.0 + - @objectstack/service-sms@15.1.0 + - @objectstack/trigger-api@15.1.0 + - @objectstack/trigger-record-change@15.1.0 + - @objectstack/trigger-schedule@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index 17e30c0aef..107eba674c 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/cli", - "version": "15.0.0", + "version": "15.1.0", "description": "Command Line Interface for ObjectStack Protocol", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/client-react/CHANGELOG.md b/packages/client-react/CHANGELOG.md index 759133f5af..6f98c1ded1 100644 --- a/packages/client-react/CHANGELOG.md +++ b/packages/client-react/CHANGELOG.md @@ -1,5 +1,40 @@ # @objectstack/client-react +## 15.1.0 + +### Patch Changes + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [99755b5] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/core@15.1.0 + - @objectstack/client@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/client-react/package.json b/packages/client-react/package.json index e7cf246f35..8601c52394 100644 --- a/packages/client-react/package.json +++ b/packages/client-react/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/client-react", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "React hooks for ObjectStack Client SDK", "main": "dist/index.js", diff --git a/packages/client/CHANGELOG.md b/packages/client/CHANGELOG.md index 0d76bc7bba..0f74a7c01a 100644 --- a/packages/client/CHANGELOG.md +++ b/packages/client/CHANGELOG.md @@ -1,5 +1,39 @@ # @objectstack/client +## 15.1.0 + +### Patch Changes + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [99755b5] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/core@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/client/package.json b/packages/client/package.json index 9dd2c390ac..24c7dc9597 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/client", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "Official Client SDK for ObjectStack Protocol", "main": "dist/index.js", diff --git a/packages/cloud-connection/CHANGELOG.md b/packages/cloud-connection/CHANGELOG.md index f78e62773a..47629ab76e 100644 --- a/packages/cloud-connection/CHANGELOG.md +++ b/packages/cloud-connection/CHANGELOG.md @@ -1,5 +1,47 @@ # @objectstack/cloud-connection +## 15.1.0 + +### Patch Changes + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [541b96a] +- Updated dependencies [4c46ee0] +- Updated dependencies [99755b5] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [3dc9fce] +- Updated dependencies [fae5dd0] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [1c58abd] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [fdfe1d9] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/runtime@15.1.0 + - @objectstack/core@15.1.0 + - @objectstack/types@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/cloud-connection/package.json b/packages/cloud-connection/package.json index 45ba983a01..7a946082e3 100644 --- a/packages/cloud-connection/package.json +++ b/packages/cloud-connection/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/cloud-connection", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "Runtime-side client for an ObjectStack cloud control plane — marketplace browse proxy, install-local, device-code binding, org catalog and installed views, and the /api/v1/runtime/config discovery endpoint. Open mechanism (ADR-0008): the hub service, plan policy, and entitlements stay server-side.", "type": "module", diff --git a/packages/connectors/connector-mcp/CHANGELOG.md b/packages/connectors/connector-mcp/CHANGELOG.md index 4fe75d8310..9b55427400 100644 --- a/packages/connectors/connector-mcp/CHANGELOG.md +++ b/packages/connectors/connector-mcp/CHANGELOG.md @@ -1,5 +1,133 @@ # @objectstack/connector-mcp +## 15.1.0 + +### Minor Changes + +- 96a14d0: feat(connectors): ADR-0096 — provider-bound declarative connector instances materialized at boot (#2977) + + Declarative `connectors:` stack entries used to be **descriptor-only** (#2612): + registered as metadata but never dispatchable, the platform's one dead metadata + surface. An entry may now name a **`provider`** — an installed generic executor + (`openapi` / `mcp` / `rest`) — and the automation service **materializes** it + into a live, dispatchable connector at boot. AI can now wire an integration as + pure metadata and a flow `connector_action` calls it end-to-end. + + - **Schema (`@objectstack/spec`).** `ConnectorSchema` gains `provider`, + `providerConfig`, and `auth` (a `credentialRef`-based instance-auth shape — + `ConnectorInstanceAuthSchema` — that references credentials, never inlines + them); `authentication` now defaults to `{ type: 'none' }` so a provider-bound + instance need not author it (loosening — existing connectors are unaffected). + `DeclarativeConnectorEntrySchema` (used by `stack.zod.ts`) rejects inline + secrets, orphan `providerConfig`/`auth`, and authored `actions`/`triggers` on a + provider-bound entry. A new `integration/connector-provider.ts` defines the + provider-factory contract as pure types. + + - **Engine + boot (`@objectstack/service-automation`).** The engine adds a + connector-provider registry (`registerConnectorProvider`/`getConnectorProvider`) + and origin-tags registered connectors. At boot the service resolves each + provider-bound entry — looking up the factory, resolving `auth.credentialRef` + via a pluggable `CredentialResolver` (open-tier default: environment + variables), and registering the materialized connector. Boot **fails loudly** + for an unknown provider, invalid `providerConfig`, an unresolvable + `credentialRef`, or a name conflict with a plugin-registered connector (no + silent precedence). + + - **Providers (`connector-rest` / `connector-openapi` / `connector-mcp`).** Each + plugin registers a provider factory in `init()` reusing its existing + generator/adapter API. Plugin options are now **optional**: with none the + plugin contributes only its provider factory; with instance options it also + registers a hand-wired connector (back-compat). `connector-openapi` adds a + `ConnectorOpenApiPlugin`. + + Open tier: static auth (`none`/`api-key`/`basic`/`bearer`) with `credentialRef` + resolved from env vars. Managed vaulting, OAuth2 refresh, and per-tenant + connection lifecycle remain the enterprise tier (ADR-0015) — an enterprise host + injects a vault-backed `CredentialResolver` with no change to the materialization + path. + +- 4f8c2d1: feat(connectors): degrade + retry declarative instances whose upstream is unreachable (#3017) + + ADR-0097 kept every declarative-connector materialization failure fatal at + boot. That is right for configuration faults (unknown provider, invalid + `providerConfig`, unresolvable `credentialRef`, name conflict) but wrong for + _operational_ ones: a `provider: 'mcp'` instance must contact its MCP server + (`tools/list`) to materialize, and a transient network blip aborted the whole + app boot. + + - **spec**: a provider factory can now throw + `ConnectorUpstreamUnavailableError` (code `CONNECTOR_UPSTREAM_UNAVAILABLE`, + structural guard `isConnectorUpstreamUnavailable`) to mark a failure as + "upstream temporarily unreachable — degrade and retry" instead of fatal. + - **service-automation**: the reconcile degrades such an instance in both boot + and reload modes: it registers an action-less husk (`state: 'degraded'` + + `degradedReason` on the `GET /connectors` descriptor) so the instance is + visible instead of silently missing — or, on a changed-config + re-materialization, keeps the old connector serving. A `connector_action` + against a degraded instance fails with the reason and a "retries + automatically" pointer. Degraded instances retry on an exponential backoff + (5s → 5min, reset by config edits) and on every `metadata:reloaded` + reconcile; recovery swaps the husk for the live connector atomically. + Reconcile runs (boot / reload / retry timer) are now serialized. + - **connector-mcp**: the `mcp` provider classifies connect / `tools/list` + failures as upstream-unavailable; transport-shape validation stays a plain + (fatal) throw. + + Configuration faults remain loud boot failures — the carve-out is only for the + unavailable marker. + +- e97885b: feat(connector-mcp)!: policy-gate declarative stdio transports — default-deny + host allowlist (#3055) + + A declarative `provider: 'mcp'` entry with a stdio transport spawns a local + child process **from metadata** — which a runtime Studio publish can introduce. + Declarative stdio is now **denied by default**; hosts opt in deliberately: + + ```ts + new ConnectorMcpPlugin({ declarativeStdio: ["my-mcp-server"] }); // command allowlist + new ConnectorMcpPlugin({ declarativeStdio: true }); // allow any (full trust) + ``` + + Behavior change: a declarative stdio instance that materialized before now + fails as a **configuration fault** (fatal at boot / skipped on reload) with an + actionable opt-in message, and is never classified upstream-unavailable — a + security rejection must not be retried into existence. `http` transports and + **hand-wired** connectors (plugin instance options / `createMcpConnector`) are + unaffected. This is the security precondition for shipping `connector-mcp` in + default presets (#3056); see ADR-0097 §"Declarative stdio policy" and + ADR-0024 §4. + +### Patch Changes + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [99755b5] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/core@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/connectors/connector-mcp/package.json b/packages/connectors/connector-mcp/package.json index 686fe03af9..7b46854594 100644 --- a/packages/connectors/connector-mcp/package.json +++ b/packages/connectors/connector-mcp/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/connector-mcp", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "Model Context Protocol (MCP) connector for ObjectStack — a generic adapter that turns any MCP server's tools into a connector's actions on the automation engine's connector registry (ADR-0024).", "main": "dist/index.js", diff --git a/packages/connectors/connector-openapi/CHANGELOG.md b/packages/connectors/connector-openapi/CHANGELOG.md index 44df932c4c..2a18cd8711 100644 --- a/packages/connectors/connector-openapi/CHANGELOG.md +++ b/packages/connectors/connector-openapi/CHANGELOG.md @@ -1,5 +1,113 @@ # @objectstack/connector-openapi +## 15.1.0 + +### Minor Changes + +- 96a14d0: feat(connectors): ADR-0096 — provider-bound declarative connector instances materialized at boot (#2977) + + Declarative `connectors:` stack entries used to be **descriptor-only** (#2612): + registered as metadata but never dispatchable, the platform's one dead metadata + surface. An entry may now name a **`provider`** — an installed generic executor + (`openapi` / `mcp` / `rest`) — and the automation service **materializes** it + into a live, dispatchable connector at boot. AI can now wire an integration as + pure metadata and a flow `connector_action` calls it end-to-end. + + - **Schema (`@objectstack/spec`).** `ConnectorSchema` gains `provider`, + `providerConfig`, and `auth` (a `credentialRef`-based instance-auth shape — + `ConnectorInstanceAuthSchema` — that references credentials, never inlines + them); `authentication` now defaults to `{ type: 'none' }` so a provider-bound + instance need not author it (loosening — existing connectors are unaffected). + `DeclarativeConnectorEntrySchema` (used by `stack.zod.ts`) rejects inline + secrets, orphan `providerConfig`/`auth`, and authored `actions`/`triggers` on a + provider-bound entry. A new `integration/connector-provider.ts` defines the + provider-factory contract as pure types. + + - **Engine + boot (`@objectstack/service-automation`).** The engine adds a + connector-provider registry (`registerConnectorProvider`/`getConnectorProvider`) + and origin-tags registered connectors. At boot the service resolves each + provider-bound entry — looking up the factory, resolving `auth.credentialRef` + via a pluggable `CredentialResolver` (open-tier default: environment + variables), and registering the materialized connector. Boot **fails loudly** + for an unknown provider, invalid `providerConfig`, an unresolvable + `credentialRef`, or a name conflict with a plugin-registered connector (no + silent precedence). + + - **Providers (`connector-rest` / `connector-openapi` / `connector-mcp`).** Each + plugin registers a provider factory in `init()` reusing its existing + generator/adapter API. Plugin options are now **optional**: with none the + plugin contributes only its provider factory; with instance options it also + registers a hand-wired connector (back-compat). `connector-openapi` adds a + `ConnectorOpenApiPlugin`. + + Open tier: static auth (`none`/`api-key`/`basic`/`bearer`) with `credentialRef` + resolved from env vars. Managed vaulting, OAuth2 refresh, and per-tenant + connection lifecycle remain the enterprise tier (ADR-0015) — an enterprise host + injects a vault-backed `CredentialResolver` with no change to the materialization + path. + +- 10a570a: feat(connector-openapi): resolve `providerConfig.spec` from a package-relative file path (#3016, ADR-0096 follow-up) + + ADR-0096's canonical example authors an OpenAPI-backed instance as + `providerConfig: { spec: './billing-openapi.json' }`, but the landed `openapi` + provider factory only accepted an inline document object or an http(s) URL. + The spec union is now complete: **inline object | file path | remote URL**. + + - **`@objectstack/spec`.** `ConnectorProviderContext` gains an optional + host-injected `loadPackageFile(relativePath)` capability (pure type): reads a + UTF-8 file resolved against the declaring stack/package root, confined to + that root. `undefined` on hosts without a filesystem. + + - **`@objectstack/service-automation`.** New `packageRoot` plugin option (the + base for relative file refs; defaults to `process.cwd()`) and an exported + `createPackageFileLoader(packageRoot)` that implements the confinement + guard — absolute paths and `..`-escaping paths are rejected — with lazy + `node:fs`/`node:path` imports so non-Node hosts only fail if a file ref is + actually dereferenced. The materializer injects the capability into every + provider factory's context. Failures follow the existing reconcile policy: + **fatal at boot, entry skipped on reload**. + + - **`@objectstack/connector-openapi`.** A string `providerConfig.spec` that is + not an http(s) URL is now read via `ctx.loadPackageFile` and parsed as an + OpenAPI JSON document (clear errors for missing/unreadable files, unparseable + JSON, and hosts without package file access). + + - **`@objectstack/cli`.** `serve`/`dev` pass the project folder (the + `objectstack.config.ts` directory) as the automation service's `packageRoot`, + mirroring how the standalone sqlite default is anchored. + +### Patch Changes + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [99755b5] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/core@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/connectors/connector-openapi/package.json b/packages/connectors/connector-openapi/package.json index 38490006ff..0f27cab2d4 100644 --- a/packages/connectors/connector-openapi/package.json +++ b/packages/connectors/connector-openapi/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/connector-openapi", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "OpenAPI 3.x connector generator for ObjectStack — turns a declarative OpenAPI document into connector actions on the automation engine's registry, with a self-contained static-auth HTTP transport (ADR-0023).", "main": "dist/index.js", diff --git a/packages/connectors/connector-rest/CHANGELOG.md b/packages/connectors/connector-rest/CHANGELOG.md index f5dd04e4bb..3d6be341ae 100644 --- a/packages/connectors/connector-rest/CHANGELOG.md +++ b/packages/connectors/connector-rest/CHANGELOG.md @@ -1,5 +1,83 @@ # @objectstack/connector-rest +## 15.1.0 + +### Minor Changes + +- 96a14d0: feat(connectors): ADR-0096 — provider-bound declarative connector instances materialized at boot (#2977) + + Declarative `connectors:` stack entries used to be **descriptor-only** (#2612): + registered as metadata but never dispatchable, the platform's one dead metadata + surface. An entry may now name a **`provider`** — an installed generic executor + (`openapi` / `mcp` / `rest`) — and the automation service **materializes** it + into a live, dispatchable connector at boot. AI can now wire an integration as + pure metadata and a flow `connector_action` calls it end-to-end. + + - **Schema (`@objectstack/spec`).** `ConnectorSchema` gains `provider`, + `providerConfig`, and `auth` (a `credentialRef`-based instance-auth shape — + `ConnectorInstanceAuthSchema` — that references credentials, never inlines + them); `authentication` now defaults to `{ type: 'none' }` so a provider-bound + instance need not author it (loosening — existing connectors are unaffected). + `DeclarativeConnectorEntrySchema` (used by `stack.zod.ts`) rejects inline + secrets, orphan `providerConfig`/`auth`, and authored `actions`/`triggers` on a + provider-bound entry. A new `integration/connector-provider.ts` defines the + provider-factory contract as pure types. + + - **Engine + boot (`@objectstack/service-automation`).** The engine adds a + connector-provider registry (`registerConnectorProvider`/`getConnectorProvider`) + and origin-tags registered connectors. At boot the service resolves each + provider-bound entry — looking up the factory, resolving `auth.credentialRef` + via a pluggable `CredentialResolver` (open-tier default: environment + variables), and registering the materialized connector. Boot **fails loudly** + for an unknown provider, invalid `providerConfig`, an unresolvable + `credentialRef`, or a name conflict with a plugin-registered connector (no + silent precedence). + + - **Providers (`connector-rest` / `connector-openapi` / `connector-mcp`).** Each + plugin registers a provider factory in `init()` reusing its existing + generator/adapter API. Plugin options are now **optional**: with none the + plugin contributes only its provider factory; with instance options it also + registers a hand-wired connector (back-compat). `connector-openapi` adds a + `ConnectorOpenApiPlugin`. + + Open tier: static auth (`none`/`api-key`/`basic`/`bearer`) with `credentialRef` + resolved from env vars. Managed vaulting, OAuth2 refresh, and per-tenant + connection lifecycle remain the enterprise tier (ADR-0015) — an enterprise host + injects a vault-backed `CredentialResolver` with no change to the materialization + path. + +### Patch Changes + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [99755b5] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/core@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/connectors/connector-rest/package.json b/packages/connectors/connector-rest/package.json index 3961c20b95..067f063c2e 100644 --- a/packages/connectors/connector-rest/package.json +++ b/packages/connectors/connector-rest/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/connector-rest", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "Generic REST connector for ObjectStack — the reference concrete connector that registers a `request` action on the automation engine's connector registry (ADR-0018 §Addendum).", "main": "dist/index.js", diff --git a/packages/connectors/connector-slack/CHANGELOG.md b/packages/connectors/connector-slack/CHANGELOG.md index dda96076da..f7851ed35b 100644 --- a/packages/connectors/connector-slack/CHANGELOG.md +++ b/packages/connectors/connector-slack/CHANGELOG.md @@ -1,5 +1,39 @@ # @objectstack/connector-slack +## 15.1.0 + +### Patch Changes + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [99755b5] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/core@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/connectors/connector-slack/package.json b/packages/connectors/connector-slack/package.json index 534e11c711..2ef506023b 100644 --- a/packages/connectors/connector-slack/package.json +++ b/packages/connectors/connector-slack/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/connector-slack", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "Slack Web API connector for ObjectStack — registers `chat.postMessage` / `chat.update` / `call` actions on the automation engine's connector registry (ADR-0018 §Addendum, ADR-0022).", "main": "dist/index.js", diff --git a/packages/console/CHANGELOG.md b/packages/console/CHANGELOG.md index e7701e1034..ae0356ee74 100644 --- a/packages/console/CHANGELOG.md +++ b/packages/console/CHANGELOG.md @@ -1,5 +1,104 @@ # @objectstack/console +## 15.1.0 + +### Minor Changes + +- 330ceba: Console (objectui) refreshed to `b8967495be73`. Frontend changes in this range: + + - fix(app-shell): guard unsaved OWD overview rows in the Access rail and Studio header nav (#2600 follow-up) (#2610) + - fix(actions): defuse the three action-visibility traps (#2358) (#2611) + - fix(studio): confirm before header SPA nav discards unsaved pillar edits (#2600) (#2606) + - feat(studio): CEL formula editor with inferred result type; structured summary roll-up editor (#1582) (#2609) + + objectui range: `23d65c396b8c...b8967495be73` + +- 5286745: Console (objectui) refreshed to `fb35e4828fdb`. Frontend changes in this range: + + - fix(data-objectstack): emit MutationEvents from batchTransaction and bulk so master-detail saves refresh bound views (#2584) + - feat(dashboard-filters): #2578 item-5 enhancements — nested variable merging, metadata-aware default bindings, server-side optionsFrom distinct (#2590) + - feat(fields+form+detail): file/image upload cells in inline line-item grids (#2360) (#2585) + - feat(app-shell): visual filterBindings editor in the dashboard widget inspector (#2578) (#2586) + - fix(detail): highlight strip lookup editor honors ObjectStack `reference` key (#2407) (#2587) + - fix(app-shell): guard Studio Access pillar against silently discarding unsaved matrix edits (#2588) + - feat(dashboard-filters): #2578 follow-ups — catalog examples, guide tutorial, i18n entries, spec-alignment cleanup (#2581) + - fix(detail+fields+app-shell): ADR-0085 #2548 follow-ups — strip title dedupe, group icon/description, currency channel, approvals Bearer (#2577) + - feat(dashboard): dashboard-level filters driving multiple charts (framework#2501) (#2576) + - feat(page-header): metadata-driven multi-button record header (#2361) (#2574) + + objectui range: `092bd859934f...fb35e4828fdb` + +### Patch Changes + +- 55bc68d: Console (objectui) refreshed to `092bd859934f`. Frontend changes in this range: + + - fix(app-shell): bind current_user.positions into the client predicate scope; align role-gating examples (#1583 / ADR-0058) (#2573) + - feat(app-shell): CEL lint + field autocomplete for condition predicates (#1582) (#2567) + - fix(detail): gate related lists on the current user's child-object read permission (#2359) (#2565) + - feat(flow-designer): connector picker lists dispatchable connectors + marks declarative instances (#2563) + - feat(app-shell): Studio CEL editor for list-view conditional formatting (#1584 / #1582) (#2558) + - fix(fields): resolve lookup chip display name via referenced object schema, not the autonumber-prone key heuristic (#2357) (#2551) + - feat(kanban): accept CEL { condition, style } conditional-formatting rules (#1584 follow-up) (#2550) + - fix(plugin-grid): sniff CSV encoding in import wizard (GB18030 fallback) (#2557) + - feat(detail): editable record highlights on the shared inline-edit draft (#2549) + - fix(spec-bridge/form): #2545 stop dropping spec FormViewSchema keys; normalize legacy groups → sections (#2552) + - feat(flow-designer): localize palette headings + cloud-sync recents + guide (#2553) + - fix(studio): refresh builder top-bar name after a package rename (#2554) + - feat(core): B3 cascading-option guardrail, role-gated demo, ADR + browser e2e (#1583) (#2547) + - feat(list): unify conditional formatting + row-action visibility onto the CEL engine (#1584) (#2544) + - feat(detail): record-level inline edit — shared InlineEditContext + one atomic Save (#2542) + - feat(flow-designer): search box + keyboard nav + recents in the add-node palette (#2543) + - feat(kanban): default card fields to object highlightFields (ADR-0085, #2162) (#2541) + - fix(types/plugin-grid): #1763 declare spec-canonical bulkActions on ObjectGridSchema (#2539) + - fix(attachments): download attachments via authenticated signed URL (framework #2970) + - feat(studio): spec-driven package create/edit/view form in a modal (#2535) + - fix(permissions/fields): #2926 ④⑧ — FLS fail-open + lookup display_field (#2537) + - feat(app-shell): CEL authoring safety for RLS policies — lint, field autocomplete, test-run (#2533) + - fix(auth): gate DeviceAuthPage on features.deviceAuthorization (framework#2874 / #2513) (#2536) + - fix(app-shell): close view config panel on discard in edit mode (#2320) + - feat(metadata-admin): create form-family views through the View create UI (#2531) + - fix(app-shell): render action's objectName as an object selector (#2325) + - fix(components): exit inline edit mode for injected cell editors (#2534) + - fix(attachments): authenticated uploads + friendly denial copy in RecordAttachmentsPanel (#2755) (#2532) + - feat(components): page:tabs honors item-level visibleWhen — conditional tabs (framework#2606) (#2516) + - feat(metadata-admin): page variable source is a component picker (#2328) (#2523) + - fix(studio-design): make object canvas overridable via studio-canvas-preview registry (#2337) (#2527) + - fix(metadata-admin): seed flow createDefaults with required `type` (#2525) + - fix(metadata-admin): give hook create form a createSchema so object renders as a ref:object picker (#2521) + - feat(studio): enforce package namespace prefix at authoring time (framework#2694) (#2524) + - fix(app-shell): render View create-form Object field as ref:object picker (#2526) + - feat(components): add `variant === 'primary'` tie-break to action:bar ordering (#2339) (#2519) + - fix(build): stop TS6059 rootDir errors in dts build across 21 packages (#2520) + - fix(app-nav): exclude record-detail pages from the 'page' nav picker (#2333) (#2517) + - fix(plugin-report): stop TS6059 rootDir errors in dts build (#2334) (#2518) + - feat(flow-designer): add 'position' xRef picker kind for approval approvers / escalateTo (#2778) (#2515) + + objectui range: `cc2156841787...092bd859934f` + +- 330ceba: Console (objectui) refreshed to `23d65c396b8c`. Frontend changes in this range: + + - fix(i18n): drop try/catch-around-hook in createSafeTranslation / useSafeTranslate (#2605) + - fix(app-shell): Studio Access matrix — history opens in-place sheet, breadcrumb stops escaping the pillar (#2599) + - fix(data-objectstack): emit mutation events from batchTransaction/bulk so related lists refresh after master-detail saves (#2607) + - fix(metadata-admin): follow the live app locale, not just navigator.language (#2602) + - feat(detail+fields+components+app-shell): record inline-edit polish (#2572) (#2604) + - fix(app-shell+kanban+list): row-predicate CEL authoring advertises runtime-bound roots; kanban binds host scope (#2571 follow-up) (#2603) + - fix(plugin-list): spec bare-string sort form crashed ListView (#2578 shape-mismatch audit) (#2601) + - fix(app-shell): lock the Access pillar permission matrix in read-only packages (#2570) + - fix(fields): localize relative-date humanize via Intl.RelativeTimeFormat (framework#3040) (#2593) + - fix(components): pin sticky leading cells at measured header widths (#2592) + - fix(app-shell,core): keep error-envelope objects out of toast.error — React #31 page crash (#2579) (#2580) + - feat(flow-designer): pick the target node per branch in the Decision Branches editor (#1942) (#2568) + - fix(core+data-objectstack+app-shell): canonicalize reference/reference_to at the schema chokepoints (#2407) (#2598) + - fix(dashboard-filters): spec-form filter options crashed the dashboard; add guide screenshots (#2578) (#2597) + - fix(fields): PeoplePicker cursor resets only on real result changes (de-flakes keyboard test) (#2594) + - fix(studio): stop force-opening the new-object dialog on empty packages (#2569) + - feat(studio): CEL editor with validate + autocomplete for field conditional rules (#1582) (#2571) + - feat(kanban): default lane field honours the ADR-0085 stageField role (#2596) + - fix(fields+detail): resolve pre-existing rules-of-hooks violations in cell renderers (#2595) + + objectui range: `fb35e4828fdb...23d65c396b8c` + ## 15.0.0 ### Patch Changes diff --git a/packages/console/package.json b/packages/console/package.json index 0087d5b4e6..2ad0cf22d6 100644 --- a/packages/console/package.json +++ b/packages/console/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/console", - "version": "15.0.0", + "version": "15.1.0", "description": "Prebuilt Console SPA pinned to this @objectstack/framework release. Source of truth: @object-ui/console (https://github.com/objectstack-ai/objectui).", "license": "Apache-2.0", "homepage": "https://github.com/objectstack-ai/framework/tree/main/packages/console", diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 47294cbac5..a0ee4cf63b 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -1,5 +1,69 @@ # @objectstack/core +## 15.1.0 + +### Minor Changes + +- 99755b5: refactor(security): converge the anonymous-deny decision into one shared function + a source-enumerating ratchet (#2567 Phase 2) + + Phase 1 gated every HTTP surface (REST `/data`, dispatcher `/graphql` + `/meta`, + raw-hono `/data`) against the secure-by-default `requireAuth` posture, but each + seam hand-rolled the same `!userId && !isSystem → 401` check. Phase 2 removes + that duplication and pins the surfaces so a new ungated entry point fails CI. + + - **New `shouldDenyAnonymous` in `@objectstack/core`** (`security/anonymous-deny.ts`) + — the single anonymous-deny decision + shared 401 body/constants, mirroring the + `auth-gate.ts` pattern (pure function so the seams can never drift). All five + seams — REST `enforceAuth`, dispatcher `handleGraphQL` / `handleMetadata` / + `handleAI`, hono `denyAnonymous` — now delegate to it. **Pure refactor: no + runtime behavior change** (verified by the unchanged Phase-1 handler + e2e + proofs). Identity resolution and the dynamic exemptions (public-form grants, + share-link tokens) are untouched — they run upstream and only ever hand the + seam an already-resolved context. + - **A `discover()` ratchet on the authz-conformance matrix** — it statically + enumerates the data/meta/graphql HTTP entry points from source (curated + per-file probes, control-plane routes excluded) and asserts each is classified + by a matrix `covers` key. A new `/data`/`/meta`/`/graphql` route (or a + removed/stale `covers`) now fails CI as UNCLASSIFIED / STALE, not in review. A + companion negative test proves the ratchet bites. + + A design trap is guarded: `isAuthGateAllowlisted(undefined)` returns `true`, so a + body-routed seam (GraphQL, which has no request path) must pass no path — the + shared function's non-empty-path guard denies anonymous unconditionally there, + never falling through to the control-plane allowlist. + +- 464418e: feat(kernel): add `kernel:bootstrapped` lifecycle anchor — the phase that fires after every `kernel:ready` handler has settled but before `kernel:listening` (HTTP socket open). `kernel:ready` handlers run sequentially in plugin-registration order, so a handler that consumes data produced by a later-starting plugin (e.g. the security bootstrap seeds `sys_position`; the app plugin's seed loader inserts records) would race the very rows it needs. `kernel:bootstrapped` is the correct anchor for reconcile/backfill work: every producer's ready handler has finished by the time it fires. Both `ObjectKernel` and `LiteKernel` trigger it. The sharing-rule boot backfill moves from `kernel:listening` to `kernel:bootstrapped` (semantics-only; behaviour unchanged). + +### Patch Changes + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + ## 15.0.0 ### Minor Changes diff --git a/packages/core/package.json b/packages/core/package.json index db5a467244..cb6a66c2d7 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/core", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "Microkernel Core for ObjectStack", "type": "module", diff --git a/packages/create-objectstack/CHANGELOG.md b/packages/create-objectstack/CHANGELOG.md index 731cf914f2..0a15e90da6 100644 --- a/packages/create-objectstack/CHANGELOG.md +++ b/packages/create-objectstack/CHANGELOG.md @@ -1,5 +1,41 @@ # create-objectstack +## 15.1.0 + +### Minor Changes + +- 8fc1208: feat(protocol): complete ADR-0087 — load-seam handshake, chain backfill 12–15, release artifacts (#2643) + + Closes the remaining ADR-0087 gaps (see the ADR's as-built Addendum): + + - **P0 load seams (D1).** The protocol handshake now runs on the boot-time + durable-package rehydration path (`@objectstack/service-package` refuses an + incompatible `sys_packages` row with the structured `OS_PROTOCOL_INCOMPATIBLE` + diagnostic and keeps booting) and on `AppPlugin` for code-defined stacks + (fail-fast before the manifest is decomposed). `objectstack lint` gains + `protocol/missing-engines-range` (warning + fix-it) and the + `create-objectstack` blank template stamps `engines: { protocol: '^' }` + (re-stamped at version time by `scripts/sync-template-versions.mjs`) — the + two ends of the grandfathering ratchet. + - **Chain backfill (D2/D3).** `MetadataConversion.retiredFromLoadPath` + implements the load-window's second half (retired entries replay only via + `migrate meta` / fixture CI). Steps 12–15 land: the `api.requireAuth` flip + (semantic), the ADR-0090 wave (3 retired conversions + 5 semantic TODOs), the + `BookAudience` rename (retired conversion), and the ADR-0089 visibility + unification (`visibleOn`/`visibility` → `visibleWhen` as LIVE load-window + conversions) + the `.strict()` flip (semantic). The protocol-11 + `compactLayout` → `highlightFields` rename is backfilled as a retired step-11 + conversion. `migrate meta --from 10` now reaches protocol 15. + - **Release artifacts (D4).** `spec-changes.json` is generated from the + registries (`gen:spec-changes`, CI drift-checked), ships in the npm artifact + together with `api-surface.json`, and is attached to each `@objectstack/spec` + GitHub Release with `added[]`/`removed[]` filled from the api-surface diff + against the previously published release. The upgrade guide + (`docs/protocol-upgrade-guide.md`) is generated from the same registries and + CI drift-checked — a projection that cannot drift. + +- a79e179: Scaffolded projects are now container-ready out of the box: the `blank` template ships a `Dockerfile` (two-stage build onto the official `ghcr.io/objectstack-ai/objectstack` runtime image), a `docker-compose.yml` (app + Postgres single-host stack), and a `.dockerignore`, plus a Deploy section in the project README. `docker build -t my-app .` works immediately after `npm create objectstack`. + ## 15.0.0 ## 14.8.0 diff --git a/packages/create-objectstack/package.json b/packages/create-objectstack/package.json index 9fd0cdf38a..bcb42d7b02 100644 --- a/packages/create-objectstack/package.json +++ b/packages/create-objectstack/package.json @@ -1,6 +1,6 @@ { "name": "create-objectstack", - "version": "15.0.0", + "version": "15.1.0", "description": "Create a new ObjectStack project — npx create-objectstack", "bin": { "create-objectstack": "./bin/create-objectstack.js" diff --git a/packages/formula/CHANGELOG.md b/packages/formula/CHANGELOG.md index 0baa17c9ac..0649c8a95f 100644 --- a/packages/formula/CHANGELOG.md +++ b/packages/formula/CHANGELOG.md @@ -1,5 +1,37 @@ # @objectstack/formula +## 15.1.0 + +### Patch Changes + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/formula/package.json b/packages/formula/package.json index 382cfe04bd..2c10d6c439 100644 --- a/packages/formula/package.json +++ b/packages/formula/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/formula", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "ObjectStack canonical expression engine — CEL (cel-js) + ObjectStack stdlib + dialect registry", "main": "dist/index.js", diff --git a/packages/lint/CHANGELOG.md b/packages/lint/CHANGELOG.md index 9525f12012..e2824e4913 100644 --- a/packages/lint/CHANGELOG.md +++ b/packages/lint/CHANGELOG.md @@ -1,5 +1,52 @@ # @objectstack/lint +## 15.1.0 + +### Patch Changes + +- 55bc68d: ADR-0085 #2548 follow-ups surfaced by the real-backend browser pass: + + - **lint**: new `field-group-shadowed` warning in `validate-semantic-roles` — a + declared fieldGroup whose every visible member is hoisted into the detail + highlight strip (or is the record title) renders on forms but silently never + on detail pages (detail bodies hide the first 4 highlightFields). Warning + tier, same as the other semantic-role rules. + - **plugin-audit**: feed/audit summaries ("Created … / Deleted … / Updated …") + now name the object by its display label ("Semantic Zoo") instead of its API + name ("showcase_semantic_zoo") — these strings render verbatim in the record + Discussion feed and Setup dashboards. Falls back to the API name when the + object definition isn't resolvable. Existing stored rows are unchanged. + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/formula@15.1.0 + - @objectstack/sdui-parser@15.1.0 + ## 15.0.0 ### Minor Changes diff --git a/packages/lint/package.json b/packages/lint/package.json index adf8f01cf5..90ab018ec0 100644 --- a/packages/lint/package.json +++ b/packages/lint/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/lint", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "Static, build-time validation for an ObjectStack metadata graph — dashboard widget bindings, CEL/predicate expressions, and more. Pure (stack) => Issue[] functions shared by the CLI's `os validate` and any other consumer (e.g. AI authoring). Depends on @objectstack/spec; never on a runtime.", "type": "module", diff --git a/packages/mcp/CHANGELOG.md b/packages/mcp/CHANGELOG.md index 8d209dbbba..cb41906d99 100644 --- a/packages/mcp/CHANGELOG.md +++ b/packages/mcp/CHANGELOG.md @@ -1,5 +1,91 @@ # @objectstack/plugin-mcp-server +## 15.1.0 + +### Minor Changes + +- 3dc9fce: feat(mcp): `aggregate_records` tool — GROUP BY aggregation over the engine read path + + New MCP tool `aggregate_records` (count/sum/avg/min/max/count_distinct, optional + groupBy incl. date bucketing, where filter, IANA timezone) in the `data:read` + family. Execution routes through the ObjectQL ENGINE (`callData('aggregate')` + deliberately never uses the raw per-env driver), so RLS/tenant scoping and the + D10 delegator intersection apply exactly as on find. + + Security hardening shipped with it: + + - plugin-security: new FLS aggregate-INPUT gate — result masking never runs for + `aggregate` (output rows carry only aliases), so any groupBy / aggregation + reference to an FLS-unreadable field is now rejected fail-closed with the + offending field names (mirrors the FLS write gate). + - runtime: `aggregate` maps to the `list` ApiMethod in the object exposure gate + (an object whose `apiMethods` whitelist excludes `list` cannot leak row + statistics through GROUP BY), and the aggregate action requires at least one + aggregation (the engine's in-memory path would otherwise degrade to raw rows + that the FLS masker does not cover). + + The bridge seam is optional: a runtime that does not implement + `McpDataBridge.aggregate` simply does not register the tool (graceful + degradation, same contract as the action tools). + +### Patch Changes + +- fdfe1d9: fix(security): enforce the `ai.exposed` opt-in on the MCP action surface (#2849) + + Business-action bodies execute as trusted code: their engine facade carries no + `ExecutionContext`, so a body's internal reads/writes bypass RLS/FLS/CRUD and + tenant scoping — the caller's permissions and an agent's ADR-0090 D10 data + ceiling do NOT bound what an invoked action does. The MCP `run_action` bridge + nevertheless allowed invoking ANY headless action, ignoring the spec's + `ai.exposed` governance gate (ADR-0011) entirely. + + The MCP bridge now fail-closes on `ai.exposed`: `list_actions` only enumerates + — and `run_action` only dispatches — actions the app author explicitly opted + into the AI surface with `ai: { exposed: true, description }`. Flow-type + actions additionally receive the caller's identity (`userId` / `positions` / + `permissions` / `tenantId`) as a proper `AutomationContext` (replacing the + former `triggerData` envelope the engine never read), so a `runAs: 'user'` + flow enforces RLS as the invoker instead of running unscoped (ADR-0049). + Trusted body dispatches are now audit-logged on both the MCP and REST action + paths, and the MCP tool/README/docs wording no longer claims action bodies run + under the caller's RLS. + + Migration: actions that should stay invokable by AI agents through MCP must + declare `ai: { exposed: true, description: '…' }` (≥40-char description). All + other invocation surfaces (UI, REST `/actions/...`) are unchanged. + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [99755b5] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [1c58abd] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/core@15.1.0 + - @objectstack/types@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/mcp/package.json b/packages/mcp/package.json index a6513d7478..a57b111274 100644 --- a/packages/mcp/package.json +++ b/packages/mcp/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/mcp", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "ObjectStack as an MCP server — exposes your app's objects (and AI tools) over the Model Context Protocol (stdio + Streamable HTTP)", "type": "module", diff --git a/packages/metadata-core/CHANGELOG.md b/packages/metadata-core/CHANGELOG.md index 3eb1628f47..09e019bef2 100644 --- a/packages/metadata-core/CHANGELOG.md +++ b/packages/metadata-core/CHANGELOG.md @@ -1,5 +1,37 @@ # @objectstack/metadata-core +## 15.1.0 + +### Patch Changes + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/metadata-core/package.json b/packages/metadata-core/package.json index eb3d78018c..260c34d101 100644 --- a/packages/metadata-core/package.json +++ b/packages/metadata-core/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/metadata-core", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "Metadata Repository contracts: types, canonicalization, errors, interface (ADR-0008).", "type": "module", diff --git a/packages/metadata-fs/CHANGELOG.md b/packages/metadata-fs/CHANGELOG.md index ec63b2be73..173e19a9b2 100644 --- a/packages/metadata-fs/CHANGELOG.md +++ b/packages/metadata-fs/CHANGELOG.md @@ -1,5 +1,11 @@ # @objectstack/metadata-fs +## 15.1.0 + +### Patch Changes + +- @objectstack/metadata-core@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/metadata-fs/package.json b/packages/metadata-fs/package.json index 9deb98f97c..1c0e4b1db3 100644 --- a/packages/metadata-fs/package.json +++ b/packages/metadata-fs/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/metadata-fs", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "FileSystemRepository: Node-only Repository implementation backed by JSON files and a JSONL change log (ADR-0008).", "type": "module", diff --git a/packages/metadata-protocol/CHANGELOG.md b/packages/metadata-protocol/CHANGELOG.md index 8858b3c935..9960d7faa1 100644 --- a/packages/metadata-protocol/CHANGELOG.md +++ b/packages/metadata-protocol/CHANGELOG.md @@ -1,5 +1,82 @@ # @objectstack/metadata-protocol +## 15.1.0 + +### Minor Changes + +- 7f68068: feat(discovery): honest capabilities — standardized stub/fallback marker + realtime route honesty (ADR-0076 D12/A1.5 framework slice, #2462) + + **Spec** — new service self-description marker for honest discovery + (ADR-0076 D12): `SERVICE_SELF_INFO_KEY` (`__serviceInfo`), + `ServiceSelfInfoSchema` / `ServiceSelfInfo`, and `readServiceSelfInfo()`, + which also normalizes plugin-dev's legacy `_dev: true` flag to + `{ status: 'stub', handlerReady: false }`. A registered service that is a + stub / dev fake / degraded fallback self-identifies via this marker; a fully + real service carries no marker. + + **Runtime + metadata-protocol** — both discovery builders + (`HttpDispatcher.getDiscoveryInfo` and the protocol shim's `getDiscovery`) + now honor the marker instead of hardcoding `status: 'available', +handlerReady: true` for every registered service. Dev stubs report `stub`, + the ObjectQL analytics fallback reports `degraded` (it keeps serving — no + `/analytics` 404), and consumers can finally trust + `status === 'available'` / `handlerReady === true`. + + **Realtime honesty fix** — discovery no longer advertises a + `/realtime` route or `websockets: true`: `service-realtime` is an + in-process pub/sub bus, no dispatcher branch or plugin mounts any + `/realtime` HTTP surface, so the advertised route always 404'd. The + registered service now reports `status: 'degraded', handlerReady: false` + with no route (clients using the SDK are unaffected — it falls back to the + conventional path, which behaves exactly as before). Also corrects the + advertised realtime provider from the nonexistent `plugin-realtime` to + `service-realtime`. + + **REST (A1.5)** — the REST layer's protocol dependency is narrowed from the + `ObjectStackProtocol` god-union to the new `RestProtocol = +DataProtocol & MetadataProtocol` slice (exported from + `@objectstack/rest`), per the ADR-0076 D9 incremental narrowing guidance. + Type-level only; no runtime change. + +- 7f9a795: OWD posture is now enforced on the runtime write path (#3050). `metadata-protocol` gains the ADR-0094-addendum `registerAuthoringGate(type, gate)` seam — an awaited, throwing pre-persistence hook inside `saveMetaItem` (draft and publish-mode saves; environment writes only). `plugin-security` registers the `object` posture gate on it: an environment overlay of a packaged object may only TIGHTEN `sharingModel`/`externalSharingModel` (ADR-0086 D1 — closes the `OS_METADATA_WRITABLE=object` unvalidated-widening hole), and `externalSharingModel ≤ sharingModel` (ADR-0090 D11) is now rejected at save time instead of only by CLI lint. Write-path only — stored metadata keeps loading unchanged. + +### Patch Changes + +- 28ba0c7: fix(metadata-protocol): findData now rejects unknown `$`-prefixed query parameters with 400 `UNSUPPORTED_QUERY_PARAM` instead of silently treating them as implicit field-equality filters that match zero rows (#2926 ⑩). A `$`-prefixed key can never be a field name, so this is loud-failure only for the unsupported-alias class; bare-key implicit equality filtering is unchanged. The error message lists the supported aliases ($top, $skip, $orderby, $select, $count, $search, $searchFields, $filter, $expand). +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [99755b5] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [1c58abd] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/core@15.1.0 + - @objectstack/types@15.1.0 + - @objectstack/formula@15.1.0 + - @objectstack/metadata-core@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/metadata-protocol/package.json b/packages/metadata-protocol/package.json index b6e1d9278a..10c75323d4 100644 --- a/packages/metadata-protocol/package.json +++ b/packages/metadata-protocol/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/metadata-protocol", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "ObjectStack metadata management protocol: sys_metadata CRUD, draft/publish, locks, package ownership, diagnostics (ADR-0076).", "type": "module", diff --git a/packages/metadata/CHANGELOG.md b/packages/metadata/CHANGELOG.md index fb72d7faaf..d66c893512 100644 --- a/packages/metadata/CHANGELOG.md +++ b/packages/metadata/CHANGELOG.md @@ -1,5 +1,48 @@ # @objectstack/metadata +## 15.1.0 + +### Patch Changes + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [94b8e44] +- Updated dependencies [86c0aea] +- Updated dependencies [99755b5] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [eb89a8c] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [1c58abd] +- Updated dependencies [aead168] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/platform-objects@15.1.0 + - @objectstack/core@15.1.0 + - @objectstack/types@15.1.0 + - @objectstack/metadata-core@15.1.0 + - @objectstack/metadata-fs@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/metadata/package.json b/packages/metadata/package.json index 15709684b4..d24986b31a 100644 --- a/packages/metadata/package.json +++ b/packages/metadata/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/metadata", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "Metadata loading, saving, and persistence for ObjectStack", "type": "module", diff --git a/packages/objectql/CHANGELOG.md b/packages/objectql/CHANGELOG.md index 0aa7917aaa..0c8bb0559c 100644 --- a/packages/objectql/CHANGELOG.md +++ b/packages/objectql/CHANGELOG.md @@ -1,5 +1,263 @@ # @objectstack/objectql +## 15.1.0 + +### Minor Changes + +- 86c0aea: feat(attachments): sys_file orphan lifecycle + parent-derived attachment access (#2755) + + **Orphan lifecycle (ADR-0057).** Deleting a `sys_attachment` join row used to + orphan the backing `sys_file` row and its storage bytes forever. `sys_file` + now declares a lifecycle (`ttl 30d` on a new `deleted_at` tombstone for + orphans; `retention 7d onlyWhen status=pending` for abandoned uploads), the + storage plugin's new hooks tombstone a file when its LAST join row is deleted + (attachments scope only — `Field.file`/`Field.image`/avatar scopes are never + touched) and un-tombstone on re-attach, and a new LifecycleService **reap + guard** seam (`registerReapGuard`) re-verifies zero references at sweep time + and deletes the storage bytes before confirming each row reap. A guarded + object is never blind-deleted; an erroring guard fails safe (rows retained). + + **Attachment access (ADR-0049, Salesforce parent-derived semantics).** + `sys_attachment` create now requires caller READ visibility of the parent + record (403 `ATTACHMENT_PARENT_ACCESS`) and server-stamps `uploaded_by` from + the session (client value ignored); delete requires uploader-or-parent-editor + (403 `ATTACHMENT_DELETE_DENIED`). The storage upload routes require an + authenticated session when an auth service is wired (401 `AUTH_REQUIRED`; + bare kernels stay open) and stamp `owner_id` on new files. + + **REMOVED — `sys_attachment.share_type` / `sys_attachment.visibility`.** + Both fields were modeled in v1 with zero runtime consumers (ADR-0049 + parsed-but-unenforced). There is no replacement key: attachment access is + derived from the parent record by the hooks above. Writers of these fields + should simply stop sending them (unknown-field validation will reject them); + existing DB columns are left as unmanaged leftovers, no migration needed. + + `@objectstack/verify` gains `BootOptions.extraPlugins` for booting optional + service pairs (e.g. storage + audit) in dogfood fixtures. + +- e0b049a: fix(security): enforce static `readonly` fields on the UPDATE write path (#2948) + + A field's static `readonly: true` was never enforced server-side on update: the + record validator only _skipped_ read-only columns from validation, and only the + conditional `readonlyWhen` variant was stripped from the write payload. A + non-system (user-context) update could therefore overwrite any `readonly` + column — audit stamps, provenance (`managed_by`), or other system-computed + values — unless a field-level permission happened to guard it. (The + cross-tenant `organization_id` face was already closed by #2946; this is the + broader in-tenant integrity face.) + + `engine.update` now strips **caller-supplied** writes to statically-`readonly` + fields for non-system contexts, on both the single-id and multi-row paths + (symmetric with `readonlyWhen` — it strips, does not reject). Two guards keep + every legitimate write intact: + + - **caller-supplied only** — the strip runs against a snapshot of the keys the + caller sent _before_ hooks/middleware ran, so server stamps applied by the + audit hook (`updated_by`/`updated_at`) and write middleware survive; only a + client that explicitly forged a read-only field has it dropped. + - **system-context exempt** — `isSystem` writes (import, seed replay, approvals, + lifecycle hooks) legitimately set read-only columns and skip the strip. + + No change for single-org or any write that does not forge a read-only column. + +- 1c58abd: Generic pinyin search recall (#2486, ADR-0098): a locale-gated + `OS_SEARCH_PINYIN_ENABLED` switch (auto-on when the stack configures any + `zh-*` locale) provisions a hidden `__search` companion column for each + object's display/name field at compile time, the new + `@objectstack/plugin-pinyin-search` fills it with full pinyin + initials + ("张伟" → "zhangwei zw") on before-save (plus boot backfill and a + `rebuildSearchCompanion` reconcile entry), and `$search` ORs the column in at + query time — so lookup pickers, list quick-search and ⌘K transparently match + `zhangwei` / `zw` against CJK names. Purely additive: `resolveSearchFields`, + `searchableFields`, drivers and non-Chinese deployments are untouched; FLS + restricted / secret / PII fields never feed the companion. + +### Patch Changes + +- 7f68068: feat(discovery): honest capabilities — standardized stub/fallback marker + realtime route honesty (ADR-0076 D12/A1.5 framework slice, #2462) + + **Spec** — new service self-description marker for honest discovery + (ADR-0076 D12): `SERVICE_SELF_INFO_KEY` (`__serviceInfo`), + `ServiceSelfInfoSchema` / `ServiceSelfInfo`, and `readServiceSelfInfo()`, + which also normalizes plugin-dev's legacy `_dev: true` flag to + `{ status: 'stub', handlerReady: false }`. A registered service that is a + stub / dev fake / degraded fallback self-identifies via this marker; a fully + real service carries no marker. + + **Runtime + metadata-protocol** — both discovery builders + (`HttpDispatcher.getDiscoveryInfo` and the protocol shim's `getDiscovery`) + now honor the marker instead of hardcoding `status: 'available', +handlerReady: true` for every registered service. Dev stubs report `stub`, + the ObjectQL analytics fallback reports `degraded` (it keeps serving — no + `/analytics` 404), and consumers can finally trust + `status === 'available'` / `handlerReady === true`. + + **Realtime honesty fix** — discovery no longer advertises a + `/realtime` route or `websockets: true`: `service-realtime` is an + in-process pub/sub bus, no dispatcher branch or plugin mounts any + `/realtime` HTTP surface, so the advertised route always 404'd. The + registered service now reports `status: 'degraded', handlerReady: false` + with no route (clients using the SDK are unaffected — it falls back to the + conventional path, which behaves exactly as before). Also corrects the + advertised realtime provider from the nonexistent `plugin-realtime` to + `service-realtime`. + + **REST (A1.5)** — the REST layer's protocol dependency is narrowed from the + `ObjectStackProtocol` god-union to the new `RestProtocol = +DataProtocol & MetadataProtocol` slice (exported from + `@objectstack/rest`), per the ADR-0076 D9 incremental narrowing guidance. + Type-level only; no runtime change. + +- e9a2885: fix(security): enforce `readonlyWhen` on the multi-row UPDATE path (#3042) + + Conditional `readonlyWhen` field locks were stripped only on the single-id + UPDATE path; the bulk `update({ multi: true, where })` path enforced static + `readonly` (#2948) but never `readonlyWhen`. A programmatic/embedded caller (or + a plugin) issuing a multi-row update in a user context could therefore write a + field its own `readonlyWhen` predicate should have locked — the conditional + lock held for a `PATCH /data/:object/:id` but not for a bulk where-predicate + update. (The external REST/SDK `updateMany` endpoint was unaffected: it loops + single-id `engine.update` calls, which already strip `readonlyWhen`.) + + `engine.update` now, on the multi-row path and only when the payload actually + writes a `readonlyWhen` field, reads the row-scoped match set with the same + composed AST the write binds (one query) and drops any field whose predicate is + TRUE for at least one matched row — a single bulk payload cannot keep a field + for some rows and drop it for others, so a field locked in any target row is + fail-safe-dropped for the batch (narrow the `where` to reach the rows where it + is unlocked). A conditional field NO matched row locks is written normally, so a + legitimate bulk edit is unaffected. Symmetric with the single-id + `stripReadonlyWhenFields` and with the static-`readonly` bulk strip; INSERT + stays exempt. No change for any single-id update or any object without + `readonlyWhen` fields. + +- 6613ad0: fix(security): exempt engine referential FK clears from the owner_id transfer guard (#3023) + + Follow-up to the #3004 ownership-anchor guard. `owner_id` is a lookup to `sys_user` + with the default `deleteBehavior: 'set_null'`, so deleting a `sys_user` makes + `cascadeDeleteRelations` null `owner_id` on every dependent row. That cascade write + re-entered the write middleware under the deleter's context, where the #3004 guard + read the `owner_id = null` as a user-initiated disown and denied it — aborting the + cascade mid-way (no transaction, so partial state) for any deleter without the + transfer grant on the child object (e.g. a member clearing a `public_read_write` + child that RLS would otherwise have allowed). + + The cascade FK clear is engine-mandated referential integrity consequent to an + already-authorized parent delete, not a user ownership change. `cascadeDeleteRelations` + now tags the `set_null` write with a server-derived `__referentialFieldClear` context + marker (set by the engine, never built from a request — same trust model as + `__expandRead`), and the ownership-anchor guard skips when that marker is present. + Ordinary user writes are unaffected; the marker cannot be forged from client input, + so it can never slip a real ownership transfer past the guard. + +- a16972b: fix(security): guard the `owner_id` ownership anchor and scope bulk writes to owner-visible rows (#3004, #2982) + + Two write-path holes on the row-ownership anchor (`owner_id`), the column OWD + row-level scoping keys off to decide who may update/delete a record. + + - **#3004 — client-writable, unguarded `owner_id`.** The anchor is deliberately + not `readonly` (ownership is transferable), so the static-readonly strip never + covered it and FLS doesn't gate it by default. A non-privileged writer could + therefore `insert` a record under someone else's name (forge) or `update` one + to a new owner (transfer / disown), evading the owner gate that governs + update/delete. The security middleware (plugin-security step 3.5) now treats + `owner_id` as system-managed for non-privileged writers: on insert an empty + value is auto-stamped to the acting user (batch rows too — previously only the + single-record path stamped, leaving bulk-inserted rows NULL-owned and + invisible to their creator), and a supplied foreign owner is denied; on update + a supplied `owner_id` is a transfer/disown and is denied — the unchanged no-op + echo of a form save is tolerated via a pre-image compare, and a bulk + change-set carrying `owner_id` fails closed. A non-scalar `owner_id` + (array/object) is rejected outright rather than string-coerced, and the + change-set membership test uses own-property semantics so a polluted + prototype cannot spoof an ownership write. Both require the transfer grant + (`allowTransfer`, or `modifyAllRecords` which implies it) to proceed. System + context (`ctx.isSystem`) stays fully exempt (OAuth provisioning / cron + snapshots / seed claims / migrations), and under delegation both principals + must hold the grant (ADR-0090 D10 intersection). Note a REST **import** runs + under the importer's own context (not `isSystem`), so a non-privileged user + importing a CSV whose `owner_id` column names other users is correctly denied + unless they hold the transfer grant — administrators (who carry + `modifyAllRecords`) are unaffected. + + - **#2982 — bulk writes skipped owner scoping on OWD-`private` objects.** A + `update({ multi: true })` / bulk delete rebuilt the driver AST from + `options.where` AFTER the middleware chain, discarding the owner/RLS write + filter that plugin-sharing (`buildWriteFilter`) and plugin-security compose + onto `opCtx.ast` — so a member's bulk write hit every matching row, including + peers'. The engine now seeds `opCtx.ast` from the caller's predicate BEFORE the + chain (the same seam reads use) and hands the middleware-composed AST to + `driver.updateMany` / `driver.deleteMany`, so bulk writes are constrained to the + rows the caller may edit — matching single-id write behavior. `delete` now + applies the same scalar-`id` guard `update` already had, so an id-list bulk + delete (`where: { id: { $in: […] } }, multi: true`) is owner-scoped too, and + both multi branches fail CLOSED (throw) rather than silently rebuilding an + unscoped predicate if the row-scoping AST is ever absent. + + Consequences of routing bulk writes through the AST: the anti-oracle + predicate guard now also applies to bulk `update`/`delete` (a bulk write + filtering on an FLS-unreadable field is rejected, as reads already are), and a + principal-less (no-`userId`, non-system) bulk write on an owner-scoped object + now correctly affects zero rows instead of all of them. + + Proven end-to-end on the real showcase app + (`packages/qa/dogfood/test/owner-anchor-and-bulk-writes.dogfood.test.ts`) and pinned + in the ADR-0096 authz-conformance ledger (`ownership-anchor-guard`, + `bulk-write-owner-scoping`). + +- 8b27dd7: fix(security): enforce referenced-object RLS/FLS on $expand (#2850) + + `expandRelatedRecords` resolved lookup/master_detail/user references via the + driver directly, so the referenced object's row- and field-level security never + ran — any API/session caller who could read a base row could `?expand=` a + foreign key and receive RLS-hidden rows and FLS-masked fields (tenant isolation + was the only surviving boundary). + + The expand batch now routes through the engine's own `find`, so the security + middleware applies the referenced object's RLS + FLS to the `id $in [...]` batch + (one query per level, no N+1). The sub-read carries a server-set `__expandRead` + marker: the middleware waives only the object-level CRUD / requiredPermissions + gate for PUBLIC referenced objects (already broadly readable — avoids + over-blocking common status/owner lookups), while PRIVATE referenced objects + keep the full gate. Covers the list and single-record REST/protocol surfaces. + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [99755b5] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [7f9a795] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [1c58abd] +- Updated dependencies [28ba0c7] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/metadata-protocol@15.1.0 + - @objectstack/core@15.1.0 + - @objectstack/types@15.1.0 + - @objectstack/formula@15.1.0 + - @objectstack/metadata-core@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/objectql/package.json b/packages/objectql/package.json index d778f34d85..d50efd0f9b 100644 --- a/packages/objectql/package.json +++ b/packages/objectql/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/objectql", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "Isomorphic ObjectQL Engine for ObjectStack", "main": "dist/index.js", diff --git a/packages/observability/CHANGELOG.md b/packages/observability/CHANGELOG.md index f54b68b718..73b97867b2 100644 --- a/packages/observability/CHANGELOG.md +++ b/packages/observability/CHANGELOG.md @@ -1,5 +1,37 @@ # @objectstack/observability +## 15.1.0 + +### Patch Changes + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/observability/package.json b/packages/observability/package.json index 837aa3f52d..cd2609cc6d 100644 --- a/packages/observability/package.json +++ b/packages/observability/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/observability", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "Observability contracts and exporters for ObjectStack — MetricsRegistry, ErrorReporter, Logger plus noop/console/OTLP-HTTP exporters. Deployment-target neutral; runtime and services depend on this so the same instrumentation works on Cloudflare Workers, Node, and self-hosted Kubernetes.", "type": "module", diff --git a/packages/platform-objects/CHANGELOG.md b/packages/platform-objects/CHANGELOG.md index 1e7948d251..7a94a8d1d5 100644 --- a/packages/platform-objects/CHANGELOG.md +++ b/packages/platform-objects/CHANGELOG.md @@ -1,5 +1,142 @@ # @objectstack/platform-objects +## 15.1.0 + +### Minor Changes + +- 86c0aea: feat(attachments): sys_file orphan lifecycle + parent-derived attachment access (#2755) + + **Orphan lifecycle (ADR-0057).** Deleting a `sys_attachment` join row used to + orphan the backing `sys_file` row and its storage bytes forever. `sys_file` + now declares a lifecycle (`ttl 30d` on a new `deleted_at` tombstone for + orphans; `retention 7d onlyWhen status=pending` for abandoned uploads), the + storage plugin's new hooks tombstone a file when its LAST join row is deleted + (attachments scope only — `Field.file`/`Field.image`/avatar scopes are never + touched) and un-tombstone on re-attach, and a new LifecycleService **reap + guard** seam (`registerReapGuard`) re-verifies zero references at sweep time + and deletes the storage bytes before confirming each row reap. A guarded + object is never blind-deleted; an erroring guard fails safe (rows retained). + + **Attachment access (ADR-0049, Salesforce parent-derived semantics).** + `sys_attachment` create now requires caller READ visibility of the parent + record (403 `ATTACHMENT_PARENT_ACCESS`) and server-stamps `uploaded_by` from + the session (client value ignored); delete requires uploader-or-parent-editor + (403 `ATTACHMENT_DELETE_DENIED`). The storage upload routes require an + authenticated session when an auth service is wired (401 `AUTH_REQUIRED`; + bare kernels stay open) and stamp `owner_id` on new files. + + **REMOVED — `sys_attachment.share_type` / `sys_attachment.visibility`.** + Both fields were modeled in v1 with zero runtime consumers (ADR-0049 + parsed-but-unenforced). There is no replacement key: attachment access is + derived from the parent record by the hooks above. Writers of these fields + should simply stop sending them (unknown-field validation will reject them); + existing DB columns are left as unmanaged leftovers, no migration needed. + + `@objectstack/verify` gains `BootOptions.extraPlugins` for booting optional + service pairs (e.g. storage + audit) in dogfood fixtures. + +### Patch Changes + +- 94b8e44: feat(attachments): edit-on-parent attach, upload-session lifecycle, trash=false (#2970 items 3-5) + + Closes the remaining enforce-or-remove / lifecycle items of #2970: + + - **Edit-on-parent for attach (item 3, Salesforce parity).** Creating a + `sys_attachment` now requires EDIT access to the parent record (via the + sharing service's `canEdit`), not merely read — public-model parents are + unchanged (canEdit is true for any member), private/owner-scoped parents + require the caller to own/edit them. Degrades to read visibility when no + sharing service is present. + - **`sys_upload_session` lifecycle (item 4).** Abandoned / terminal chunked + upload sessions are reaped by the platform LifecycleService (`transient`; + TTL 1d past `expires_at`; retention 7d for terminal statuses). Row reap + only — a reap guard that aborts backend multipart uploads for partial S3 + sessions is a filed follow-up. + - **`sys_attachment.enable.trash` → `false` (item 5, ADR-0049).** The flag is + `dead` in the liveness ledger (no engine soft-delete reader) and attachment + deletes are hard (the reap guard reclaims a file's bytes once its last join + row is gone, so a restore would dangle) — declare the honest state rather + than claim a restore capability the runtime does not provide. + +- e7d5291: **Every feature-gated capability is now UI-gated, guardrailed by a flag registry and a declarative `requiresFeature` annotation (#2874, generalizing the create-user phone fix #2871).** + + `@objectstack/spec/kernel` gains `PUBLIC_AUTH_FEATURES` — a classification registry for all 13 boolean flags served at `/api/v1/auth/config`: consumption surface (crud/login/status), default semantics (opt-in `== true` vs default-on `!= false`), and the gated spec inputs or an exemption reason. A plugin-auth drift test pins the served key set to the registry, and a platform-objects completeness guard pins the registry to the actual gates in both directions. + + `ActionSchema`/`ActionParamSchema` gain `requiresFeature: ''` (enum-checked), lowered at parse time into the canonical `visible` CEL predicate per the flag's registered semantics, AND-composed with any explicit `visible`, and stripped from the output — renderers and lint see only `visible`, so objectui needs no changes. All 22 hand-written `features.*` gates migrated (behavior-locked by an exact-string matrix test), and the audit gated 17 previously naked capability-dependent actions: the six `sys_user` platform-admin actions, six 2FA actions, and five `sys_oauth_application` actions now hide when their plugin is off instead of rendering buttons that 404. + +- eb89a8c: feat(cli): `os i18n extract` now emits action param keys (`o.._actions..params..*`) so action-dialog forms are translatable (#3030) + + The console client already resolves param labels, help text, placeholders and + option labels from `o.._actions..params.*`, but the extractor + never walked `actions[].params`, so those keys were absent from generated + bundles and dialogs like Setup → Create User rendered raw English under any + locale. The extractor now emits: + + - inline params → `label` / `helpText` / `placeholder` / `options.`; + - field-backed params (`{ field: '…' }`) → only when they carry a literal + override (field translations already cover them at runtime); + - both object actions and top-level (global) actions. + + `@objectstack/platform-objects` regenerates its en/zh-CN/ja-JP/es-ES bundles + with the new keys filled (user admin actions, sys_jwks fields, page variable + forms). Re-running extract with `--merge` stays idempotent. + +- aead168: fix(auth): align the better-auth family on 1.7.0-rc.1, implement the new adapter methods, and add the new sys_jwks columns (#2974) + + Remediating GHSA-p2fr-6hmx-4528 (`@better-auth/oauth-provider`) requires the + 1.7 plugin line, which imports `CLIENT_ASSERTION_TYPE` and other symbols that + only exist in `@better-auth/core` 1.7.x — so the whole better-auth family is + pinned to `1.7.0-rc.1` together (mixing a 1.7 plugin with 1.6.23 core 500s on + sign-in). better-auth 1.7 also extends its `CustomAdapter` contract with two + new methods, which the ObjectQL adapter now implements: + + - `consumeOne` — atomic single-row consume (find the guarded row, delete it, + return it), used by better-auth for single-use credential consumption + (e.g. verification tokens on the sign-in path). + - `incrementOne` — guarded counter mutation (`field = field + delta` per + `increment` entry plus any absolute `set` values), returning the updated row + or `null` when the guard matches nothing. + + Both are find-then-write mirrors of the existing `delete` / `update` methods + (ObjectQL exposes no native atomic primitive) and honour the same core/plugin + field-name bridging. + + better-auth 1.7 also extends its `jwks` model with two new optional columns, + `alg` (signing algorithm, e.g. `EdDSA`) and `crv` (curve, e.g. `Ed25519`), and + writes them when minting signing keys. The `sys_jwks` platform object gains the + matching fields — without them every JWKS write failed (`table sys_jwks has no +column named alg`), 500ing token signing and breaking session validation + (sign-in succeeded but every authenticated request 401'd). + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/metadata-core@15.1.0 + ## 15.0.0 ### Minor Changes diff --git a/packages/platform-objects/package.json b/packages/platform-objects/package.json index 2a91270658..29240c0bf5 100644 --- a/packages/platform-objects/package.json +++ b/packages/platform-objects/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/platform-objects", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "Core platform object schemas for ObjectStack — identity, security, audit, tenant, and metadata objects", "main": "dist/index.js", diff --git a/packages/plugins/driver-memory/CHANGELOG.md b/packages/plugins/driver-memory/CHANGELOG.md index e421844a6d..eb1cf7ece8 100644 --- a/packages/plugins/driver-memory/CHANGELOG.md +++ b/packages/plugins/driver-memory/CHANGELOG.md @@ -1,5 +1,39 @@ # @objectstack/driver-memory +## 15.1.0 + +### Patch Changes + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [99755b5] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/core@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/plugins/driver-memory/package.json b/packages/plugins/driver-memory/package.json index 61c0856b16..31d4569cc9 100644 --- a/packages/plugins/driver-memory/package.json +++ b/packages/plugins/driver-memory/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/driver-memory", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "In-Memory Driver for ObjectStack (Reference Implementation)", "main": "dist/index.js", diff --git a/packages/plugins/driver-mongodb/CHANGELOG.md b/packages/plugins/driver-mongodb/CHANGELOG.md index 7151cbce07..417a3dca39 100644 --- a/packages/plugins/driver-mongodb/CHANGELOG.md +++ b/packages/plugins/driver-mongodb/CHANGELOG.md @@ -1,5 +1,39 @@ # @objectstack/driver-mongodb +## 15.1.0 + +### Patch Changes + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [99755b5] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/core@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/plugins/driver-mongodb/package.json b/packages/plugins/driver-mongodb/package.json index c76d95c94f..901ed45d84 100644 --- a/packages/plugins/driver-mongodb/package.json +++ b/packages/plugins/driver-mongodb/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/driver-mongodb", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "MongoDB Driver for ObjectStack - Native document database driver via official mongodb client", "main": "dist/index.js", diff --git a/packages/plugins/driver-sql/CHANGELOG.md b/packages/plugins/driver-sql/CHANGELOG.md index be16f988f7..250e79b28c 100644 --- a/packages/plugins/driver-sql/CHANGELOG.md +++ b/packages/plugins/driver-sql/CHANGELOG.md @@ -1,5 +1,41 @@ # @objectstack/driver-sql +## 15.1.0 + +### Patch Changes + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [99755b5] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [1c58abd] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/core@15.1.0 + - @objectstack/types@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/plugins/driver-sql/package.json b/packages/plugins/driver-sql/package.json index 559cafba76..863b09931d 100644 --- a/packages/plugins/driver-sql/package.json +++ b/packages/plugins/driver-sql/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/driver-sql", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "SQL Driver for ObjectStack - Supports PostgreSQL, MySQL, SQLite via Knex", "main": "dist/index.js", diff --git a/packages/plugins/driver-sqlite-wasm/CHANGELOG.md b/packages/plugins/driver-sqlite-wasm/CHANGELOG.md index b2a9ef1125..f0808a367d 100644 --- a/packages/plugins/driver-sqlite-wasm/CHANGELOG.md +++ b/packages/plugins/driver-sqlite-wasm/CHANGELOG.md @@ -1,5 +1,40 @@ # @objectstack/driver-sqlite-wasm +## 15.1.0 + +### Patch Changes + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [99755b5] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/core@15.1.0 + - @objectstack/driver-sql@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/plugins/driver-sqlite-wasm/package.json b/packages/plugins/driver-sqlite-wasm/package.json index 6da5ad2548..532137e262 100644 --- a/packages/plugins/driver-sqlite-wasm/package.json +++ b/packages/plugins/driver-sqlite-wasm/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/driver-sqlite-wasm", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "WASM SQLite Driver for ObjectStack — runs in browser/WebContainer (StackBlitz) without native bindings", "keywords": [ diff --git a/packages/plugins/embedder-openai/CHANGELOG.md b/packages/plugins/embedder-openai/CHANGELOG.md index b3f30cda7e..1a2f96cf41 100644 --- a/packages/plugins/embedder-openai/CHANGELOG.md +++ b/packages/plugins/embedder-openai/CHANGELOG.md @@ -1,5 +1,37 @@ # @objectstack/embedder-openai +## 15.1.0 + +### Patch Changes + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/plugins/embedder-openai/package.json b/packages/plugins/embedder-openai/package.json index f60e9267a0..6bac6d6c03 100644 --- a/packages/plugins/embedder-openai/package.json +++ b/packages/plugins/embedder-openai/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/embedder-openai", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "OpenAI-compatible embedder for ObjectStack — works against OpenAI, 阿里通义 DashScope, 智谱 BigModel, 硅基流动 SiliconFlow, 火山引擎 Doubao, MiniMax, Ollama, and any drop-in OpenAI-shape endpoint.", "main": "dist/index.js", diff --git a/packages/plugins/knowledge-memory/CHANGELOG.md b/packages/plugins/knowledge-memory/CHANGELOG.md index 5ebff4080e..407a887a2c 100644 --- a/packages/plugins/knowledge-memory/CHANGELOG.md +++ b/packages/plugins/knowledge-memory/CHANGELOG.md @@ -1,5 +1,41 @@ # @objectstack/knowledge-memory +## 15.1.0 + +### Patch Changes + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [99755b5] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [e07645c] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/core@15.1.0 + - @objectstack/service-knowledge@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/plugins/knowledge-memory/package.json b/packages/plugins/knowledge-memory/package.json index fd2005fe6f..8804f23d7a 100644 --- a/packages/plugins/knowledge-memory/package.json +++ b/packages/plugins/knowledge-memory/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/knowledge-memory", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "In-memory knowledge adapter for ObjectStack (dev / test reference implementation).", "main": "dist/index.js", diff --git a/packages/plugins/knowledge-ragflow/CHANGELOG.md b/packages/plugins/knowledge-ragflow/CHANGELOG.md index 89882d1a5e..ce6f35bdd5 100644 --- a/packages/plugins/knowledge-ragflow/CHANGELOG.md +++ b/packages/plugins/knowledge-ragflow/CHANGELOG.md @@ -1,5 +1,41 @@ # @objectstack/knowledge-ragflow +## 15.1.0 + +### Patch Changes + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [99755b5] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [e07645c] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/core@15.1.0 + - @objectstack/service-knowledge@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/plugins/knowledge-ragflow/package.json b/packages/plugins/knowledge-ragflow/package.json index 06aded2b32..80d38fd159 100644 --- a/packages/plugins/knowledge-ragflow/package.json +++ b/packages/plugins/knowledge-ragflow/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/knowledge-ragflow", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "RAGFlow knowledge adapter for ObjectStack — production-grade RAG via the Apache 2.0 RAGFlow REST API.", "main": "dist/index.js", diff --git a/packages/plugins/plugin-approvals/CHANGELOG.md b/packages/plugins/plugin-approvals/CHANGELOG.md index 530761bcc0..b8c9643452 100644 --- a/packages/plugins/plugin-approvals/CHANGELOG.md +++ b/packages/plugins/plugin-approvals/CHANGELOG.md @@ -1,5 +1,46 @@ # @objectstack/plugin-approvals +## 15.1.0 + +### Patch Changes + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [94b8e44] +- Updated dependencies [86c0aea] +- Updated dependencies [99755b5] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [eb89a8c] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [aead168] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/platform-objects@15.1.0 + - @objectstack/core@15.1.0 + - @objectstack/formula@15.1.0 + - @objectstack/metadata-core@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/plugins/plugin-approvals/package.json b/packages/plugins/plugin-approvals/package.json index d689eba28c..e80025b80c 100644 --- a/packages/plugins/plugin-approvals/package.json +++ b/packages/plugins/plugin-approvals/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/plugin-approvals", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "Multi-step approval engine for ObjectStack — sys_approval_process + sys_approval_request + sys_approval_action + IApprovalService.", "main": "dist/index.js", diff --git a/packages/plugins/plugin-audit/CHANGELOG.md b/packages/plugins/plugin-audit/CHANGELOG.md index 13ff83d77f..7cad656965 100644 --- a/packages/plugins/plugin-audit/CHANGELOG.md +++ b/packages/plugins/plugin-audit/CHANGELOG.md @@ -1,5 +1,66 @@ # @objectstack/plugin-audit +## 15.1.0 + +### Patch Changes + +- 55bc68d: ADR-0085 #2548 follow-ups surfaced by the real-backend browser pass: + + - **lint**: new `field-group-shadowed` warning in `validate-semantic-roles` — a + declared fieldGroup whose every visible member is hoisted into the detail + highlight strip (or is the record title) renders on forms but silently never + on detail pages (detail bodies hide the first 4 highlightFields). Warning + tier, same as the other semantic-role rules. + - **plugin-audit**: feed/audit summaries ("Created … / Deleted … / Updated …") + now name the object by its display label ("Semantic Zoo") instead of its API + name ("showcase_semantic_zoo") — these strings render verbatim in the record + Discussion feed and Setup dashboards. Falls back to the API name when the + object definition isn't resolvable. Existing stored rows are unchanged. + +- 21f75ce: Localize activity summaries to the workspace default locale (#3039). Activity + writers previously hardcoded English verbs and the object API name + (`Created person_qualification "OC-00001"`). The writer now resolves the + ADR-0053 `localization.locale` setting per write (memoized per tenant/user + scope), renders the verb through new `messages.activityCreated/Updated/Deleted` + i18n templates (en, zh-CN, ja-JP, es-ES shipped), and names the object by its + localized label (`objects.{name}.label`) with fallback to the authored def + label, then the API name. Missing i18n/settings services or bundle keys + degrade to the previous English summaries. +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [94b8e44] +- Updated dependencies [86c0aea] +- Updated dependencies [99755b5] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [eb89a8c] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [aead168] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/platform-objects@15.1.0 + - @objectstack/core@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/plugins/plugin-audit/package.json b/packages/plugins/plugin-audit/package.json index e90eb72f84..f8eae695bb 100644 --- a/packages/plugins/plugin-audit/package.json +++ b/packages/plugins/plugin-audit/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/plugin-audit", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "Audit Plugin for ObjectStack — System audit log object and audit trail", "main": "dist/index.js", diff --git a/packages/plugins/plugin-auth/CHANGELOG.md b/packages/plugins/plugin-auth/CHANGELOG.md index d273646af2..52d99b1185 100644 --- a/packages/plugins/plugin-auth/CHANGELOG.md +++ b/packages/plugins/plugin-auth/CHANGELOG.md @@ -1,5 +1,94 @@ # Changelog +## 15.1.0 + +### Patch Changes + +- 23925e9: fix(plugin-auth): re-run membership backfill when app seeding settles (#2996) + + The ADR-0093 D6 membership backfill — the only safety net for users created + by app seeds (raw `engine.insert` into `sys_user` bypasses better-auth's + `user.create.after` reconciler) — ran only once on `kernel:ready`. When a seed + bundle overruns its inline budget (`OS_INLINE_SEED_BUDGET_MS`, default 8s) it + finishes in the background _after_ `kernel:ready`, so its users stayed + member-less in single-org `auto` mode until the next restart re-ran the backfill. + + `AppPlugin` now emits a new **`app:seeded`** lifecycle event when an app's inline + seed settles (success, partial, or fallback) — carrying `{ appId, overBudget }`, + where `overBudget: true` marks the post-`kernel:ready` background case. plugin-auth + subscribes and re-runs the (idempotent, self-guarding, opt-out-able) + `backfillMemberships` on that signal, closing the window without waiting for a + restart. No behavior change when a seed completes within budget, in multi-tenant + mode, or under `invite-only` policy; `OS_SKIP_MEMBERSHIP_BACKFILL=1` still opts out. + +- aead168: fix(auth): align the better-auth family on 1.7.0-rc.1, implement the new adapter methods, and add the new sys_jwks columns (#2974) + + Remediating GHSA-p2fr-6hmx-4528 (`@better-auth/oauth-provider`) requires the + 1.7 plugin line, which imports `CLIENT_ASSERTION_TYPE` and other symbols that + only exist in `@better-auth/core` 1.7.x — so the whole better-auth family is + pinned to `1.7.0-rc.1` together (mixing a 1.7 plugin with 1.6.23 core 500s on + sign-in). better-auth 1.7 also extends its `CustomAdapter` contract with two + new methods, which the ObjectQL adapter now implements: + + - `consumeOne` — atomic single-row consume (find the guarded row, delete it, + return it), used by better-auth for single-use credential consumption + (e.g. verification tokens on the sign-in path). + - `incrementOne` — guarded counter mutation (`field = field + delta` per + `increment` entry plus any absolute `set` values), returning the updated row + or `null` when the guard matches nothing. + + Both are find-then-write mirrors of the existing `delete` / `update` methods + (ObjectQL exposes no native atomic primitive) and honour the same core/plugin + field-name bridging. + + better-auth 1.7 also extends its `jwks` model with two new optional columns, + `alg` (signing algorithm, e.g. `EdDSA`) and `crv` (curve, e.g. `Ed25519`), and + writes them when minting signing keys. The `sys_jwks` platform object gains the + matching fields — without them every JWKS write failed (`table sys_jwks has no +column named alg`), 500ing token signing and breaking session validation + (sign-in succeeded but every authenticated request 401'd). + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [94b8e44] +- Updated dependencies [86c0aea] +- Updated dependencies [99755b5] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [7bc9e79] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [eb89a8c] +- Updated dependencies [aeb2110] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [1c58abd] +- Updated dependencies [aead168] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [28ba0c7] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/rest@15.1.0 + - @objectstack/platform-objects@15.1.0 + - @objectstack/core@15.1.0 + - @objectstack/types@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/plugins/plugin-auth/package.json b/packages/plugins/plugin-auth/package.json index 0946e1a551..ad3c6a01f7 100644 --- a/packages/plugins/plugin-auth/package.json +++ b/packages/plugins/plugin-auth/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/plugin-auth", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "Authentication & Identity Plugin for ObjectStack", "main": "dist/index.js", diff --git a/packages/plugins/plugin-dev/CHANGELOG.md b/packages/plugins/plugin-dev/CHANGELOG.md index c91e817d0d..cddb3eda0f 100644 --- a/packages/plugins/plugin-dev/CHANGELOG.md +++ b/packages/plugins/plugin-dev/CHANGELOG.md @@ -1,5 +1,73 @@ # @objectstack/plugin-dev +## 15.1.0 + +### Patch Changes + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [86c0aea] +- Updated dependencies [541b96a] +- Updated dependencies [4c46ee0] +- Updated dependencies [99755b5] +- Updated dependencies [93bb7b4] +- Updated dependencies [c11e24b] +- Updated dependencies [e0b049a] +- Updated dependencies [e9a2885] +- Updated dependencies [ef50372] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [7bc9e79] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [aeb2110] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [3dc9fce] +- Updated dependencies [fae5dd0] +- Updated dependencies [23925e9] +- Updated dependencies [7f9a795] +- Updated dependencies [6613ad0] +- Updated dependencies [a16972b] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [1c58abd] +- Updated dependencies [aead168] +- Updated dependencies [dee7feb] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [28ba0c7] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [fdfe1d9] +- Updated dependencies [8b27dd7] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [464418e] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/runtime@15.1.0 + - @objectstack/objectql@15.1.0 + - @objectstack/rest@15.1.0 + - @objectstack/plugin-hono-server@15.1.0 + - @objectstack/core@15.1.0 + - @objectstack/plugin-security@15.1.0 + - @objectstack/plugin-auth@15.1.0 + - @objectstack/types@15.1.0 + - @objectstack/account@15.1.0 + - @objectstack/setup@15.1.0 + - @objectstack/driver-memory@15.1.0 + - @objectstack/service-i18n@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/plugins/plugin-dev/package.json b/packages/plugins/plugin-dev/package.json index 798f793f71..1644c64681 100644 --- a/packages/plugins/plugin-dev/package.json +++ b/packages/plugins/plugin-dev/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/plugin-dev", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "Development Mode Plugin for ObjectStack — auto-enables all services with in-memory implementations", "main": "dist/index.js", diff --git a/packages/plugins/plugin-email/CHANGELOG.md b/packages/plugins/plugin-email/CHANGELOG.md index 8d25ad3509..9b1c9e0ca7 100644 --- a/packages/plugins/plugin-email/CHANGELOG.md +++ b/packages/plugins/plugin-email/CHANGELOG.md @@ -1,5 +1,45 @@ # @objectstack/plugin-email +## 15.1.0 + +### Patch Changes + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [94b8e44] +- Updated dependencies [86c0aea] +- Updated dependencies [99755b5] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [eb89a8c] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [aead168] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/platform-objects@15.1.0 + - @objectstack/core@15.1.0 + - @objectstack/formula@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/plugins/plugin-email/package.json b/packages/plugins/plugin-email/package.json index 9d261986f7..4a2df5b92f 100644 --- a/packages/plugins/plugin-email/package.json +++ b/packages/plugins/plugin-email/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/plugin-email", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "Email service plugin for ObjectStack — IEmailService + transport-pluggable outbound delivery with sys_email persistence.", "main": "dist/index.js", diff --git a/packages/plugins/plugin-hono-server/CHANGELOG.md b/packages/plugins/plugin-hono-server/CHANGELOG.md index 897954018b..25ec171b9e 100644 --- a/packages/plugins/plugin-hono-server/CHANGELOG.md +++ b/packages/plugins/plugin-hono-server/CHANGELOG.md @@ -1,5 +1,147 @@ # @objectstack/plugin-hono-server +## 15.1.0 + +### Minor Changes + +- 541b96a: fix(security): enforce the anonymous-deny posture uniformly across HTTP surfaces (#2567) + + The ADR-0056 D2 `requireAuth` flip made REST `/data/*` deny-anonymous by + default, but three sibling surfaces reached ObjectQL without passing through the + gate — so the platform's anonymous posture was **inconsistent by surface**: an + anonymous caller denied on `/data` could read the same object data through a + different door. This closes the remaining two gaps (the `/meta` gate had already + landed) and pins every surface with a conformance row. + + - **Dispatcher GraphQL** (`runtime/http-dispatcher.ts`, `dispatcher-plugin.ts`): + `POST /graphql` reached `kernel.graphql`, whose security middleware falls + **open** for an anonymous context. `handleGraphQL` now applies the same + `requireAuth` gate as `/data` and `/meta`, resolving identity for the direct + route that does not flow through `dispatch()`. The dispatcher's `requireAuth` + default is aligned with the REST plugin's (`?? true`) so a bare host no longer + denies anonymous `/data` while serving the same rows over `/graphql`; an + explicit `requireAuth: false` opt-out is honoured and logs a boot warning. + + - **Raw-hono standard `/data` routes** (`plugin-hono-server/hono-plugin.ts`): + these delegate straight to ObjectQL and were only _shadowed_ when the REST + plugin registered the same paths first — so secure-by-default depended on + plugin registration order. Each route now consults `requireAuth` (secure by + default, mirroring `rest-server.ts`), making the deny decision a property of + this entry point too. Order no longer affects the anonymous posture. + + **Behaviour change:** on a `requireAuth` deployment (the secure default), + anonymous `POST /graphql` and anonymous raw-hono `/data` now return 401. + Deployments that intentionally serve these surfaces publicly set + `requireAuth: false` (a boot warning is logged). Proven end-to-end on the + platform default in `showcase-anonymous-deny-surfaces.dogfood.test.ts`, with + handler-level regression coverage in `http-dispatcher.requireauth.test.ts` and + `hono-anonymous-deny.test.ts`, and pinned by three new authz-conformance rows. + +### Patch Changes + +- 7f68068: feat(discovery): honest capabilities — standardized stub/fallback marker + realtime route honesty (ADR-0076 D12/A1.5 framework slice, #2462) + + **Spec** — new service self-description marker for honest discovery + (ADR-0076 D12): `SERVICE_SELF_INFO_KEY` (`__serviceInfo`), + `ServiceSelfInfoSchema` / `ServiceSelfInfo`, and `readServiceSelfInfo()`, + which also normalizes plugin-dev's legacy `_dev: true` flag to + `{ status: 'stub', handlerReady: false }`. A registered service that is a + stub / dev fake / degraded fallback self-identifies via this marker; a fully + real service carries no marker. + + **Runtime + metadata-protocol** — both discovery builders + (`HttpDispatcher.getDiscoveryInfo` and the protocol shim's `getDiscovery`) + now honor the marker instead of hardcoding `status: 'available', +handlerReady: true` for every registered service. Dev stubs report `stub`, + the ObjectQL analytics fallback reports `degraded` (it keeps serving — no + `/analytics` 404), and consumers can finally trust + `status === 'available'` / `handlerReady === true`. + + **Realtime honesty fix** — discovery no longer advertises a + `/realtime` route or `websockets: true`: `service-realtime` is an + in-process pub/sub bus, no dispatcher branch or plugin mounts any + `/realtime` HTTP surface, so the advertised route always 404'd. The + registered service now reports `status: 'degraded', handlerReady: false` + with no route (clients using the SDK are unaffected — it falls back to the + conventional path, which behaves exactly as before). Also corrects the + advertised realtime provider from the nonexistent `plugin-realtime` to + `service-realtime`. + + **REST (A1.5)** — the REST layer's protocol dependency is narrowed from the + `ObjectStackProtocol` god-union to the new `RestProtocol = +DataProtocol & MetadataProtocol` slice (exported from + `@objectstack/rest`), per the ADR-0076 D9 incremental narrowing guidance. + Type-level only; no runtime change. + +- 99755b5: refactor(security): converge the anonymous-deny decision into one shared function + a source-enumerating ratchet (#2567 Phase 2) + + Phase 1 gated every HTTP surface (REST `/data`, dispatcher `/graphql` + `/meta`, + raw-hono `/data`) against the secure-by-default `requireAuth` posture, but each + seam hand-rolled the same `!userId && !isSystem → 401` check. Phase 2 removes + that duplication and pins the surfaces so a new ungated entry point fails CI. + + - **New `shouldDenyAnonymous` in `@objectstack/core`** (`security/anonymous-deny.ts`) + — the single anonymous-deny decision + shared 401 body/constants, mirroring the + `auth-gate.ts` pattern (pure function so the seams can never drift). All five + seams — REST `enforceAuth`, dispatcher `handleGraphQL` / `handleMetadata` / + `handleAI`, hono `denyAnonymous` — now delegate to it. **Pure refactor: no + runtime behavior change** (verified by the unchanged Phase-1 handler + e2e + proofs). Identity resolution and the dynamic exemptions (public-form grants, + share-link tokens) are untouched — they run upstream and only ever hand the + seam an already-resolved context. + - **A `discover()` ratchet on the authz-conformance matrix** — it statically + enumerates the data/meta/graphql HTTP entry points from source (curated + per-file probes, control-plane routes excluded) and asserts each is classified + by a matrix `covers` key. A new `/data`/`/meta`/`/graphql` route (or a + removed/stale `covers`) now fails CI as UNCLASSIFIED / STALE, not in review. A + companion negative test proves the ratchet bites. + + A design trap is guarded: `isAuthGateAllowlisted(undefined)` returns `true`, so a + body-routed seam (GraphQL, which has no request path) must pass no path — the + shared function's non-empty-path guard denies anonymous unconditionally there, + never falling through to the control-plane allowlist. + +- ef50372: CORS default `allowHeaders` now includes `If-Match`. The REST record update + accepts the OCC token as an `If-Match` header (objectui's record-level inline + edit sends it on every save), but the preflight allow-list omitted it — so on + any split-origin deployment (console dev server against a backend on another + origin) the browser failed the preflight and every inline-edit save died with + "Failed to fetch". Found live while dogfooding objectui#2572; same + split-origin failure class as the #2548 Bearer fixes. Explicit user-supplied + `allowHeaders` still win unchanged. +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [99755b5] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [1c58abd] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/core@15.1.0 + - @objectstack/types@15.1.0 + - @objectstack/observability@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/plugins/plugin-hono-server/package.json b/packages/plugins/plugin-hono-server/package.json index 816e99c701..28ab9cfd6b 100644 --- a/packages/plugins/plugin-hono-server/package.json +++ b/packages/plugins/plugin-hono-server/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/plugin-hono-server", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "Standard Hono Server Adapter for ObjectStack Runtime", "main": "dist/index.js", diff --git a/packages/plugins/plugin-pinyin-search/CHANGELOG.md b/packages/plugins/plugin-pinyin-search/CHANGELOG.md new file mode 100644 index 0000000000..01a8360e8f --- /dev/null +++ b/packages/plugins/plugin-pinyin-search/CHANGELOG.md @@ -0,0 +1,33 @@ +# @objectstack/plugin-pinyin-search + +## 15.1.0 + +### Minor Changes + +- 1c58abd: Generic pinyin search recall (#2486, ADR-0098): a locale-gated + `OS_SEARCH_PINYIN_ENABLED` switch (auto-on when the stack configures any + `zh-*` locale) provisions a hidden `__search` companion column for each + object's display/name field at compile time, the new + `@objectstack/plugin-pinyin-search` fills it with full pinyin + initials + ("张伟" → "zhangwei zw") on before-save (plus boot backfill and a + `rebuildSearchCompanion` reconcile entry), and `$search` ORs the column in at + query time — so lookup pickers, list quick-search and ⌘K transparently match + `zhangwei` / `zw` against CJK names. Purely additive: `resolveSearchFields`, + `searchableFields`, drivers and non-Chinese deployments are untouched; FLS + restricted / secret / PII fields never feed the companion. + +### Patch Changes + +- Updated dependencies [7f68068] +- Updated dependencies [86c0aea] +- Updated dependencies [99755b5] +- Updated dependencies [e0b049a] +- Updated dependencies [e9a2885] +- Updated dependencies [464418e] +- Updated dependencies [6613ad0] +- Updated dependencies [a16972b] +- Updated dependencies [1c58abd] +- Updated dependencies [8b27dd7] + - @objectstack/objectql@15.1.0 + - @objectstack/core@15.1.0 + - @objectstack/types@15.1.0 diff --git a/packages/plugins/plugin-pinyin-search/package.json b/packages/plugins/plugin-pinyin-search/package.json index 80a8ddbbc3..7add16c6bc 100644 --- a/packages/plugins/plugin-pinyin-search/package.json +++ b/packages/plugins/plugin-pinyin-search/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/plugin-pinyin-search", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "Pinyin search recall for ObjectStack — populates the hidden `__search` companion column (full pinyin + initials of the display/name field) so `$search` hits CJK names typed as pinyin. Locale-gated via OS_SEARCH_PINYIN_ENABLED (#2486).", "main": "dist/index.js", diff --git a/packages/plugins/plugin-reports/CHANGELOG.md b/packages/plugins/plugin-reports/CHANGELOG.md index 9a0bfc8745..0496bb4ea2 100644 --- a/packages/plugins/plugin-reports/CHANGELOG.md +++ b/packages/plugins/plugin-reports/CHANGELOG.md @@ -1,5 +1,81 @@ # @objectstack/plugin-reports +## 15.1.0 + +### Patch Changes + +- e07645c: fix(security): close three execution-surface authz holes surfaced by the #2849 class sweep (#2980, #2981, #2982) + + Three independent, confirmed-exploitable defects where an execution surface + ignored the caller's identity or fell open on a missing one. Each is fixed at + its own enforcement point; none change behaviour for correctly-scoped callers. + + - **#2980 — reports IDOR + scheduled-report RLS bypass.** `ReportService` + discarded the caller's context and read/wrote `sys_saved_report` with a system + context, so any authenticated user could read, delete, or overwrite any saved + report by id (cross-owner / cross-tenant), and `listReports` enumerated all + owners. `getReport`/`deleteReport`/`saveReport`/`listReports` are now + owner-scoped (system read of the protection-locked metadata object, but + authorization enforced by owner match); create/overwrite can no longer spoof + ownership. Scheduled dispatch no longer runs `isSystem` (which emailed the + target object's entire table past the owner's RLS): it resolves the owner to a + real RLS-bearing context via a new `resolveOwnerContext` seam and **fails + closed** (skips + marks the schedule failed) when the owner can't be resolved, + rather than running elevated. Wiring that resolver is the reports-surface + consumer of ADR-0073's user-less identity resolution. + + - **#2981 — knowledge/RAG retrieval fall-open.** `applyPermissionFilter` returned + every hit when the context was missing _or_ system. A missing identity is no + longer treated as a grant: object-backed hits fail closed (dropped, keeping + ACL-less file/http hits), and only an **explicit** system context passes + through. Closes the agent path where an omitted `ToolExecutionContext.actor` + yielded unfiltered semantic search over the whole corpus. + + - **#2982 — bulk-write OWD gap.** `update({multi:true})` / `deleteMany` had no + single id to `canEdit`-gate, so owner scoping was skipped on private (and + public_read) objects. A new `SharingService.buildWriteFilter` (the edit-set + analogue of `buildReadFilter`) is AND-ed into the write AST for multi writes, + constraining them to rows the caller may edit — including the on-behalf-of + delegator intersection. + + Tracked as the motivating evidence of ADR-0096 (execution-surface identity + admission); the mechanism that would prevent the class structurally is separate. + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [94b8e44] +- Updated dependencies [86c0aea] +- Updated dependencies [99755b5] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [eb89a8c] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [aead168] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/platform-objects@15.1.0 + - @objectstack/core@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/plugins/plugin-reports/package.json b/packages/plugins/plugin-reports/package.json index 6e46cd30a1..f1fb6cd293 100644 --- a/packages/plugins/plugin-reports/package.json +++ b/packages/plugins/plugin-reports/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/plugin-reports", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "Saved reports + scheduled email digests for ObjectStack — sys_saved_report + sys_report_schedule + IReportService.", "main": "dist/index.js", diff --git a/packages/plugins/plugin-security/CHANGELOG.md b/packages/plugins/plugin-security/CHANGELOG.md index 2043d90dc7..e0873129b9 100644 --- a/packages/plugins/plugin-security/CHANGELOG.md +++ b/packages/plugins/plugin-security/CHANGELOG.md @@ -1,5 +1,243 @@ # @objectstack/plugin-security +## 15.1.0 + +### Minor Changes + +- 7f9a795: OWD posture is now enforced on the runtime write path (#3050). `metadata-protocol` gains the ADR-0094-addendum `registerAuthoringGate(type, gate)` seam — an awaited, throwing pre-persistence hook inside `saveMetaItem` (draft and publish-mode saves; environment writes only). `plugin-security` registers the `object` posture gate on it: an environment overlay of a packaged object may only TIGHTEN `sharingModel`/`externalSharingModel` (ADR-0086 D1 — closes the `OS_METADATA_WRITABLE=object` unvalidated-widening hole), and `externalSharingModel ≤ sharingModel` (ADR-0090 D11) is now rejected at save time instead of only by CLI lint. Write-path only — stored metadata keeps loading unchanged. + +### Patch Changes + +- 93bb7b4: fix(security): fail-closed sentinel for on-behalf-of reads on getReadFilter (#2852) + + `getReadFilter` (the read-scope provider the analytics/raw-SQL path binds to) + resolves only the caller's own ceiling — the ADR-0090 D10 delegator RLS + intersection that the engine middleware applies to find/count/aggregate is not + implemented on this path. Computing a filter here for a delegated (on-behalf-of) + context would therefore silently widen the read past the delegator's scope. + + Until the intersection is threaded through `computeRlsFilter` (tracked with + #2920 B1 / ADR-0095 D1), `getReadFilter` now denies fail-closed (deny sentinel + + error log) when `context.onBehalfOf.userId` is set. System on-behalf-of bypasses + ahead of the guard, and no agent surface reaches analytics today, so this is a + latent-invariant guard rather than a live-traffic behavior change. + +- 3dc9fce: feat(mcp): `aggregate_records` tool — GROUP BY aggregation over the engine read path + + New MCP tool `aggregate_records` (count/sum/avg/min/max/count_distinct, optional + groupBy incl. date bucketing, where filter, IANA timezone) in the `data:read` + family. Execution routes through the ObjectQL ENGINE (`callData('aggregate')` + deliberately never uses the raw per-env driver), so RLS/tenant scoping and the + D10 delegator intersection apply exactly as on find. + + Security hardening shipped with it: + + - plugin-security: new FLS aggregate-INPUT gate — result masking never runs for + `aggregate` (output rows carry only aliases), so any groupBy / aggregation + reference to an FLS-unreadable field is now rejected fail-closed with the + offending field names (mirrors the FLS write gate). + - runtime: `aggregate` maps to the `list` ApiMethod in the object exposure gate + (an object whose `apiMethods` whitelist excludes `list` cannot leak row + statistics through GROUP BY), and the aggregate action requires at least one + aggregation (the engine's in-memory path would otherwise degrade to raw rows + that the FLS masker does not cover). + + The bridge seam is optional: a runtime that does not implement + `McpDataBridge.aggregate` simply does not register the tool (graceful + degradation, same contract as the action tools). + +- 6613ad0: fix(security): exempt engine referential FK clears from the owner_id transfer guard (#3023) + + Follow-up to the #3004 ownership-anchor guard. `owner_id` is a lookup to `sys_user` + with the default `deleteBehavior: 'set_null'`, so deleting a `sys_user` makes + `cascadeDeleteRelations` null `owner_id` on every dependent row. That cascade write + re-entered the write middleware under the deleter's context, where the #3004 guard + read the `owner_id = null` as a user-initiated disown and denied it — aborting the + cascade mid-way (no transaction, so partial state) for any deleter without the + transfer grant on the child object (e.g. a member clearing a `public_read_write` + child that RLS would otherwise have allowed). + + The cascade FK clear is engine-mandated referential integrity consequent to an + already-authorized parent delete, not a user ownership change. `cascadeDeleteRelations` + now tags the `set_null` write with a server-derived `__referentialFieldClear` context + marker (set by the engine, never built from a request — same trust model as + `__expandRead`), and the ownership-anchor guard skips when that marker is present. + Ordinary user writes are unaffected; the marker cannot be forged from client input, + so it can never slip a real ownership transfer past the guard. + +- a16972b: fix(security): guard the `owner_id` ownership anchor and scope bulk writes to owner-visible rows (#3004, #2982) + + Two write-path holes on the row-ownership anchor (`owner_id`), the column OWD + row-level scoping keys off to decide who may update/delete a record. + + - **#3004 — client-writable, unguarded `owner_id`.** The anchor is deliberately + not `readonly` (ownership is transferable), so the static-readonly strip never + covered it and FLS doesn't gate it by default. A non-privileged writer could + therefore `insert` a record under someone else's name (forge) or `update` one + to a new owner (transfer / disown), evading the owner gate that governs + update/delete. The security middleware (plugin-security step 3.5) now treats + `owner_id` as system-managed for non-privileged writers: on insert an empty + value is auto-stamped to the acting user (batch rows too — previously only the + single-record path stamped, leaving bulk-inserted rows NULL-owned and + invisible to their creator), and a supplied foreign owner is denied; on update + a supplied `owner_id` is a transfer/disown and is denied — the unchanged no-op + echo of a form save is tolerated via a pre-image compare, and a bulk + change-set carrying `owner_id` fails closed. A non-scalar `owner_id` + (array/object) is rejected outright rather than string-coerced, and the + change-set membership test uses own-property semantics so a polluted + prototype cannot spoof an ownership write. Both require the transfer grant + (`allowTransfer`, or `modifyAllRecords` which implies it) to proceed. System + context (`ctx.isSystem`) stays fully exempt (OAuth provisioning / cron + snapshots / seed claims / migrations), and under delegation both principals + must hold the grant (ADR-0090 D10 intersection). Note a REST **import** runs + under the importer's own context (not `isSystem`), so a non-privileged user + importing a CSV whose `owner_id` column names other users is correctly denied + unless they hold the transfer grant — administrators (who carry + `modifyAllRecords`) are unaffected. + + - **#2982 — bulk writes skipped owner scoping on OWD-`private` objects.** A + `update({ multi: true })` / bulk delete rebuilt the driver AST from + `options.where` AFTER the middleware chain, discarding the owner/RLS write + filter that plugin-sharing (`buildWriteFilter`) and plugin-security compose + onto `opCtx.ast` — so a member's bulk write hit every matching row, including + peers'. The engine now seeds `opCtx.ast` from the caller's predicate BEFORE the + chain (the same seam reads use) and hands the middleware-composed AST to + `driver.updateMany` / `driver.deleteMany`, so bulk writes are constrained to the + rows the caller may edit — matching single-id write behavior. `delete` now + applies the same scalar-`id` guard `update` already had, so an id-list bulk + delete (`where: { id: { $in: […] } }, multi: true`) is owner-scoped too, and + both multi branches fail CLOSED (throw) rather than silently rebuilding an + unscoped predicate if the row-scoping AST is ever absent. + + Consequences of routing bulk writes through the AST: the anti-oracle + predicate guard now also applies to bulk `update`/`delete` (a bulk write + filtering on an FLS-unreadable field is rejected, as reads already are), and a + principal-less (no-`userId`, non-system) bulk write on an owner-scoped object + now correctly affects zero rows instead of all of them. + + Proven end-to-end on the real showcase app + (`packages/qa/dogfood/test/owner-anchor-and-bulk-writes.dogfood.test.ts`) and pinned + in the ADR-0096 authz-conformance ledger (`ownership-anchor-guard`, + `bulk-write-owner-scoping`). + +- dee7feb: fix(security): scope the bulk-write predicate guard to the caller's own filter, and dedupe pre-image reads (#3018 review follow-ups) + + Two hardening follow-ups from the #3018 adversarial review. + + **Predicate guard is now middleware-order-independent for writes.** #2982 made bulk + `update`/`delete` carry an `opCtx.ast`, which brought them under the step-2.9 + anti-oracle predicate guard for the first time. That guard is documented to run + against the _caller's own_ predicate — RLS / sharing filters legitimately reference + fields the caller cannot read (e.g. `owner_id`). But for a bulk write it inspected + `opCtx.ast.where`, which a sibling middleware (`plugin-sharing`) may have already had + an `owner_id` owner-match composed into — and the two middlewares' registration order + is not contractually guaranteed. On an object whose `owner_id` is FLS-hidden, that + could 403 a legitimate bulk write purely because the injected filter named the field. + The guard now inspects `opCtx.options.where` (the caller's untouched predicate) for + `update`/`delete`, so it can never mistake an injected owner/RLS filter for a caller + probe, independent of middleware order. Reads are unchanged (the read seed is the + caller's query verbatim and the guard runs before this middleware's own injection). + + **Pre-image reads deduplicated.** The by-id "read the target row" pattern was inlined + at ~5 gates with slightly divergent shapes; a single `readRowById` helper (fail-closed: + missing engine / null id / thrown read → `null`, which always denies) now backs the + provenance gates, and a memoized `getCallerPreImage` collapses the owner-anchor echo + check (3.5) and the RLS `check` post-image (3.6) — which read the identical + `(object, id, caller-context)` row — into one read per operation. No behavior change; + the read shape can no longer drift across sites. + +- aaec5db: fix(security): public-form submissions can no longer forge server-managed anchors (#3022) + + The anonymous public-form surface (ADR-0056 Option A, `POST /forms/:slug/submit`) + is authorized by the declaration-derived `publicFormGrant`, which short-circuits + the security middleware BEFORE every write gate (CRUD, FLS, the owner anchor + guard, the tenant CHECK). The only field-side defense was the route's + declared-field allow-list — and a FormView with zero declared section fields + fell back to merging the raw body wholesale, so an unauthenticated visitor + could `POST owner_id=` (or `organization_id`, audit columns, `id`) and + attach the record to another user or tenant — the #3004 insert-forge, with no + credentials at all. + + Server-managed anchors are now enforced on this surface at BOTH layers, from a + single shared definition (`PUBLIC_FORM_SERVER_MANAGED_FIELDS`, new in + `@objectstack/spec/security`): + + - **Data layer (authoritative)** — the `publicFormGrant` branch in + `@objectstack/plugin-security` strips `id` / `owner_id` / `organization_id` / + `tenant_id` / audit columns / soft-delete state / `__search` from every row + of a granted insert (batch included) before admitting the write, so the + boundary holds no matter what any route lets through. Ownership stays NULL + for object hooks / the first-admin bootstrap to assign, as for other + anonymous-seeded rows. + - **Route layer** — the submit allow-list excludes the same set + unconditionally: an explicitly declared `owner_id` section field no longer + passes, and the zero-declared-sections fallback keeps its documented + all-fields behavior for business columns while refusing the managed set. + The resolve route (`GET /forms/:slug`) drops the managed fields from the + rendered sections and the embedded object schema so a form never collects a + value the submit refuses, and `GET /forms/:slug/lookup/:field` refuses a + `publicPicker` declared on a managed anchor (which would have opened + anonymous `sys_user` search through `owner_id`). + + Authenticated writes are unaffected — this is the anonymous-surface rule only; + `owner_id` transfer semantics for signed-in callers stay governed by the + transfer grant (#3004 / PR #3018). + +- 8b27dd7: fix(security): enforce referenced-object RLS/FLS on $expand (#2850) + + `expandRelatedRecords` resolved lookup/master_detail/user references via the + driver directly, so the referenced object's row- and field-level security never + ran — any API/session caller who could read a base row could `?expand=` a + foreign key and receive RLS-hidden rows and FLS-masked fields (tenant isolation + was the only surviving boundary). + + The expand batch now routes through the engine's own `find`, so the security + middleware applies the referenced object's RLS + FLS to the `id $in [...]` batch + (one query per level, no N+1). The sub-read carries a server-set `__expandRead` + marker: the middleware waives only the object-level CRUD / requiredPermissions + gate for PUBLIC referenced objects (already broadly readable — avoids + over-blocking common status/owner lookups), while PRIVATE referenced objects + keep the full gate. Covers the list and single-record REST/protocol surfaces. + +- 28ba0c7: fix(plugin-security): stop clobbering admin-edited capability `scope` on boot (#2909 T3). `scope` is an admin-editable classification select on sys_capability, but the curated seeder refreshed it on every boot alongside label/description — silently reverting admin reclassifications. It is now seed-once: written on insert, never refreshed (a curated scope change in a new platform version requires a data migration; recorded in the ADR-0094 addendum). +- 28ba0c7: fix(plugin-security): re-arm the sys_position system-row write gate after the A4 managed_by rename (#2926 ①). The gate's provenance map still keyed on the legacy `system`/`config` values while rows are now stamped (and boot-normalized to) `platform`/`package`, so platform/package-managed positions — including the `everyone`/`guest` audience anchors — could be physically deleted through the data API once their bindings were removed. The map now guards both the canonical and legacy vocabularies, and the misleading "no runtime path branches on legacy values" safety notes were corrected. +- 464418e: fix(plugin-security): bind the fallback permission set to the `everyone` anchor AFTER the anchor is seeded. The baseline auto-bind (ADR-0090 D5) ran earlier in `runBootstrap` than `bootstrapBuiltinRoles`, which creates the `everyone` position — so the `everyone` lookup returned nothing and the app's `isDefault` set was never bound, leaving a fresh deploy's `everyone` empty (personas silently degraded) and a redundant `sys_audience_binding_suggestion` filed for the same set. The auto-bind now runs after `bootstrapBuiltinRoles` and before `syncAudienceBindingSuggestions`, so the documented app-level auto-bind actually happens and the suggestion sync correctly skips the already-bound set. +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [94b8e44] +- Updated dependencies [86c0aea] +- Updated dependencies [99755b5] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [eb89a8c] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [aead168] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/platform-objects@15.1.0 + - @objectstack/core@15.1.0 + - @objectstack/formula@15.1.0 + ## 15.0.0 ### Major Changes diff --git a/packages/plugins/plugin-security/package.json b/packages/plugins/plugin-security/package.json index 2eab2010dd..5491d98fbb 100644 --- a/packages/plugins/plugin-security/package.json +++ b/packages/plugins/plugin-security/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/plugin-security", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "Security Plugin for ObjectStack — RBAC, RLS, and Field-Level Security Runtime", "main": "dist/index.js", diff --git a/packages/plugins/plugin-sharing/CHANGELOG.md b/packages/plugins/plugin-sharing/CHANGELOG.md index 052a8cfa84..f4b24c8f04 100644 --- a/packages/plugins/plugin-sharing/CHANGELOG.md +++ b/packages/plugins/plugin-sharing/CHANGELOG.md @@ -1,5 +1,150 @@ # @objectstack/plugin-sharing +## 15.1.0 + +### Minor Changes + +- 28ba0c7: feat(plugin-sharing): sys_sharing_rule provenance + seed-not-clobber (#2909 P0/T1). The object gains readonly `managed_by` (unified A4 tri-state platform/package/admin) and `customized` columns; declared rules seed with `managed_by: 'package'`. defineRule in seed mode adopts pristine/legacy rows (package upgrades stay deliverable) but never overwrites admin-authored or customized rows — an admin's `active: false` on an over-sharing rule now survives redeploys instead of being resurrected at boot. A beforeUpdate hook stamps `customized` on any non-system edit of a seeded rule. Deliberately NO write gate: sharing rules remain a first-class admin authoring surface (ADR-0094 addendum tradeoff). + +### Patch Changes + +- 464418e: feat(kernel): add `kernel:bootstrapped` lifecycle anchor — the phase that fires after every `kernel:ready` handler has settled but before `kernel:listening` (HTTP socket open). `kernel:ready` handlers run sequentially in plugin-registration order, so a handler that consumes data produced by a later-starting plugin (e.g. the security bootstrap seeds `sys_position`; the app plugin's seed loader inserts records) would race the very rows it needs. `kernel:bootstrapped` is the correct anchor for reconcile/backfill work: every producer's ready handler has finished by the time it fires. Both `ObjectKernel` and `LiteKernel` trigger it. The sharing-rule boot backfill moves from `kernel:listening` to `kernel:bootstrapped` (semantics-only; behaviour unchanged). +- a16972b: fix(security): guard the `owner_id` ownership anchor and scope bulk writes to owner-visible rows (#3004, #2982) + + Two write-path holes on the row-ownership anchor (`owner_id`), the column OWD + row-level scoping keys off to decide who may update/delete a record. + + - **#3004 — client-writable, unguarded `owner_id`.** The anchor is deliberately + not `readonly` (ownership is transferable), so the static-readonly strip never + covered it and FLS doesn't gate it by default. A non-privileged writer could + therefore `insert` a record under someone else's name (forge) or `update` one + to a new owner (transfer / disown), evading the owner gate that governs + update/delete. The security middleware (plugin-security step 3.5) now treats + `owner_id` as system-managed for non-privileged writers: on insert an empty + value is auto-stamped to the acting user (batch rows too — previously only the + single-record path stamped, leaving bulk-inserted rows NULL-owned and + invisible to their creator), and a supplied foreign owner is denied; on update + a supplied `owner_id` is a transfer/disown and is denied — the unchanged no-op + echo of a form save is tolerated via a pre-image compare, and a bulk + change-set carrying `owner_id` fails closed. A non-scalar `owner_id` + (array/object) is rejected outright rather than string-coerced, and the + change-set membership test uses own-property semantics so a polluted + prototype cannot spoof an ownership write. Both require the transfer grant + (`allowTransfer`, or `modifyAllRecords` which implies it) to proceed. System + context (`ctx.isSystem`) stays fully exempt (OAuth provisioning / cron + snapshots / seed claims / migrations), and under delegation both principals + must hold the grant (ADR-0090 D10 intersection). Note a REST **import** runs + under the importer's own context (not `isSystem`), so a non-privileged user + importing a CSV whose `owner_id` column names other users is correctly denied + unless they hold the transfer grant — administrators (who carry + `modifyAllRecords`) are unaffected. + + - **#2982 — bulk writes skipped owner scoping on OWD-`private` objects.** A + `update({ multi: true })` / bulk delete rebuilt the driver AST from + `options.where` AFTER the middleware chain, discarding the owner/RLS write + filter that plugin-sharing (`buildWriteFilter`) and plugin-security compose + onto `opCtx.ast` — so a member's bulk write hit every matching row, including + peers'. The engine now seeds `opCtx.ast` from the caller's predicate BEFORE the + chain (the same seam reads use) and hands the middleware-composed AST to + `driver.updateMany` / `driver.deleteMany`, so bulk writes are constrained to the + rows the caller may edit — matching single-id write behavior. `delete` now + applies the same scalar-`id` guard `update` already had, so an id-list bulk + delete (`where: { id: { $in: […] } }, multi: true`) is owner-scoped too, and + both multi branches fail CLOSED (throw) rather than silently rebuilding an + unscoped predicate if the row-scoping AST is ever absent. + + Consequences of routing bulk writes through the AST: the anti-oracle + predicate guard now also applies to bulk `update`/`delete` (a bulk write + filtering on an FLS-unreadable field is rejected, as reads already are), and a + principal-less (no-`userId`, non-system) bulk write on an owner-scoped object + now correctly affects zero rows instead of all of them. + + Proven end-to-end on the real showcase app + (`packages/qa/dogfood/test/owner-anchor-and-bulk-writes.dogfood.test.ts`) and pinned + in the ADR-0096 authz-conformance ledger (`ownership-anchor-guard`, + `bulk-write-owner-scoping`). + +- e07645c: fix(security): close three execution-surface authz holes surfaced by the #2849 class sweep (#2980, #2981, #2982) + + Three independent, confirmed-exploitable defects where an execution surface + ignored the caller's identity or fell open on a missing one. Each is fixed at + its own enforcement point; none change behaviour for correctly-scoped callers. + + - **#2980 — reports IDOR + scheduled-report RLS bypass.** `ReportService` + discarded the caller's context and read/wrote `sys_saved_report` with a system + context, so any authenticated user could read, delete, or overwrite any saved + report by id (cross-owner / cross-tenant), and `listReports` enumerated all + owners. `getReport`/`deleteReport`/`saveReport`/`listReports` are now + owner-scoped (system read of the protection-locked metadata object, but + authorization enforced by owner match); create/overwrite can no longer spoof + ownership. Scheduled dispatch no longer runs `isSystem` (which emailed the + target object's entire table past the owner's RLS): it resolves the owner to a + real RLS-bearing context via a new `resolveOwnerContext` seam and **fails + closed** (skips + marks the schedule failed) when the owner can't be resolved, + rather than running elevated. Wiring that resolver is the reports-surface + consumer of ADR-0073's user-less identity resolution. + + - **#2981 — knowledge/RAG retrieval fall-open.** `applyPermissionFilter` returned + every hit when the context was missing _or_ system. A missing identity is no + longer treated as a grant: object-backed hits fail closed (dropped, keeping + ACL-less file/http hits), and only an **explicit** system context passes + through. Closes the agent path where an omitted `ToolExecutionContext.actor` + yielded unfiltered semantic search over the whole corpus. + + - **#2982 — bulk-write OWD gap.** `update({multi:true})` / `deleteMany` had no + single id to `canEdit`-gate, so owner scoping was skipped on private (and + public_read) objects. A new `SharingService.buildWriteFilter` (the edit-set + analogue of `buildReadFilter`) is AND-ed into the write AST for multi writes, + constraining them to rows the caller may edit — including the on-behalf-of + delegator intersection. + + Tracked as the motivating evidence of ADR-0096 (execution-surface identity + admission); the mechanism that would prevent the class structurally is separate. + +- 28ba0c7: fix(plugin-sharing): reconcile every active sharing rule once at boot (#2926 ③). Rule grants are materialized by write hooks, which deliberately skip `isSystem` writes — so seed-loader records never produced `sys_record_share` rows and demo data shipping with matching sharing rules was broken out of the box until each record was touched at runtime. The boot backfill runs on `kernel:listening` — the phase the kernel fires only after every `kernel:ready` handler has settled, including the AppPlugin seed loader — so the reconcile sees the seeded rows rather than racing them. It is idempotent (diff-based reconcile) and best-effort per rule so one broken rule cannot block startup. +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [94b8e44] +- Updated dependencies [86c0aea] +- Updated dependencies [99755b5] +- Updated dependencies [c11e24b] +- Updated dependencies [e0b049a] +- Updated dependencies [e9a2885] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [eb89a8c] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [6613ad0] +- Updated dependencies [a16972b] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [1c58abd] +- Updated dependencies [aead168] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [8b27dd7] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/objectql@15.1.0 + - @objectstack/platform-objects@15.1.0 + - @objectstack/core@15.1.0 + - @objectstack/formula@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/plugins/plugin-sharing/package.json b/packages/plugins/plugin-sharing/package.json index 25a5a4efcc..dd2f5ce970 100644 --- a/packages/plugins/plugin-sharing/package.json +++ b/packages/plugins/plugin-sharing/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/plugin-sharing", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "Record-level sharing for ObjectStack — sys_record_share + middleware that enforces sharingModel + ISharingService.", "main": "dist/index.js", diff --git a/packages/plugins/plugin-webhooks/CHANGELOG.md b/packages/plugins/plugin-webhooks/CHANGELOG.md index 7543025bbc..efd580a933 100644 --- a/packages/plugins/plugin-webhooks/CHANGELOG.md +++ b/packages/plugins/plugin-webhooks/CHANGELOG.md @@ -1,5 +1,40 @@ # @objectstack/plugin-webhooks +## 15.1.0 + +### Patch Changes + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [99755b5] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/core@15.1.0 + - @objectstack/service-messaging@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/plugins/plugin-webhooks/package.json b/packages/plugins/plugin-webhooks/package.json index d8f6d97399..8a291ddf72 100644 --- a/packages/plugins/plugin-webhooks/package.json +++ b/packages/plugins/plugin-webhooks/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/plugin-webhooks", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "Persistent, cluster-aware webhook dispatcher. Durable outbox + per-partition cluster.lock for exactly-once-ish delivery across nodes. See content/docs/concepts/webhook-delivery.mdx.", "type": "module", diff --git a/packages/qa/dogfood/CHANGELOG.md b/packages/qa/dogfood/CHANGELOG.md index 7673d11088..72f84ac615 100644 --- a/packages/qa/dogfood/CHANGELOG.md +++ b/packages/qa/dogfood/CHANGELOG.md @@ -1,5 +1,70 @@ # @objectstack/dogfood +## 0.0.36 + +### Patch Changes + +- Updated dependencies [7f68068] +- Updated dependencies [55bc68d] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [e97885b] +- Updated dependencies [0289d5a] +- Updated dependencies [94b8e44] +- Updated dependencies [d49deb5] +- Updated dependencies [86c0aea] +- Updated dependencies [93bb7b4] +- Updated dependencies [c11e24b] +- Updated dependencies [e0b049a] +- Updated dependencies [e9a2885] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [3dc9fce] +- Updated dependencies [23925e9] +- Updated dependencies [7f9a795] +- Updated dependencies [6613ad0] +- Updated dependencies [a16972b] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [1c58abd] +- Updated dependencies [21f75ce] +- Updated dependencies [aead168] +- Updated dependencies [dee7feb] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [8b27dd7] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [464418e] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [7c800c6] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/objectql@15.1.0 + - @objectstack/plugin-audit@15.1.0 + - @objectstack/connector-rest@15.1.0 + - @objectstack/connector-openapi@15.1.0 + - @objectstack/connector-mcp@15.1.0 + - @objectstack/service-storage@15.1.0 + - @objectstack/verify@15.1.0 + - @objectstack/plugin-security@15.1.0 + - @objectstack/plugin-auth@15.1.0 + - @objectstack/example-crm@4.0.88 + - @objectstack/example-showcase@0.3.10 + ## 0.0.35 ### Patch Changes diff --git a/packages/qa/dogfood/package.json b/packages/qa/dogfood/package.json index 9ba3b743e1..cb10b11d90 100644 --- a/packages/qa/dogfood/package.json +++ b/packages/qa/dogfood/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/dogfood", - "version": "0.0.35", + "version": "0.0.36", "private": true, "license": "Apache-2.0", "description": "Dogfood regression gate — hand-written golden tests that boot real example apps through @objectstack/verify's in-process HTTP stack, pinning historical runtime regressions (#2018 timezone bucketing, #1994 cross-owner RLS, #2004 field fidelity) that static checks miss.", diff --git a/packages/qa/downstream-contract/CHANGELOG.md b/packages/qa/downstream-contract/CHANGELOG.md index 646cc387ad..13609edec8 100644 --- a/packages/qa/downstream-contract/CHANGELOG.md +++ b/packages/qa/downstream-contract/CHANGELOG.md @@ -1,5 +1,37 @@ # @objectstack/downstream-contract +## 0.0.34 + +### Patch Changes + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + ## 0.0.33 ### Patch Changes diff --git a/packages/qa/downstream-contract/package.json b/packages/qa/downstream-contract/package.json index bac3eb18b4..825e874298 100644 --- a/packages/qa/downstream-contract/package.json +++ b/packages/qa/downstream-contract/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/downstream-contract", - "version": "0.0.33", + "version": "0.0.34", "description": "Frozen third-party consumer fixture — a backward-compatibility gate for @objectstack/spec. Authored the way an external project on a published release authors metadata; if a spec change breaks it, that change is breaking (#2035).", "license": "Apache-2.0", "private": true, diff --git a/packages/qa/http-conformance/CHANGELOG.md b/packages/qa/http-conformance/CHANGELOG.md new file mode 100644 index 0000000000..721e53c6ba --- /dev/null +++ b/packages/qa/http-conformance/CHANGELOG.md @@ -0,0 +1,9 @@ +# @objectstack/http-conformance + +## 0.0.2 + +### Patch Changes + +- Updated dependencies [99755b5] +- Updated dependencies [464418e] + - @objectstack/core@15.1.0 diff --git a/packages/qa/http-conformance/package.json b/packages/qa/http-conformance/package.json index 89779e46b3..4ef0d4cadb 100644 --- a/packages/qa/http-conformance/package.json +++ b/packages/qa/http-conformance/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/http-conformance", - "version": "0.0.1", + "version": "0.0.2", "private": true, "license": "Apache-2.0", "description": "HTTP transport-port conformance gate (ADR-0076 D11/OQ#10, #2462) — a zero-dependency node:http reference implementation of IHttpServer plus a cross-adapter suite that boots the dispatcher bridge and REST generator on it AND on plugin-hono-server, pinning that the port stays free of framework-isms. Not published; validation instrument, not a product server.", diff --git a/packages/rest/CHANGELOG.md b/packages/rest/CHANGELOG.md index 78d5f41004..e86305b9cf 100644 --- a/packages/rest/CHANGELOG.md +++ b/packages/rest/CHANGELOG.md @@ -1,5 +1,243 @@ # @objectstack/rest +## 15.1.0 + +### Patch Changes + +- 7f68068: feat(discovery): honest capabilities — standardized stub/fallback marker + realtime route honesty (ADR-0076 D12/A1.5 framework slice, #2462) + + **Spec** — new service self-description marker for honest discovery + (ADR-0076 D12): `SERVICE_SELF_INFO_KEY` (`__serviceInfo`), + `ServiceSelfInfoSchema` / `ServiceSelfInfo`, and `readServiceSelfInfo()`, + which also normalizes plugin-dev's legacy `_dev: true` flag to + `{ status: 'stub', handlerReady: false }`. A registered service that is a + stub / dev fake / degraded fallback self-identifies via this marker; a fully + real service carries no marker. + + **Runtime + metadata-protocol** — both discovery builders + (`HttpDispatcher.getDiscoveryInfo` and the protocol shim's `getDiscovery`) + now honor the marker instead of hardcoding `status: 'available', +handlerReady: true` for every registered service. Dev stubs report `stub`, + the ObjectQL analytics fallback reports `degraded` (it keeps serving — no + `/analytics` 404), and consumers can finally trust + `status === 'available'` / `handlerReady === true`. + + **Realtime honesty fix** — discovery no longer advertises a + `/realtime` route or `websockets: true`: `service-realtime` is an + in-process pub/sub bus, no dispatcher branch or plugin mounts any + `/realtime` HTTP surface, so the advertised route always 404'd. The + registered service now reports `status: 'degraded', handlerReady: false` + with no route (clients using the SDK are unaffected — it falls back to the + conventional path, which behaves exactly as before). Also corrects the + advertised realtime provider from the nonexistent `plugin-realtime` to + `service-realtime`. + + **REST (A1.5)** — the REST layer's protocol dependency is narrowed from the + `ObjectStackProtocol` god-union to the new `RestProtocol = +DataProtocol & MetadataProtocol` slice (exported from + `@objectstack/rest`), per the ADR-0076 D9 incremental narrowing guidance. + Type-level only; no runtime change. + +- 86c0aea: feat(attachments): sys_file orphan lifecycle + parent-derived attachment access (#2755) + + **Orphan lifecycle (ADR-0057).** Deleting a `sys_attachment` join row used to + orphan the backing `sys_file` row and its storage bytes forever. `sys_file` + now declares a lifecycle (`ttl 30d` on a new `deleted_at` tombstone for + orphans; `retention 7d onlyWhen status=pending` for abandoned uploads), the + storage plugin's new hooks tombstone a file when its LAST join row is deleted + (attachments scope only — `Field.file`/`Field.image`/avatar scopes are never + touched) and un-tombstone on re-attach, and a new LifecycleService **reap + guard** seam (`registerReapGuard`) re-verifies zero references at sweep time + and deletes the storage bytes before confirming each row reap. A guarded + object is never blind-deleted; an erroring guard fails safe (rows retained). + + **Attachment access (ADR-0049, Salesforce parent-derived semantics).** + `sys_attachment` create now requires caller READ visibility of the parent + record (403 `ATTACHMENT_PARENT_ACCESS`) and server-stamps `uploaded_by` from + the session (client value ignored); delete requires uploader-or-parent-editor + (403 `ATTACHMENT_DELETE_DENIED`). The storage upload routes require an + authenticated session when an auth service is wired (401 `AUTH_REQUIRED`; + bare kernels stay open) and stamp `owner_id` on new files. + + **REMOVED — `sys_attachment.share_type` / `sys_attachment.visibility`.** + Both fields were modeled in v1 with zero runtime consumers (ADR-0049 + parsed-but-unenforced). There is no replacement key: attachment access is + derived from the parent record by the hooks above. Writers of these fields + should simply stop sending them (unknown-field validation will reject them); + existing DB columns are left as unmanaged leftovers, no migration needed. + + `@objectstack/verify` gains `BootOptions.extraPlugins` for booting optional + service pairs (e.g. storage + audit) in dogfood fixtures. + +- 99755b5: refactor(security): converge the anonymous-deny decision into one shared function + a source-enumerating ratchet (#2567 Phase 2) + + Phase 1 gated every HTTP surface (REST `/data`, dispatcher `/graphql` + `/meta`, + raw-hono `/data`) against the secure-by-default `requireAuth` posture, but each + seam hand-rolled the same `!userId && !isSystem → 401` check. Phase 2 removes + that duplication and pins the surfaces so a new ungated entry point fails CI. + + - **New `shouldDenyAnonymous` in `@objectstack/core`** (`security/anonymous-deny.ts`) + — the single anonymous-deny decision + shared 401 body/constants, mirroring the + `auth-gate.ts` pattern (pure function so the seams can never drift). All five + seams — REST `enforceAuth`, dispatcher `handleGraphQL` / `handleMetadata` / + `handleAI`, hono `denyAnonymous` — now delegate to it. **Pure refactor: no + runtime behavior change** (verified by the unchanged Phase-1 handler + e2e + proofs). Identity resolution and the dynamic exemptions (public-form grants, + share-link tokens) are untouched — they run upstream and only ever hand the + seam an already-resolved context. + - **A `discover()` ratchet on the authz-conformance matrix** — it statically + enumerates the data/meta/graphql HTTP entry points from source (curated + per-file probes, control-plane routes excluded) and asserts each is classified + by a matrix `covers` key. A new `/data`/`/meta`/`/graphql` route (or a + removed/stale `covers`) now fails CI as UNCLASSIFIED / STALE, not in review. A + companion negative test proves the ratchet bites. + + A design trap is guarded: `isAuthGateAllowlisted(undefined)` returns `true`, so a + body-routed seam (GraphQL, which has no request path) must pass no path — the + shared function's non-empty-path guard denies anonymous unconditionally there, + never falling through to the control-plane allowlist. + +- c11e24b: fix(authz): carry the derived posture rung on ExecutionContext (#2947) + + The ADR-0095 D2 posture ladder (`PLATFORM_ADMIN > TENANT_ADMIN > MEMBER > +EXTERNAL`) is derived once by the shared authz resolver from capability grants, + but both HTTP/MCP entry points that build the `ExecutionContext` dropped it — + so any enforcement-side reader of `context.posture` always saw `undefined` + (the same drop that forced the explain layer to re-derive it, #2949). + + `ExecutionContextSchema` now carries an optional `posture` field, and both + `rest-server` and the runtime `resolveExecutionContext` plumb the resolver's + value through. Additive and **behavior-preserving**: no enforcement decision + consumes `posture` yet — whether the hot path evaluates _by_ posture remains a + larger ADR-level decision — this only stops the already-computed value from + being discarded, so enforcement and explain read the same derived rung. + +- 7bc9e79: fix(import): make async import-job cancellation actually stop the worker (#2824) + + Cancelling a running async import used to have no effect on a synchronous + storage driver (better-sqlite3 / wasm fallback): every `await` in the row + loop resolved as a microtask, so a 50k-row import monopolized the Node event + loop for minutes — the cancel route's HTTP handler (and every progress poll) + could never run, so the in-memory flag `shouldCancel` polls was never set. + The job then finished `succeeded` with all rows written despite the user's + cancel. + + Three-part fix: + + - **`runImport` yields one macrotask at every progress boundary** (every + `progressEvery` rows), so pending I/O — the cancel request, progress + polls, any other traffic — gets serviced during a large import. This is + the root-cause fix; it also unblocks progress polling for the wizard. + - **The worker's `shouldCancel` now also reads the durable job row** as a + fallback: a cancel accepted by another process (or after a restart + dropped the in-memory flag) still stops the worker. + - **A late cancel wins the terminal state**: the worker's final patch no + longer overwrites the cancel route's durable `cancelled` with + `succeeded`, and a job cancelled while still `pending` doesn't start at + all. Counts stay truthful — they reflect what was actually written. + +- aeb2110: fix(rest): split multi-value fields on import so `multiple: true` columns resolve per-token (#3063) + + The bulk-import coercion (`import-coerce.ts`) resolved a reference cell as a + single value regardless of the field's `multiple` flag: a `multiple: true` + lookup/user cell like `张焊工;李质检` was passed whole to name resolution and + always failed with `no matches "张焊工;李质检"`, so every multi-value + association had to be back-filled by hand in the record UI after import. + + Coercion now mirrors objectql's `isMultiValueField` predicate. A field whose + stored value is an array — an inherently-multi type (multiselect/checkboxes/tags) + or a multi-capable type flagged `multiple: true` (per the spec: select, lookup, + file, image; `radio` shares select's branch and `user` shares lookup's) — has + its cell split on the export separator (`, ` / `;` / `、` / newline) and each + token coerced individually: + + - **lookup / user (`multiple: true`)** — resolve each name token to an id, store + the id array; an unmatched/ambiguous token reports the **specific token** + (`no sys_user matches "查无此人"`) instead of the whole string. + - **select / radio (`multiple: true`)** — match each token against the options, + store the option-value array. + - **file / image (`multiple: true`)** — split into an id/url array. + + Single-value fields and the non-multi-capable reference types (master_detail / + reference / tree) are unchanged — a stray `multiple: true` on them stays a + single resolved value, matching the engine. + +- aaec5db: fix(security): public-form submissions can no longer forge server-managed anchors (#3022) + + The anonymous public-form surface (ADR-0056 Option A, `POST /forms/:slug/submit`) + is authorized by the declaration-derived `publicFormGrant`, which short-circuits + the security middleware BEFORE every write gate (CRUD, FLS, the owner anchor + guard, the tenant CHECK). The only field-side defense was the route's + declared-field allow-list — and a FormView with zero declared section fields + fell back to merging the raw body wholesale, so an unauthenticated visitor + could `POST owner_id=` (or `organization_id`, audit columns, `id`) and + attach the record to another user or tenant — the #3004 insert-forge, with no + credentials at all. + + Server-managed anchors are now enforced on this surface at BOTH layers, from a + single shared definition (`PUBLIC_FORM_SERVER_MANAGED_FIELDS`, new in + `@objectstack/spec/security`): + + - **Data layer (authoritative)** — the `publicFormGrant` branch in + `@objectstack/plugin-security` strips `id` / `owner_id` / `organization_id` / + `tenant_id` / audit columns / soft-delete state / `__search` from every row + of a granted insert (batch included) before admitting the write, so the + boundary holds no matter what any route lets through. Ownership stays NULL + for object hooks / the first-admin bootstrap to assign, as for other + anonymous-seeded rows. + - **Route layer** — the submit allow-list excludes the same set + unconditionally: an explicitly declared `owner_id` section field no longer + passes, and the zero-declared-sections fallback keeps its documented + all-fields behavior for business columns while refusing the managed set. + The resolve route (`GET /forms/:slug`) drops the managed fields from the + rendered sections and the embedded object schema so a form never collects a + value the submit refuses, and `GET /forms/:slug/lookup/:field` refuses a + `publicPicker` declared on a managed anchor (which would have opened + anonymous `sys_user` search through `owner_id`). + + Authenticated writes are unaffected — this is the anonymous-surface rule only; + `owner_id` transfer semantics for signed-in callers stay governed by the + transfer grant (#3004 / PR #3018). + +- 28ba0c7: fix(rest): mapDataError now honors an explicit 4xx `error.status`/`error.code` carried by domain errors (#2926 ⑦). Record-scope authorization denials from plugin-sharing (status 403, code FORBIDDEN) previously degraded to a bare 400 with no machine-readable code because the generic data routes bypass sendError's status passthrough. Structured 409 envelopes (CONCURRENT_UPDATE, DELETE_RESTRICTED) keep their dedicated branches; 5xx statuses still go through the message-sanitizing heuristics. +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [94b8e44] +- Updated dependencies [86c0aea] +- Updated dependencies [99755b5] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [eb89a8c] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [1c58abd] +- Updated dependencies [aead168] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/service-package@15.1.0 + - @objectstack/platform-objects@15.1.0 + - @objectstack/core@15.1.0 + - @objectstack/types@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/rest/package.json b/packages/rest/package.json index 2ab32122bf..abbd6f69ef 100644 --- a/packages/rest/package.json +++ b/packages/rest/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/rest", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "ObjectStack REST API Server - automatic REST endpoint generation from protocol", "type": "module", diff --git a/packages/runtime/CHANGELOG.md b/packages/runtime/CHANGELOG.md index d1eb1ba126..44c2fbeb0b 100644 --- a/packages/runtime/CHANGELOG.md +++ b/packages/runtime/CHANGELOG.md @@ -1,5 +1,327 @@ # @objectstack/runtime +## 15.1.0 + +### Minor Changes + +- 7f68068: feat(discovery): honest capabilities — standardized stub/fallback marker + realtime route honesty (ADR-0076 D12/A1.5 framework slice, #2462) + + **Spec** — new service self-description marker for honest discovery + (ADR-0076 D12): `SERVICE_SELF_INFO_KEY` (`__serviceInfo`), + `ServiceSelfInfoSchema` / `ServiceSelfInfo`, and `readServiceSelfInfo()`, + which also normalizes plugin-dev's legacy `_dev: true` flag to + `{ status: 'stub', handlerReady: false }`. A registered service that is a + stub / dev fake / degraded fallback self-identifies via this marker; a fully + real service carries no marker. + + **Runtime + metadata-protocol** — both discovery builders + (`HttpDispatcher.getDiscoveryInfo` and the protocol shim's `getDiscovery`) + now honor the marker instead of hardcoding `status: 'available', +handlerReady: true` for every registered service. Dev stubs report `stub`, + the ObjectQL analytics fallback reports `degraded` (it keeps serving — no + `/analytics` 404), and consumers can finally trust + `status === 'available'` / `handlerReady === true`. + + **Realtime honesty fix** — discovery no longer advertises a + `/realtime` route or `websockets: true`: `service-realtime` is an + in-process pub/sub bus, no dispatcher branch or plugin mounts any + `/realtime` HTTP surface, so the advertised route always 404'd. The + registered service now reports `status: 'degraded', handlerReady: false` + with no route (clients using the SDK are unaffected — it falls back to the + conventional path, which behaves exactly as before). Also corrects the + advertised realtime provider from the nonexistent `plugin-realtime` to + `service-realtime`. + + **REST (A1.5)** — the REST layer's protocol dependency is narrowed from the + `ObjectStackProtocol` god-union to the new `RestProtocol = +DataProtocol & MetadataProtocol` slice (exported from + `@objectstack/rest`), per the ADR-0076 D9 incremental narrowing guidance. + Type-level only; no runtime change. + +- 8fc1208: feat(protocol): complete ADR-0087 — load-seam handshake, chain backfill 12–15, release artifacts (#2643) + + Closes the remaining ADR-0087 gaps (see the ADR's as-built Addendum): + + - **P0 load seams (D1).** The protocol handshake now runs on the boot-time + durable-package rehydration path (`@objectstack/service-package` refuses an + incompatible `sys_packages` row with the structured `OS_PROTOCOL_INCOMPATIBLE` + diagnostic and keeps booting) and on `AppPlugin` for code-defined stacks + (fail-fast before the manifest is decomposed). `objectstack lint` gains + `protocol/missing-engines-range` (warning + fix-it) and the + `create-objectstack` blank template stamps `engines: { protocol: '^' }` + (re-stamped at version time by `scripts/sync-template-versions.mjs`) — the + two ends of the grandfathering ratchet. + - **Chain backfill (D2/D3).** `MetadataConversion.retiredFromLoadPath` + implements the load-window's second half (retired entries replay only via + `migrate meta` / fixture CI). Steps 12–15 land: the `api.requireAuth` flip + (semantic), the ADR-0090 wave (3 retired conversions + 5 semantic TODOs), the + `BookAudience` rename (retired conversion), and the ADR-0089 visibility + unification (`visibleOn`/`visibility` → `visibleWhen` as LIVE load-window + conversions) + the `.strict()` flip (semantic). The protocol-11 + `compactLayout` → `highlightFields` rename is backfilled as a retired step-11 + conversion. `migrate meta --from 10` now reaches protocol 15. + - **Release artifacts (D4).** `spec-changes.json` is generated from the + registries (`gen:spec-changes`, CI drift-checked), ships in the npm artifact + together with `api-surface.json`, and is attached to each `@objectstack/spec` + GitHub Release with `added[]`/`removed[]` filled from the api-surface diff + against the previously published release. The upgrade guide + (`docs/protocol-upgrade-guide.md`) is generated from the same registries and + CI drift-checked — a projection that cannot drift. + +- 541b96a: fix(security): enforce the anonymous-deny posture uniformly across HTTP surfaces (#2567) + + The ADR-0056 D2 `requireAuth` flip made REST `/data/*` deny-anonymous by + default, but three sibling surfaces reached ObjectQL without passing through the + gate — so the platform's anonymous posture was **inconsistent by surface**: an + anonymous caller denied on `/data` could read the same object data through a + different door. This closes the remaining two gaps (the `/meta` gate had already + landed) and pins every surface with a conformance row. + + - **Dispatcher GraphQL** (`runtime/http-dispatcher.ts`, `dispatcher-plugin.ts`): + `POST /graphql` reached `kernel.graphql`, whose security middleware falls + **open** for an anonymous context. `handleGraphQL` now applies the same + `requireAuth` gate as `/data` and `/meta`, resolving identity for the direct + route that does not flow through `dispatch()`. The dispatcher's `requireAuth` + default is aligned with the REST plugin's (`?? true`) so a bare host no longer + denies anonymous `/data` while serving the same rows over `/graphql`; an + explicit `requireAuth: false` opt-out is honoured and logs a boot warning. + + - **Raw-hono standard `/data` routes** (`plugin-hono-server/hono-plugin.ts`): + these delegate straight to ObjectQL and were only _shadowed_ when the REST + plugin registered the same paths first — so secure-by-default depended on + plugin registration order. Each route now consults `requireAuth` (secure by + default, mirroring `rest-server.ts`), making the deny decision a property of + this entry point too. Order no longer affects the anonymous posture. + + **Behaviour change:** on a `requireAuth` deployment (the secure default), + anonymous `POST /graphql` and anonymous raw-hono `/data` now return 401. + Deployments that intentionally serve these surfaces publicly set + `requireAuth: false` (a boot warning is logged). Proven end-to-end on the + platform default in `showcase-anonymous-deny-surfaces.dogfood.test.ts`, with + handler-level regression coverage in `http-dispatcher.requireauth.test.ts` and + `hono-anonymous-deny.test.ts`, and pinned by three new authz-conformance rows. + +- 3dc9fce: feat(mcp): `aggregate_records` tool — GROUP BY aggregation over the engine read path + + New MCP tool `aggregate_records` (count/sum/avg/min/max/count_distinct, optional + groupBy incl. date bucketing, where filter, IANA timezone) in the `data:read` + family. Execution routes through the ObjectQL ENGINE (`callData('aggregate')` + deliberately never uses the raw per-env driver), so RLS/tenant scoping and the + D10 delegator intersection apply exactly as on find. + + Security hardening shipped with it: + + - plugin-security: new FLS aggregate-INPUT gate — result masking never runs for + `aggregate` (output rows carry only aliases), so any groupBy / aggregation + reference to an FLS-unreadable field is now rejected fail-closed with the + offending field names (mirrors the FLS write gate). + - runtime: `aggregate` maps to the `list` ApiMethod in the object exposure gate + (an object whose `apiMethods` whitelist excludes `list` cannot leak row + statistics through GROUP BY), and the aggregate action requires at least one + aggregation (the engine's in-memory path would otherwise degrade to raw rows + that the FLS masker does not cover). + + The bridge seam is optional: a runtime that does not implement + `McpDataBridge.aggregate` simply does not register the tool (graceful + degradation, same contract as the action tools). + +### Patch Changes + +- 4c46ee0: refactor(security): migrate the handleSecurity admin gate to shouldDenyAnonymous (#2567 follow-up) + + The dispatcher's `/security/suggested-bindings` admin surface was the last HTTP + seam still hand-rolling the `!userId && !isSystem → 401` check. It now delegates + to the shared `shouldDenyAnonymous` decision like every other seam — with + `requireAuth: true` hardcoded, preserving its UNCONDITIONAL semantics (an admin + surface denies anonymous callers even on a `requireAuth: false` demo deployment). + The 401 body adopts the shared shape (`code: 'unauthenticated'`). + + Deliberately NOT migrated: `handleNotification`'s `!userId` check — that is a + "needs a user identity" predicate (the inbox is keyed by userId; a system + context has no inbox), not an anonymous-posture decision; migrating it would + change semantics. + +- 99755b5: refactor(security): converge the anonymous-deny decision into one shared function + a source-enumerating ratchet (#2567 Phase 2) + + Phase 1 gated every HTTP surface (REST `/data`, dispatcher `/graphql` + `/meta`, + raw-hono `/data`) against the secure-by-default `requireAuth` posture, but each + seam hand-rolled the same `!userId && !isSystem → 401` check. Phase 2 removes + that duplication and pins the surfaces so a new ungated entry point fails CI. + + - **New `shouldDenyAnonymous` in `@objectstack/core`** (`security/anonymous-deny.ts`) + — the single anonymous-deny decision + shared 401 body/constants, mirroring the + `auth-gate.ts` pattern (pure function so the seams can never drift). All five + seams — REST `enforceAuth`, dispatcher `handleGraphQL` / `handleMetadata` / + `handleAI`, hono `denyAnonymous` — now delegate to it. **Pure refactor: no + runtime behavior change** (verified by the unchanged Phase-1 handler + e2e + proofs). Identity resolution and the dynamic exemptions (public-form grants, + share-link tokens) are untouched — they run upstream and only ever hand the + seam an already-resolved context. + - **A `discover()` ratchet on the authz-conformance matrix** — it statically + enumerates the data/meta/graphql HTTP entry points from source (curated + per-file probes, control-plane routes excluded) and asserts each is classified + by a matrix `covers` key. A new `/data`/`/meta`/`/graphql` route (or a + removed/stale `covers`) now fails CI as UNCLASSIFIED / STALE, not in review. A + companion negative test proves the ratchet bites. + + A design trap is guarded: `isAuthGateAllowlisted(undefined)` returns `true`, so a + body-routed seam (GraphQL, which has no request path) must pass no path — the + shared function's non-empty-path guard denies anonymous unconditionally there, + never falling through to the control-plane allowlist. + +- c11e24b: fix(authz): carry the derived posture rung on ExecutionContext (#2947) + + The ADR-0095 D2 posture ladder (`PLATFORM_ADMIN > TENANT_ADMIN > MEMBER > +EXTERNAL`) is derived once by the shared authz resolver from capability grants, + but both HTTP/MCP entry points that build the `ExecutionContext` dropped it — + so any enforcement-side reader of `context.posture` always saw `undefined` + (the same drop that forced the explain layer to re-derive it, #2949). + + `ExecutionContextSchema` now carries an optional `posture` field, and both + `rest-server` and the runtime `resolveExecutionContext` plumb the resolver's + value through. Additive and **behavior-preserving**: no enforcement decision + consumes `posture` yet — whether the hot path evaluates _by_ posture remains a + larger ADR-level decision — this only stops the already-computed value from + being discarded, so enforcement and explain read the same derived rung. + +- 59cd765: fix(security): pre-wiring identity admission for the GraphQL and realtime surfaces (#2992, ADR-0096 D4) + + Two latent execution surfaces — neither reachable by a client today — would + have fallen open the instant a real transport was wired, because both drop or + lack the caller's identity. Per ADR-0096, the identity story is fixed and + pinned in CI _before_ wiring, not after an adversarial review: + + - **GraphQL (surface 1 — latent context-drop, now threaded).** + `handleGraphQL` passed only `{ request }` to `kernel.graphql`, dropping the + resolved `ExecutionContext` — the moment a real engine resolved objects + through ObjectQL it would have run context-less (security middleware falls + OPEN on a missing principal = full authority). The entry point now resolves + the caller identity even on the direct dispatcher-plugin route and even when + `requireAuth` is off, and threads it as `options.context`; + `IGraphQLService.execute` documents that implementations MUST forward it to + every data-engine call. Unit-proven; the authz conformance matrix pins the + threading (`graphql-identity-thread` row) so removing it goes STALE and + fails CI. + + - **realtime (surface 2 — no per-recipient authz seam, posture registered).** + Delivery is a pure fan-out (subscriptions carry no principal, + `matchesSubscription` filters only by object+eventTypes, the engine + publishes the full `after` row), safe only while every subscriber is + server-internal. The posture is now registered as an `experimental` matrix + row (`realtime-delivery-authz`) stating the admission requirement + (per-recipient RLS/FLS/tenant re-check on delivery, or id-only payload + + client re-fetch), and transport TRIPWIRE probes turn any newly wired + WebSocket/SSE/subscribe/client transport into an UNCLASSIFIED surface → red + CI until the identity story ships with it. The `service-realtime` README — + which advertised `authorizeChannel`/`broadcastToUser`/presence auth that do + not exist — is rewritten to describe the real, trusted-internal-only + surface, and the contract docs carry the admission requirement at the seam. + +- fae5dd0: Surface standalone authored `action` metadata rows on the MCP action bridge (#3010). `list_actions` and `run_action` now resolve declarations from `object.actions` unioned with standalone `action` items, keyed the same way the engine registers their handlers (`objectName` → legacy `object` → `'global'`), with object-embedded declarations winning on a key clash. Previously a Studio-authored standalone action executed via REST but was invisible and uninvokable on the MCP/AI surface, even with `ai.exposed: true`. All invoke-time gates (`ai.exposed` fail-closed, ADR-0066 D4 capability gate, sys\_\* fail-closed) are unchanged. +- 23925e9: fix(plugin-auth): re-run membership backfill when app seeding settles (#2996) + + The ADR-0093 D6 membership backfill — the only safety net for users created + by app seeds (raw `engine.insert` into `sys_user` bypasses better-auth's + `user.create.after` reconciler) — ran only once on `kernel:ready`. When a seed + bundle overruns its inline budget (`OS_INLINE_SEED_BUDGET_MS`, default 8s) it + finishes in the background _after_ `kernel:ready`, so its users stayed + member-less in single-org `auto` mode until the next restart re-ran the backfill. + + `AppPlugin` now emits a new **`app:seeded`** lifecycle event when an app's inline + seed settles (success, partial, or fallback) — carrying `{ appId, overBudget }`, + where `overBudget: true` marks the post-`kernel:ready` background case. plugin-auth + subscribes and re-runs the (idempotent, self-guarding, opt-out-able) + `backfillMemberships` on that signal, closing the window without waiting for a + restart. No behavior change when a seed completes within budget, in multi-tenant + mode, or under `invite-only` policy; `OS_SKIP_MEMBERSHIP_BACKFILL=1` still opts out. + +- fdfe1d9: fix(security): enforce the `ai.exposed` opt-in on the MCP action surface (#2849) + + Business-action bodies execute as trusted code: their engine facade carries no + `ExecutionContext`, so a body's internal reads/writes bypass RLS/FLS/CRUD and + tenant scoping — the caller's permissions and an agent's ADR-0090 D10 data + ceiling do NOT bound what an invoked action does. The MCP `run_action` bridge + nevertheless allowed invoking ANY headless action, ignoring the spec's + `ai.exposed` governance gate (ADR-0011) entirely. + + The MCP bridge now fail-closes on `ai.exposed`: `list_actions` only enumerates + — and `run_action` only dispatches — actions the app author explicitly opted + into the AI surface with `ai: { exposed: true, description }`. Flow-type + actions additionally receive the caller's identity (`userId` / `positions` / + `permissions` / `tenantId`) as a proper `AutomationContext` (replacing the + former `triggerData` envelope the engine never read), so a `runAs: 'user'` + flow enforces RLS as the invoker instead of running unscoped (ADR-0049). + Trusted body dispatches are now audit-logged on both the MCP and REST action + paths, and the MCP tool/README/docs wording no longer claims action bodies run + under the caller's RLS. + + Migration: actions that should stay invokable by AI agents through MCP must + declare `ai: { exposed: true, description: '…' }` (≥40-char description). All + other invocation surfaces (UI, REST `/actions/...`) are unchanged. + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [86c0aea] +- Updated dependencies [99755b5] +- Updated dependencies [93bb7b4] +- Updated dependencies [c11e24b] +- Updated dependencies [e0b049a] +- Updated dependencies [e9a2885] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [7bc9e79] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [aeb2110] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [3dc9fce] +- Updated dependencies [23925e9] +- Updated dependencies [7f9a795] +- Updated dependencies [6613ad0] +- Updated dependencies [a16972b] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [1c58abd] +- Updated dependencies [aead168] +- Updated dependencies [dee7feb] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [28ba0c7] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [8b27dd7] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [464418e] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/objectql@15.1.0 + - @objectstack/rest@15.1.0 + - @objectstack/core@15.1.0 + - @objectstack/plugin-security@15.1.0 + - @objectstack/plugin-auth@15.1.0 + - @objectstack/types@15.1.0 + - @objectstack/formula@15.1.0 + - @objectstack/metadata@15.1.0 + - @objectstack/metadata-core@15.1.0 + - @objectstack/observability@15.1.0 + - @objectstack/driver-memory@15.1.0 + - @objectstack/driver-sql@15.1.0 + - @objectstack/driver-sqlite-wasm@15.1.0 + - @objectstack/service-cluster@15.1.0 + - @objectstack/service-datasource@15.1.0 + - @objectstack/service-i18n@15.1.0 + ## 15.0.0 ### Minor Changes diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 299200549b..dc07d72996 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/runtime", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "ObjectStack Core Runtime & Query Engine", "type": "module", diff --git a/packages/sdui-parser/CHANGELOG.md b/packages/sdui-parser/CHANGELOG.md index 9bd1e1af9d..94a657e5c7 100644 --- a/packages/sdui-parser/CHANGELOG.md +++ b/packages/sdui-parser/CHANGELOG.md @@ -1,5 +1,7 @@ # @objectstack/sdui-parser +## 15.1.0 + ## 15.0.0 ## 14.8.0 diff --git a/packages/sdui-parser/package.json b/packages/sdui-parser/package.json index a0e33bd20d..92394185d9 100644 --- a/packages/sdui-parser/package.json +++ b/packages/sdui-parser/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/sdui-parser", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "ObjectStack constrained JSX-source → SDUI SchemaNode tree compiler (parse, never execute). Isomorphic, zero React. ADR-0080.", "main": "dist/index.js", diff --git a/packages/services/service-analytics/CHANGELOG.md b/packages/services/service-analytics/CHANGELOG.md index 6566b4457c..de9e15f1c4 100644 --- a/packages/services/service-analytics/CHANGELOG.md +++ b/packages/services/service-analytics/CHANGELOG.md @@ -1,5 +1,39 @@ # Changelog — @objectstack/service-analytics +## 15.1.0 + +### Patch Changes + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [99755b5] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/core@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/services/service-analytics/package.json b/packages/services/service-analytics/package.json index e0701d523c..4281dbfc2c 100644 --- a/packages/services/service-analytics/package.json +++ b/packages/services/service-analytics/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-analytics", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "Analytics Service for ObjectStack — implements IAnalyticsService with multi-driver strategy pattern (NativeSQL, ObjectQL, InMemory)", "type": "module", diff --git a/packages/services/service-automation/CHANGELOG.md b/packages/services/service-automation/CHANGELOG.md index b8af1dcecf..93c98c72ac 100644 --- a/packages/services/service-automation/CHANGELOG.md +++ b/packages/services/service-automation/CHANGELOG.md @@ -1,5 +1,195 @@ # @objectstack/service-automation +## 15.1.0 + +### Minor Changes + +- 96a14d0: feat(connectors): ADR-0096 — provider-bound declarative connector instances materialized at boot (#2977) + + Declarative `connectors:` stack entries used to be **descriptor-only** (#2612): + registered as metadata but never dispatchable, the platform's one dead metadata + surface. An entry may now name a **`provider`** — an installed generic executor + (`openapi` / `mcp` / `rest`) — and the automation service **materializes** it + into a live, dispatchable connector at boot. AI can now wire an integration as + pure metadata and a flow `connector_action` calls it end-to-end. + + - **Schema (`@objectstack/spec`).** `ConnectorSchema` gains `provider`, + `providerConfig`, and `auth` (a `credentialRef`-based instance-auth shape — + `ConnectorInstanceAuthSchema` — that references credentials, never inlines + them); `authentication` now defaults to `{ type: 'none' }` so a provider-bound + instance need not author it (loosening — existing connectors are unaffected). + `DeclarativeConnectorEntrySchema` (used by `stack.zod.ts`) rejects inline + secrets, orphan `providerConfig`/`auth`, and authored `actions`/`triggers` on a + provider-bound entry. A new `integration/connector-provider.ts` defines the + provider-factory contract as pure types. + + - **Engine + boot (`@objectstack/service-automation`).** The engine adds a + connector-provider registry (`registerConnectorProvider`/`getConnectorProvider`) + and origin-tags registered connectors. At boot the service resolves each + provider-bound entry — looking up the factory, resolving `auth.credentialRef` + via a pluggable `CredentialResolver` (open-tier default: environment + variables), and registering the materialized connector. Boot **fails loudly** + for an unknown provider, invalid `providerConfig`, an unresolvable + `credentialRef`, or a name conflict with a plugin-registered connector (no + silent precedence). + + - **Providers (`connector-rest` / `connector-openapi` / `connector-mcp`).** Each + plugin registers a provider factory in `init()` reusing its existing + generator/adapter API. Plugin options are now **optional**: with none the + plugin contributes only its provider factory; with instance options it also + registers a hand-wired connector (back-compat). `connector-openapi` adds a + `ConnectorOpenApiPlugin`. + + Open tier: static auth (`none`/`api-key`/`basic`/`bearer`) with `credentialRef` + resolved from env vars. Managed vaulting, OAuth2 refresh, and per-tenant + connection lifecycle remain the enterprise tier (ADR-0015) — an enterprise host + injects a vault-backed `CredentialResolver` with no change to the materialization + path. + +- 10a570a: feat(connector-openapi): resolve `providerConfig.spec` from a package-relative file path (#3016, ADR-0096 follow-up) + + ADR-0096's canonical example authors an OpenAPI-backed instance as + `providerConfig: { spec: './billing-openapi.json' }`, but the landed `openapi` + provider factory only accepted an inline document object or an http(s) URL. + The spec union is now complete: **inline object | file path | remote URL**. + + - **`@objectstack/spec`.** `ConnectorProviderContext` gains an optional + host-injected `loadPackageFile(relativePath)` capability (pure type): reads a + UTF-8 file resolved against the declaring stack/package root, confined to + that root. `undefined` on hosts without a filesystem. + + - **`@objectstack/service-automation`.** New `packageRoot` plugin option (the + base for relative file refs; defaults to `process.cwd()`) and an exported + `createPackageFileLoader(packageRoot)` that implements the confinement + guard — absolute paths and `..`-escaping paths are rejected — with lazy + `node:fs`/`node:path` imports so non-Node hosts only fail if a file ref is + actually dereferenced. The materializer injects the capability into every + provider factory's context. Failures follow the existing reconcile policy: + **fatal at boot, entry skipped on reload**. + + - **`@objectstack/connector-openapi`.** A string `providerConfig.spec` that is + not an http(s) URL is now read via `ctx.loadPackageFile` and parsed as an + OpenAPI JSON document (clear errors for missing/unreadable files, unparseable + JSON, and hosts without package file access). + + - **`@objectstack/cli`.** `serve`/`dev` pass the project folder (the + `objectstack.config.ts` directory) as the automation service's `packageRoot`, + mirroring how the standalone sqlite default is anchored. + +- 93fd58e: feat(connectors): ADR-0096 runtime re-materialization of declarative connectors (#2977 follow-up) + + Provider-bound declarative `connectors:` instances (ADR-0096) previously + materialized only at boot — a connector published from Studio while the server + ran did not become dispatchable until a restart. `materializeDeclaredConnectors` + is now a **reconcile** run both at boot and on `metadata:reloaded`: + + - **Add** newly-declared instances, **tear down** removed / newly-`enabled:false` + ones (calling their `close`, e.g. an MCP connection), and **re-materialize** + only instances whose signature — a stable hash of `provider` + `providerConfig` + - `auth` + identity — changed. An unchanged MCP instance is never needlessly + reconnected on an unrelated metadata reload. + - **Boot stays fatal** ("fail loudly"): unknown provider / invalid providerConfig + / unresolvable credentialRef / name conflict aborts startup. **Reload is soft**: + the same problems are logged and the offending entry skipped, so a bad publish + never crashes a running server; a changed instance's old connector keeps + serving until its replacement materializes successfully. + + Also: `ConnectorDescriptor` (served by `GET /api/v1/automation/connectors`) now + carries an `origin` field (`'plugin' | 'declarative'`), so a designer can + distinguish a materialized declarative instance from a plugin-registered + connector. + +- 4f8c2d1: feat(connectors): degrade + retry declarative instances whose upstream is unreachable (#3017) + + ADR-0097 kept every declarative-connector materialization failure fatal at + boot. That is right for configuration faults (unknown provider, invalid + `providerConfig`, unresolvable `credentialRef`, name conflict) but wrong for + _operational_ ones: a `provider: 'mcp'` instance must contact its MCP server + (`tools/list`) to materialize, and a transient network blip aborted the whole + app boot. + + - **spec**: a provider factory can now throw + `ConnectorUpstreamUnavailableError` (code `CONNECTOR_UPSTREAM_UNAVAILABLE`, + structural guard `isConnectorUpstreamUnavailable`) to mark a failure as + "upstream temporarily unreachable — degrade and retry" instead of fatal. + - **service-automation**: the reconcile degrades such an instance in both boot + and reload modes: it registers an action-less husk (`state: 'degraded'` + + `degradedReason` on the `GET /connectors` descriptor) so the instance is + visible instead of silently missing — or, on a changed-config + re-materialization, keeps the old connector serving. A `connector_action` + against a degraded instance fails with the reason and a "retries + automatically" pointer. Degraded instances retry on an exponential backoff + (5s → 5min, reset by config edits) and on every `metadata:reloaded` + reconcile; recovery swaps the husk for the live connector atomically. + Reconcile runs (boot / reload / retry timer) are now serialized. + - **connector-mcp**: the `mcp` provider classifies connect / `tools/list` + failures as upstream-unavailable; transport-shape validation stays a plain + (fatal) throw. + + Configuration faults remain loud boot failures — the carve-out is only for the + unavailable marker. + +- d8f7f6a: feat(automation): descriptor-only contract + boot audit for declarative `connectors:` (#2612) + + Declarative `connectors:` stack entries never reach the automation engine's + connector registry — only plugins populate it via + `engine.registerConnector(def, handlers)` (ADR-0018 §Addendum) — so a declared + connector with actions and no plugin behind it _looked_ dispatchable but was + silently inert. + + The contract is now explicit and audited: + + - **Boot audit (service-automation).** At `kernel:ready` (and again on + `metadata:reloaded`), declared connectors with `actions` but no same-name + runtime registration log a loud warning naming each inert entry and + pointing at the fix (install the matching connector plugin, or mark a + deliberate catalog entry). Nothing is registered on your behalf — the + warning surfaces the gap `connector_action` would otherwise hit at + dispatch time. + - **`enabled: false` = deliberate catalog descriptor (spec).** Setting it on + a declarative entry documents "descriptor-only on purpose" and silences the + audit. Schema docs on `stack.zod.ts` (`connectors:`) and + `integration/connector.zod.ts` now state the descriptor-vs-registered + contract explicitly (including for AI stack authoring via `.describe()`). + + Declarative provider-bound connector _instances_ — entries a generic executor + (connector-openapi / connector-mcp) materializes into live connectors at boot, + upgrading this warning to a hard error — are specified in ADR-0096 and tracked + in #2977. + +### Patch Changes + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [99755b5] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/core@15.1.0 + - @objectstack/formula@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/services/service-automation/package.json b/packages/services/service-automation/package.json index 5aa4204b2d..2bc1c2568e 100644 --- a/packages/services/service-automation/package.json +++ b/packages/services/service-automation/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-automation", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "Automation Service for ObjectStack — implements IAutomationService with plugin-based DAG flow execution engine", "type": "module", diff --git a/packages/services/service-cache/CHANGELOG.md b/packages/services/service-cache/CHANGELOG.md index 5c86d49afb..2b71fa17ee 100644 --- a/packages/services/service-cache/CHANGELOG.md +++ b/packages/services/service-cache/CHANGELOG.md @@ -1,5 +1,40 @@ # @objectstack/service-cache +## 15.1.0 + +### Patch Changes + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [99755b5] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/core@15.1.0 + - @objectstack/observability@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/services/service-cache/package.json b/packages/services/service-cache/package.json index 485ee2aa35..cc4a273656 100644 --- a/packages/services/service-cache/package.json +++ b/packages/services/service-cache/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-cache", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "Cache Service for ObjectStack — implements ICacheService with in-memory and Redis adapters", "type": "module", diff --git a/packages/services/service-cluster-redis/CHANGELOG.md b/packages/services/service-cluster-redis/CHANGELOG.md index 08ba401d48..db1787f326 100644 --- a/packages/services/service-cluster-redis/CHANGELOG.md +++ b/packages/services/service-cluster-redis/CHANGELOG.md @@ -1,5 +1,38 @@ # @objectstack/service-cluster-redis +## 15.1.0 + +### Patch Changes + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/service-cluster@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/services/service-cluster-redis/package.json b/packages/services/service-cluster-redis/package.json index 080f906152..4e49bc36eb 100644 --- a/packages/services/service-cluster-redis/package.json +++ b/packages/services/service-cluster-redis/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-cluster-redis", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "Redis cluster driver for ObjectStack — implements IPubSub/ILock/IKV/ICounter against Redis using ioredis.", "type": "module", diff --git a/packages/services/service-cluster/CHANGELOG.md b/packages/services/service-cluster/CHANGELOG.md index 3a950e5275..c9d949b4a7 100644 --- a/packages/services/service-cluster/CHANGELOG.md +++ b/packages/services/service-cluster/CHANGELOG.md @@ -1,5 +1,39 @@ # @objectstack/service-cluster +## 15.1.0 + +### Patch Changes + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [99755b5] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/core@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/services/service-cluster/package.json b/packages/services/service-cluster/package.json index 06ab31c720..e855375a91 100644 --- a/packages/services/service-cluster/package.json +++ b/packages/services/service-cluster/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-cluster", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "Cluster Service for ObjectStack — pluggable PubSub/Lock/KV/Counter primitives. Memory driver included; postgres/redis drivers ship separately.", "type": "module", diff --git a/packages/services/service-datasource/CHANGELOG.md b/packages/services/service-datasource/CHANGELOG.md index a31642e2f8..b05eb52720 100644 --- a/packages/services/service-datasource/CHANGELOG.md +++ b/packages/services/service-datasource/CHANGELOG.md @@ -1,5 +1,39 @@ # @objectstack/service-external-datasource +## 15.1.0 + +### Patch Changes + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [99755b5] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/core@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/services/service-datasource/package.json b/packages/services/service-datasource/package.json index 213f980cf1..ecb9f5c133 100644 --- a/packages/services/service-datasource/package.json +++ b/packages/services/service-datasource/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-datasource", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "The datasource service (ADR-0015): external-table federation (introspect/draft/import/validate) + runtime UI datasource lifecycle (list/test/create/update/remove + REST routes). Open-source mechanism; the tier line falls on which ICryptoProvider / driver factory a host injects.", "type": "module", diff --git a/packages/services/service-i18n/CHANGELOG.md b/packages/services/service-i18n/CHANGELOG.md index 90d5acecbc..ea21904517 100644 --- a/packages/services/service-i18n/CHANGELOG.md +++ b/packages/services/service-i18n/CHANGELOG.md @@ -1,5 +1,39 @@ # @objectstack/service-i18n +## 15.1.0 + +### Patch Changes + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [99755b5] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/core@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/services/service-i18n/package.json b/packages/services/service-i18n/package.json index 4c85abce1d..d14407857e 100644 --- a/packages/services/service-i18n/package.json +++ b/packages/services/service-i18n/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-i18n", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "I18n Service for ObjectStack — implements II18nService with file-based locale loading", "type": "module", diff --git a/packages/services/service-job/CHANGELOG.md b/packages/services/service-job/CHANGELOG.md index e32baeef33..6cf5ada141 100644 --- a/packages/services/service-job/CHANGELOG.md +++ b/packages/services/service-job/CHANGELOG.md @@ -1,5 +1,44 @@ # @objectstack/service-job +## 15.1.0 + +### Patch Changes + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [94b8e44] +- Updated dependencies [86c0aea] +- Updated dependencies [99755b5] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [eb89a8c] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [aead168] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/platform-objects@15.1.0 + - @objectstack/core@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/services/service-job/package.json b/packages/services/service-job/package.json index 9f9b1aca26..2f8fb0fbd4 100644 --- a/packages/services/service-job/package.json +++ b/packages/services/service-job/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-job", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "Job Service for ObjectStack — implements IJobService with setInterval and cron scheduling", "type": "module", diff --git a/packages/services/service-knowledge/CHANGELOG.md b/packages/services/service-knowledge/CHANGELOG.md index e562f7742d..678b319b8d 100644 --- a/packages/services/service-knowledge/CHANGELOG.md +++ b/packages/services/service-knowledge/CHANGELOG.md @@ -1,5 +1,76 @@ # @objectstack/service-knowledge +## 15.1.0 + +### Patch Changes + +- e07645c: fix(security): close three execution-surface authz holes surfaced by the #2849 class sweep (#2980, #2981, #2982) + + Three independent, confirmed-exploitable defects where an execution surface + ignored the caller's identity or fell open on a missing one. Each is fixed at + its own enforcement point; none change behaviour for correctly-scoped callers. + + - **#2980 — reports IDOR + scheduled-report RLS bypass.** `ReportService` + discarded the caller's context and read/wrote `sys_saved_report` with a system + context, so any authenticated user could read, delete, or overwrite any saved + report by id (cross-owner / cross-tenant), and `listReports` enumerated all + owners. `getReport`/`deleteReport`/`saveReport`/`listReports` are now + owner-scoped (system read of the protection-locked metadata object, but + authorization enforced by owner match); create/overwrite can no longer spoof + ownership. Scheduled dispatch no longer runs `isSystem` (which emailed the + target object's entire table past the owner's RLS): it resolves the owner to a + real RLS-bearing context via a new `resolveOwnerContext` seam and **fails + closed** (skips + marks the schedule failed) when the owner can't be resolved, + rather than running elevated. Wiring that resolver is the reports-surface + consumer of ADR-0073's user-less identity resolution. + + - **#2981 — knowledge/RAG retrieval fall-open.** `applyPermissionFilter` returned + every hit when the context was missing _or_ system. A missing identity is no + longer treated as a grant: object-backed hits fail closed (dropped, keeping + ACL-less file/http hits), and only an **explicit** system context passes + through. Closes the agent path where an omitted `ToolExecutionContext.actor` + yielded unfiltered semantic search over the whole corpus. + + - **#2982 — bulk-write OWD gap.** `update({multi:true})` / `deleteMany` had no + single id to `canEdit`-gate, so owner scoping was skipped on private (and + public_read) objects. A new `SharingService.buildWriteFilter` (the edit-set + analogue of `buildReadFilter`) is AND-ed into the write AST for multi writes, + constraining them to rows the caller may edit — including the on-behalf-of + delegator intersection. + + Tracked as the motivating evidence of ADR-0096 (execution-surface identity + admission); the mechanism that would prevent the class structurally is separate. + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [99755b5] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/core@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/services/service-knowledge/package.json b/packages/services/service-knowledge/package.json index 7b5bd294b9..e033ac596e 100644 --- a/packages/services/service-knowledge/package.json +++ b/packages/services/service-knowledge/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-knowledge", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "Knowledge Service for ObjectStack — orchestrator implementing IKnowledgeService over pluggable IKnowledgeAdapter backends (RAGFlow, LlamaIndex, Dify, in-memory).", "type": "module", diff --git a/packages/services/service-messaging/CHANGELOG.md b/packages/services/service-messaging/CHANGELOG.md index b735ad4521..2211298dae 100644 --- a/packages/services/service-messaging/CHANGELOG.md +++ b/packages/services/service-messaging/CHANGELOG.md @@ -1,5 +1,39 @@ # @objectstack/service-messaging +## 15.1.0 + +### Patch Changes + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [99755b5] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/core@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/services/service-messaging/package.json b/packages/services/service-messaging/package.json index 43098a16f8..90b134f4ab 100644 --- a/packages/services/service-messaging/package.json +++ b/packages/services/service-messaging/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-messaging", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "Messaging Service for ObjectStack — outbound notification dispatch (ADR-0012). Ships the MessagingChannel registry, emit() fan-out, and the always-on inbox channel; other channels (email/webhook/push/IM) plug in.", "type": "module", diff --git a/packages/services/service-package/CHANGELOG.md b/packages/services/service-package/CHANGELOG.md index 14b0cd99c5..3bef55a469 100644 --- a/packages/services/service-package/CHANGELOG.md +++ b/packages/services/service-package/CHANGELOG.md @@ -1,5 +1,72 @@ # @objectstack/service-package +## 15.1.0 + +### Minor Changes + +- 8fc1208: feat(protocol): complete ADR-0087 — load-seam handshake, chain backfill 12–15, release artifacts (#2643) + + Closes the remaining ADR-0087 gaps (see the ADR's as-built Addendum): + + - **P0 load seams (D1).** The protocol handshake now runs on the boot-time + durable-package rehydration path (`@objectstack/service-package` refuses an + incompatible `sys_packages` row with the structured `OS_PROTOCOL_INCOMPATIBLE` + diagnostic and keeps booting) and on `AppPlugin` for code-defined stacks + (fail-fast before the manifest is decomposed). `objectstack lint` gains + `protocol/missing-engines-range` (warning + fix-it) and the + `create-objectstack` blank template stamps `engines: { protocol: '^' }` + (re-stamped at version time by `scripts/sync-template-versions.mjs`) — the + two ends of the grandfathering ratchet. + - **Chain backfill (D2/D3).** `MetadataConversion.retiredFromLoadPath` + implements the load-window's second half (retired entries replay only via + `migrate meta` / fixture CI). Steps 12–15 land: the `api.requireAuth` flip + (semantic), the ADR-0090 wave (3 retired conversions + 5 semantic TODOs), the + `BookAudience` rename (retired conversion), and the ADR-0089 visibility + unification (`visibleOn`/`visibility` → `visibleWhen` as LIVE load-window + conversions) + the `.strict()` flip (semantic). The protocol-11 + `compactLayout` → `highlightFields` rename is backfilled as a retired step-11 + conversion. `migrate meta --from 10` now reaches protocol 15. + - **Release artifacts (D4).** `spec-changes.json` is generated from the + registries (`gen:spec-changes`, CI drift-checked), ships in the npm artifact + together with `api-surface.json`, and is attached to each `@objectstack/spec` + GitHub Release with `added[]`/`removed[]` filled from the api-surface diff + against the previously published release. The upgrade guide + (`docs/protocol-upgrade-guide.md`) is generated from the same registries and + CI drift-checked — a projection that cannot drift. + +### Patch Changes + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [99755b5] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/core@15.1.0 + - @objectstack/metadata-core@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/services/service-package/package.json b/packages/services/service-package/package.json index 4299ed3079..443aa79fea 100644 --- a/packages/services/service-package/package.json +++ b/packages/services/service-package/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-package", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "Package management service for ObjectStack — publish, install, and manage packages", "type": "module", diff --git a/packages/services/service-queue/CHANGELOG.md b/packages/services/service-queue/CHANGELOG.md index 9a6e547927..b044210b9d 100644 --- a/packages/services/service-queue/CHANGELOG.md +++ b/packages/services/service-queue/CHANGELOG.md @@ -1,5 +1,44 @@ # @objectstack/service-queue +## 15.1.0 + +### Patch Changes + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [94b8e44] +- Updated dependencies [86c0aea] +- Updated dependencies [99755b5] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [eb89a8c] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [aead168] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/platform-objects@15.1.0 + - @objectstack/core@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/services/service-queue/package.json b/packages/services/service-queue/package.json index 96137b5a15..d75e04be92 100644 --- a/packages/services/service-queue/package.json +++ b/packages/services/service-queue/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-queue", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "Queue Service for ObjectStack — implements IQueueService with in-memory and durable DB-backed (sys_job_queue) adapters", "type": "module", diff --git a/packages/services/service-realtime/CHANGELOG.md b/packages/services/service-realtime/CHANGELOG.md index 12b9e95f31..13bf7f3cf3 100644 --- a/packages/services/service-realtime/CHANGELOG.md +++ b/packages/services/service-realtime/CHANGELOG.md @@ -1,5 +1,77 @@ # @objectstack/service-realtime +## 15.1.0 + +### Patch Changes + +- 59cd765: fix(security): pre-wiring identity admission for the GraphQL and realtime surfaces (#2992, ADR-0096 D4) + + Two latent execution surfaces — neither reachable by a client today — would + have fallen open the instant a real transport was wired, because both drop or + lack the caller's identity. Per ADR-0096, the identity story is fixed and + pinned in CI _before_ wiring, not after an adversarial review: + + - **GraphQL (surface 1 — latent context-drop, now threaded).** + `handleGraphQL` passed only `{ request }` to `kernel.graphql`, dropping the + resolved `ExecutionContext` — the moment a real engine resolved objects + through ObjectQL it would have run context-less (security middleware falls + OPEN on a missing principal = full authority). The entry point now resolves + the caller identity even on the direct dispatcher-plugin route and even when + `requireAuth` is off, and threads it as `options.context`; + `IGraphQLService.execute` documents that implementations MUST forward it to + every data-engine call. Unit-proven; the authz conformance matrix pins the + threading (`graphql-identity-thread` row) so removing it goes STALE and + fails CI. + + - **realtime (surface 2 — no per-recipient authz seam, posture registered).** + Delivery is a pure fan-out (subscriptions carry no principal, + `matchesSubscription` filters only by object+eventTypes, the engine + publishes the full `after` row), safe only while every subscriber is + server-internal. The posture is now registered as an `experimental` matrix + row (`realtime-delivery-authz`) stating the admission requirement + (per-recipient RLS/FLS/tenant re-check on delivery, or id-only payload + + client re-fetch), and transport TRIPWIRE probes turn any newly wired + WebSocket/SSE/subscribe/client transport into an UNCLASSIFIED surface → red + CI until the identity story ships with it. The `service-realtime` README — + which advertised `authorizeChannel`/`broadcastToUser`/presence auth that do + not exist — is rewritten to describe the real, trusted-internal-only + surface, and the contract docs carry the admission requirement at the seam. + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [94b8e44] +- Updated dependencies [86c0aea] +- Updated dependencies [99755b5] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [eb89a8c] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [aead168] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/platform-objects@15.1.0 + - @objectstack/core@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/services/service-realtime/package.json b/packages/services/service-realtime/package.json index 1c4d883f8c..180514a9ea 100644 --- a/packages/services/service-realtime/package.json +++ b/packages/services/service-realtime/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-realtime", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "Realtime Service for ObjectStack — implements IRealtimeService with WebSocket and in-memory pub/sub", "type": "module", diff --git a/packages/services/service-settings/CHANGELOG.md b/packages/services/service-settings/CHANGELOG.md index 194dfe1b90..30bc3fcf9a 100644 --- a/packages/services/service-settings/CHANGELOG.md +++ b/packages/services/service-settings/CHANGELOG.md @@ -1,5 +1,46 @@ # @objectstack/service-settings +## 15.1.0 + +### Patch Changes + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [94b8e44] +- Updated dependencies [86c0aea] +- Updated dependencies [99755b5] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [eb89a8c] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [1c58abd] +- Updated dependencies [aead168] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/platform-objects@15.1.0 + - @objectstack/core@15.1.0 + - @objectstack/types@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/services/service-settings/package.json b/packages/services/service-settings/package.json index 9efe4a18b7..06332cb53b 100644 --- a/packages/services/service-settings/package.json +++ b/packages/services/service-settings/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-settings", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "Settings service for ObjectStack — manifest registry + K/V resolver (OS_* env > Tenant > User > Default) + REST routes. See ADR-0007.", "type": "module", diff --git a/packages/services/service-sms/CHANGELOG.md b/packages/services/service-sms/CHANGELOG.md index e7b800a53e..04e400101b 100644 --- a/packages/services/service-sms/CHANGELOG.md +++ b/packages/services/service-sms/CHANGELOG.md @@ -1,5 +1,39 @@ # @objectstack/service-sms +## 15.1.0 + +### Patch Changes + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [99755b5] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/core@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/services/service-sms/package.json b/packages/services/service-sms/package.json index c8b328c8a7..6b21e9298b 100644 --- a/packages/services/service-sms/package.json +++ b/packages/services/service-sms/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-sms", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "SMS service for ObjectStack — ISmsService + transport-pluggable outbound delivery (Aliyun / Twilio / log).", "main": "dist/index.js", diff --git a/packages/services/service-storage/CHANGELOG.md b/packages/services/service-storage/CHANGELOG.md index cc26156a06..0092dd87d9 100644 --- a/packages/services/service-storage/CHANGELOG.md +++ b/packages/services/service-storage/CHANGELOG.md @@ -1,5 +1,152 @@ # @objectstack/service-storage +## 15.1.0 + +### Minor Changes + +- 0289d5a: feat(attachments): authenticated, parent-scoped downloads for attachments files (#2970) + + Closes item 2 of #2970. The storage download endpoints (`GET /storage/files/:fileId` + and `/files/:fileId/url`) were anonymous capability URLs — anyone holding a + `fileId` could mint a download without a session or any access check. + + For `scope === 'attachments'`, non-`public_read` files, both endpoints now gate + on a new `authorizeFileRead` seam: `401 AUTH_REQUIRED` without a session, `403 +ATTACHMENT_DOWNLOAD_DENIED` when the caller is neither the file's owner nor able + to READ a record the file is attached to (parent-derived, resolved through the + full caller context via `resolveAuthzContext`), and otherwise a **short-lived** + signed URL (`downloadTtl`, default 300s). Non-attachments files (field files, + avatars, org logos — embedded in `` which cannot carry a bearer token) + keep the stable anonymous capability URL, and bare kernels/tests without the + seam wired stay open (back-compat). + +- 94b8e44: feat(attachments): edit-on-parent attach, upload-session lifecycle, trash=false (#2970 items 3-5) + + Closes the remaining enforce-or-remove / lifecycle items of #2970: + + - **Edit-on-parent for attach (item 3, Salesforce parity).** Creating a + `sys_attachment` now requires EDIT access to the parent record (via the + sharing service's `canEdit`), not merely read — public-model parents are + unchanged (canEdit is true for any member), private/owner-scoped parents + require the caller to own/edit them. Degrades to read visibility when no + sharing service is present. + - **`sys_upload_session` lifecycle (item 4).** Abandoned / terminal chunked + upload sessions are reaped by the platform LifecycleService (`transient`; + TTL 1d past `expires_at`; retention 7d for terminal statuses). Row reap + only — a reap guard that aborts backend multipart uploads for partial S3 + sessions is a filed follow-up. + - **`sys_attachment.enable.trash` → `false` (item 5, ADR-0049).** The flag is + `dead` in the liveness ledger (no engine soft-delete reader) and attachment + deletes are hard (the reap guard reclaims a file's bytes once its last join + row is gone, so a restore would dangle) — declare the honest state rather + than claim a restore capability the runtime does not provide. + +- d49deb5: feat(attachments): sys_attachment read inherits parent-record visibility (#2970) + + Follow-up to #2755. The create/delete gates landed, but a member could still + LIST `sys_attachment` rows (file_name, size, parent_id) pointing at records + they cannot read — an information leak, since attachment access derives from + the PARENT record (Salesforce ContentDocumentLink semantics). `sys_attachment` + is a public system object with no owner field, so the sharing/RLS static + predicates never narrowed it. + + `installAttachmentReadVisibility` registers a `sys_attachment`-scoped engine + **middleware** (not a find-hook) so it filters `find`, `findOne`, `count`, and + `aggregate` identically — critically, the list `total` (which comes from + `engine.count()`, never the find path) is filtered too, so it cannot leak the + count of hidden rows. Generalizing ADR-0055 `controlled_by_parent` to the + polymorphic parent, each read resolves the visible parent ids per + `parent_object` through the caller-scoped engine (the parent's own RLS/OWD/ + sharing apply) and ANDs a `$or` of `{ parent_object, parent_id: { $in } }` + into the query; no visible parent ⇒ a deny-all sentinel. Fails closed on any + compute error. System / context-less internal reads are not narrowed. + +- 86c0aea: feat(attachments): sys_file orphan lifecycle + parent-derived attachment access (#2755) + + **Orphan lifecycle (ADR-0057).** Deleting a `sys_attachment` join row used to + orphan the backing `sys_file` row and its storage bytes forever. `sys_file` + now declares a lifecycle (`ttl 30d` on a new `deleted_at` tombstone for + orphans; `retention 7d onlyWhen status=pending` for abandoned uploads), the + storage plugin's new hooks tombstone a file when its LAST join row is deleted + (attachments scope only — `Field.file`/`Field.image`/avatar scopes are never + touched) and un-tombstone on re-attach, and a new LifecycleService **reap + guard** seam (`registerReapGuard`) re-verifies zero references at sweep time + and deletes the storage bytes before confirming each row reap. A guarded + object is never blind-deleted; an erroring guard fails safe (rows retained). + + **Attachment access (ADR-0049, Salesforce parent-derived semantics).** + `sys_attachment` create now requires caller READ visibility of the parent + record (403 `ATTACHMENT_PARENT_ACCESS`) and server-stamps `uploaded_by` from + the session (client value ignored); delete requires uploader-or-parent-editor + (403 `ATTACHMENT_DELETE_DENIED`). The storage upload routes require an + authenticated session when an auth service is wired (401 `AUTH_REQUIRED`; + bare kernels stay open) and stamp `owner_id` on new files. + + **REMOVED — `sys_attachment.share_type` / `sys_attachment.visibility`.** + Both fields were modeled in v1 with zero runtime consumers (ADR-0049 + parsed-but-unenforced). There is no replacement key: attachment access is + derived from the parent record by the hooks above. Writers of these fields + should simply stop sending them (unknown-field validation will reject them); + existing DB columns are left as unmanaged leftovers, no migration needed. + + `@objectstack/verify` gains `BootOptions.extraPlugins` for booting optional + service pairs (e.g. storage + audit) in dogfood fixtures. + +### Patch Changes + +- 7c800c6: fix(storage): abort the backend multipart upload when reaping an abandoned sys_upload_session (#2970) + + The `sys_upload_session` lifecycle (added in #2984) reaps abandoned/terminal + chunked-upload session ROWS, but not the underlying backend multipart upload — + on S3 an initiated-but-not-completed multipart keeps its already-uploaded parts + billable and invisible to normal listing until an explicit + `AbortMultipartUpload`, so reaping only the row stranded them (with + `backend_upload_id`, the sole pointer, gone). + + `createUploadSessionReapGuard` registers a `LifecycleReapGuard` on + `sys_upload_session` that aborts the backend multipart before the row is + deleted: it skips `completed` sessions (their multipart already became a real + object — an abort would `NoSuchUpload`-error), re-seeds the S3 adapter's + `uploadId → key` map from the row (a cold sweep lacks the live in-process map), + and vetoes (keeps the row for retry) on abort failure so the pointer survives. + The local adapter's parts directory is removed the same way. + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [94b8e44] +- Updated dependencies [86c0aea] +- Updated dependencies [99755b5] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [eb89a8c] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [aead168] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/platform-objects@15.1.0 + - @objectstack/core@15.1.0 + - @objectstack/observability@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/services/service-storage/package.json b/packages/services/service-storage/package.json index 29f3970ac3..57b64fa2e6 100644 --- a/packages/services/service-storage/package.json +++ b/packages/services/service-storage/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-storage", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "Storage Service for ObjectStack — implements IStorageService with local filesystem and S3 adapter skeleton", "type": "module", diff --git a/packages/spec/CHANGELOG.md b/packages/spec/CHANGELOG.md index 14ca950030..dfe7360b9e 100644 --- a/packages/spec/CHANGELOG.md +++ b/packages/spec/CHANGELOG.md @@ -1,5 +1,483 @@ # @objectstack/spec +## 15.1.0 + +### Minor Changes + +- 7f68068: feat(discovery): honest capabilities — standardized stub/fallback marker + realtime route honesty (ADR-0076 D12/A1.5 framework slice, #2462) + + **Spec** — new service self-description marker for honest discovery + (ADR-0076 D12): `SERVICE_SELF_INFO_KEY` (`__serviceInfo`), + `ServiceSelfInfoSchema` / `ServiceSelfInfo`, and `readServiceSelfInfo()`, + which also normalizes plugin-dev's legacy `_dev: true` flag to + `{ status: 'stub', handlerReady: false }`. A registered service that is a + stub / dev fake / degraded fallback self-identifies via this marker; a fully + real service carries no marker. + + **Runtime + metadata-protocol** — both discovery builders + (`HttpDispatcher.getDiscoveryInfo` and the protocol shim's `getDiscovery`) + now honor the marker instead of hardcoding `status: 'available', +handlerReady: true` for every registered service. Dev stubs report `stub`, + the ObjectQL analytics fallback reports `degraded` (it keeps serving — no + `/analytics` 404), and consumers can finally trust + `status === 'available'` / `handlerReady === true`. + + **Realtime honesty fix** — discovery no longer advertises a + `/realtime` route or `websockets: true`: `service-realtime` is an + in-process pub/sub bus, no dispatcher branch or plugin mounts any + `/realtime` HTTP surface, so the advertised route always 404'd. The + registered service now reports `status: 'degraded', handlerReady: false` + with no route (clients using the SDK are unaffected — it falls back to the + conventional path, which behaves exactly as before). Also corrects the + advertised realtime provider from the nonexistent `plugin-realtime` to + `service-realtime`. + + **REST (A1.5)** — the REST layer's protocol dependency is narrowed from the + `ObjectStackProtocol` god-union to the new `RestProtocol = +DataProtocol & MetadataProtocol` slice (exported from + `@objectstack/rest`), per the ADR-0076 D9 incremental narrowing guidance. + Type-level only; no runtime change. + +- 8fc1208: feat(protocol): complete ADR-0087 — load-seam handshake, chain backfill 12–15, release artifacts (#2643) + + Closes the remaining ADR-0087 gaps (see the ADR's as-built Addendum): + + - **P0 load seams (D1).** The protocol handshake now runs on the boot-time + durable-package rehydration path (`@objectstack/service-package` refuses an + incompatible `sys_packages` row with the structured `OS_PROTOCOL_INCOMPATIBLE` + diagnostic and keeps booting) and on `AppPlugin` for code-defined stacks + (fail-fast before the manifest is decomposed). `objectstack lint` gains + `protocol/missing-engines-range` (warning + fix-it) and the + `create-objectstack` blank template stamps `engines: { protocol: '^' }` + (re-stamped at version time by `scripts/sync-template-versions.mjs`) — the + two ends of the grandfathering ratchet. + - **Chain backfill (D2/D3).** `MetadataConversion.retiredFromLoadPath` + implements the load-window's second half (retired entries replay only via + `migrate meta` / fixture CI). Steps 12–15 land: the `api.requireAuth` flip + (semantic), the ADR-0090 wave (3 retired conversions + 5 semantic TODOs), the + `BookAudience` rename (retired conversion), and the ADR-0089 visibility + unification (`visibleOn`/`visibility` → `visibleWhen` as LIVE load-window + conversions) + the `.strict()` flip (semantic). The protocol-11 + `compactLayout` → `highlightFields` rename is backfilled as a retired step-11 + conversion. `migrate meta --from 10` now reaches protocol 15. + - **Release artifacts (D4).** `spec-changes.json` is generated from the + registries (`gen:spec-changes`, CI drift-checked), ships in the npm artifact + together with `api-surface.json`, and is attached to each `@objectstack/spec` + GitHub Release with `added[]`/`removed[]` filled from the api-surface diff + against the previously published release. The upgrade guide + (`docs/protocol-upgrade-guide.md`) is generated from the same registries and + CI drift-checked — a projection that cannot drift. + +- 96a14d0: feat(connectors): ADR-0096 — provider-bound declarative connector instances materialized at boot (#2977) + + Declarative `connectors:` stack entries used to be **descriptor-only** (#2612): + registered as metadata but never dispatchable, the platform's one dead metadata + surface. An entry may now name a **`provider`** — an installed generic executor + (`openapi` / `mcp` / `rest`) — and the automation service **materializes** it + into a live, dispatchable connector at boot. AI can now wire an integration as + pure metadata and a flow `connector_action` calls it end-to-end. + + - **Schema (`@objectstack/spec`).** `ConnectorSchema` gains `provider`, + `providerConfig`, and `auth` (a `credentialRef`-based instance-auth shape — + `ConnectorInstanceAuthSchema` — that references credentials, never inlines + them); `authentication` now defaults to `{ type: 'none' }` so a provider-bound + instance need not author it (loosening — existing connectors are unaffected). + `DeclarativeConnectorEntrySchema` (used by `stack.zod.ts`) rejects inline + secrets, orphan `providerConfig`/`auth`, and authored `actions`/`triggers` on a + provider-bound entry. A new `integration/connector-provider.ts` defines the + provider-factory contract as pure types. + + - **Engine + boot (`@objectstack/service-automation`).** The engine adds a + connector-provider registry (`registerConnectorProvider`/`getConnectorProvider`) + and origin-tags registered connectors. At boot the service resolves each + provider-bound entry — looking up the factory, resolving `auth.credentialRef` + via a pluggable `CredentialResolver` (open-tier default: environment + variables), and registering the materialized connector. Boot **fails loudly** + for an unknown provider, invalid `providerConfig`, an unresolvable + `credentialRef`, or a name conflict with a plugin-registered connector (no + silent precedence). + + - **Providers (`connector-rest` / `connector-openapi` / `connector-mcp`).** Each + plugin registers a provider factory in `init()` reusing its existing + generator/adapter API. Plugin options are now **optional**: with none the + plugin contributes only its provider factory; with instance options it also + registers a hand-wired connector (back-compat). `connector-openapi` adds a + `ConnectorOpenApiPlugin`. + + Open tier: static auth (`none`/`api-key`/`basic`/`bearer`) with `credentialRef` + resolved from env vars. Managed vaulting, OAuth2 refresh, and per-tenant + connection lifecycle remain the enterprise tier (ADR-0015) — an enterprise host + injects a vault-backed `CredentialResolver` with no change to the materialization + path. + +- 10a570a: feat(connector-openapi): resolve `providerConfig.spec` from a package-relative file path (#3016, ADR-0096 follow-up) + + ADR-0096's canonical example authors an OpenAPI-backed instance as + `providerConfig: { spec: './billing-openapi.json' }`, but the landed `openapi` + provider factory only accepted an inline document object or an http(s) URL. + The spec union is now complete: **inline object | file path | remote URL**. + + - **`@objectstack/spec`.** `ConnectorProviderContext` gains an optional + host-injected `loadPackageFile(relativePath)` capability (pure type): reads a + UTF-8 file resolved against the declaring stack/package root, confined to + that root. `undefined` on hosts without a filesystem. + + - **`@objectstack/service-automation`.** New `packageRoot` plugin option (the + base for relative file refs; defaults to `process.cwd()`) and an exported + `createPackageFileLoader(packageRoot)` that implements the confinement + guard — absolute paths and `..`-escaping paths are rejected — with lazy + `node:fs`/`node:path` imports so non-Node hosts only fail if a file ref is + actually dereferenced. The materializer injects the capability into every + provider factory's context. Failures follow the existing reconcile policy: + **fatal at boot, entry skipped on reload**. + + - **`@objectstack/connector-openapi`.** A string `providerConfig.spec` that is + not an http(s) URL is now read via `ctx.loadPackageFile` and parsed as an + OpenAPI JSON document (clear errors for missing/unreadable files, unparseable + JSON, and hosts without package file access). + + - **`@objectstack/cli`.** `serve`/`dev` pass the project folder (the + `objectstack.config.ts` directory) as the automation service's `packageRoot`, + mirroring how the standalone sqlite default is anchored. + +- 4f8c2d1: feat(connectors): degrade + retry declarative instances whose upstream is unreachable (#3017) + + ADR-0097 kept every declarative-connector materialization failure fatal at + boot. That is right for configuration faults (unknown provider, invalid + `providerConfig`, unresolvable `credentialRef`, name conflict) but wrong for + _operational_ ones: a `provider: 'mcp'` instance must contact its MCP server + (`tools/list`) to materialize, and a transient network blip aborted the whole + app boot. + + - **spec**: a provider factory can now throw + `ConnectorUpstreamUnavailableError` (code `CONNECTOR_UPSTREAM_UNAVAILABLE`, + structural guard `isConnectorUpstreamUnavailable`) to mark a failure as + "upstream temporarily unreachable — degrade and retry" instead of fatal. + - **service-automation**: the reconcile degrades such an instance in both boot + and reload modes: it registers an action-less husk (`state: 'degraded'` + + `degradedReason` on the `GET /connectors` descriptor) so the instance is + visible instead of silently missing — or, on a changed-config + re-materialization, keeps the old connector serving. A `connector_action` + against a degraded instance fails with the reason and a "retries + automatically" pointer. Degraded instances retry on an exponential backoff + (5s → 5min, reset by config edits) and on every `metadata:reloaded` + reconcile; recovery swaps the husk for the live connector atomically. + Reconcile runs (boot / reload / retry timer) are now serialized. + - **connector-mcp**: the `mcp` provider classifies connect / `tools/list` + failures as upstream-unavailable; transport-shape validation stays a plain + (fatal) throw. + + Configuration faults remain loud boot failures — the carve-out is only for the + unavailable marker. + +- c11e24b: fix(authz): carry the derived posture rung on ExecutionContext (#2947) + + The ADR-0095 D2 posture ladder (`PLATFORM_ADMIN > TENANT_ADMIN > MEMBER > +EXTERNAL`) is derived once by the shared authz resolver from capability grants, + but both HTTP/MCP entry points that build the `ExecutionContext` dropped it — + so any enforcement-side reader of `context.posture` always saw `undefined` + (the same drop that forced the explain layer to re-derive it, #2949). + + `ExecutionContextSchema` now carries an optional `posture` field, and both + `rest-server` and the runtime `resolveExecutionContext` plumb the resolver's + value through. Additive and **behavior-preserving**: no enforcement decision + consumes `posture` yet — whether the hot path evaluates _by_ posture remains a + larger ADR-level decision — this only stops the already-computed value from + being discarded, so enforcement and explain read the same derived rung. + +- bf1720b: Dashboard-level filters spec pairing (framework#2501, objectui#2578) — land the + two properties the objectui runtime already ships (objectui#2576) so the + protocol and the renderer agree: + + - **`GlobalFilterSchema.name`** (optional string) — stable filter name used as + the dashboard-variable key (readable in widget expressions as `page.`) + and as the key widgets reference in `filterBindings`. Defaults to `field`; + `"dateRange"` is reserved for the built-in dashboard date range. + - **`DashboardWidgetSchema.filterBindings`** (optional + `Record`) — per-widget binding from a dashboard + filter name to one of THIS widget's fields: a string re-targets the filter to + that field, `false` opts the widget out, absent falls back to the filter's + own `field`. + + Purely additive — existing dashboards parse unchanged. The metadata-admin + dashboard inspector (objectui `dashboard-schema.ts`) derives its form from this + schema via `z.toJSONSchema`, so both properties surface there automatically + once objectui picks up this spec version. + +- 663e7d6: feat(spec): structured `buttons` + `defaults` config on `FormViewSchema` (#2998) + + `FormViewSchema` gains two optional top-level keys — the spec home for the flat + renderer-invented form config ObjectUI's `ObjectForm` reads today + (`showSubmit`/`submitText`/`showCancel`/`cancelText`/`showReset`/`initialValues`, + objectui#2545), which the strip-mode container silently discards: + + - **`buttons`** — structured action-button config: per-button `{ show, label }` + for `submit` / `cancel` / `reset` (new exported leaf `FormButtonConfigSchema`, + `.strict()` per ADR-0089 D3a so typo'd keys error loudly). + - **`defaults`** — initial field values for create-mode forms, keyed by field + machine name (absorbs ObjectUI's `initialValues`). + + Both are marked `[EXPERIMENTAL — NOT ENFORCED]` per ADR-0078's escape hatch + until the ObjectUI renderer reads them (tracked in objectui#2545); authoring + them today is declared, not yet honored. Purely additive — no existing key + changes shape, no tombstone needed. + +- 464418e: feat(kernel): add `kernel:bootstrapped` lifecycle anchor — the phase that fires after every `kernel:ready` handler has settled but before `kernel:listening` (HTTP socket open). `kernel:ready` handlers run sequentially in plugin-registration order, so a handler that consumes data produced by a later-starting plugin (e.g. the security bootstrap seeds `sys_position`; the app plugin's seed loader inserts records) would race the very rows it needs. `kernel:bootstrapped` is the correct anchor for reconcile/backfill work: every producer's ready handler has finished by the time it fires. Both `ObjectKernel` and `LiteKernel` trigger it. The sharing-rule boot backfill moves from `kernel:listening` to `kernel:bootstrapped` (semantics-only; behaviour unchanged). +- 23925e9: fix(plugin-auth): re-run membership backfill when app seeding settles (#2996) + + The ADR-0093 D6 membership backfill — the only safety net for users created + by app seeds (raw `engine.insert` into `sys_user` bypasses better-auth's + `user.create.after` reconciler) — ran only once on `kernel:ready`. When a seed + bundle overruns its inline budget (`OS_INLINE_SEED_BUDGET_MS`, default 8s) it + finishes in the background _after_ `kernel:ready`, so its users stayed + member-less in single-org `auto` mode until the next restart re-ran the backfill. + + `AppPlugin` now emits a new **`app:seeded`** lifecycle event when an app's inline + seed settles (success, partial, or fallback) — carrying `{ appId, overBudget }`, + where `overBudget: true` marks the post-`kernel:ready` background case. plugin-auth + subscribes and re-runs the (idempotent, self-guarding, opt-out-able) + `backfillMemberships` on that signal, closing the window without waiting for a + restart. No behavior change when a seed completes within budget, in multi-tenant + mode, or under `invite-only` policy; `OS_SKIP_MEMBERSHIP_BACKFILL=1` still opts out. + +- c64ee8c: Conditional tabs (#2606): `page:tabs` items accept an optional `visibleWhen` CEL predicate. When it evaluates FALSE the whole tab — header **and** panel — is omitted from the tab strip, unlike a child component's own `visibleWhen`, which hides only the panel content and leaves an empty tab header behind. The predicate binds the same environment as page-component `visibleWhen` (`record` + `current_user`, plus page state as `page.`) and is re-evaluated live when page variables change. + + Per ADR-0089 the key uses the canonical `*When` name from day one — the deprecated `visibility` / `visibleOn` aliases are **not** accepted on tab items (this surface is new; there is no legacy metadata to alias for). + + Additive and back-compatible: items without `visibleWhen` behave exactly as before. + +- ddc2bad: feat(spec): page variable `source` renders as a component picker (objectui#2328) + + The page metadata form's `variables` repeater now declares explicit sub-fields + and pins `{ field: 'source', widget: 'ref:component' }`. A page variable's + `source` names the component (by `id`) that writes it, so Studio can offer it as + a dropdown of the components actually placed on the page — mirroring how the + sibling `object` field uses `ref:object` — instead of a free-text input the + author has to type an id into by hand. The `ref:component` widget itself lives + in objectui (app-shell metadata-admin); this change is the form-spec trigger. + +- f71d19a: feat(spec)!: remove `tenancy.strategy` + `tenancy.crossTenantAccess`; tenancy block is now strict (#2763) + + > ⚠️ RELEASE NOTE — breaking by strict semver, shipped as `minor` per the + > launch-window policy (owner decision on PR #2962): the fields had zero + > consumers, behavior is unchanged, and the parse error carries the + > migration. Fold into the v15 release page's "What's new in 15.x" section + > when versioning. + + BREAKING CHANGE: `TenancyConfigSchema` drops its two zero-consumer fields, and + the `tenancy` block is now `.strict()` — an unknown key is a loud parse error + with tombstone guidance instead of a silent zod strip (#1535; precedent + ADR-0056 D8 "compliance-grade config must never merely look live", ADR-0049 + enforce-or-remove). + + The platform has exactly two tenancy modes, and neither needs object-level + strategy config: database-per-tenant isolation is an environment/deployment + choice (each environment carries its own database URL), and shared-database + row isolation is `tenancy.enabled` + `tenancy.tenantField` (both stay, both + live: sql-driver row scoping, security-plugin org scoping). Cross-tenant + visibility is governed by sharing rules / OWD (ADR-0056), + `externalSharingModel` (ADR-0090 D11), and the object access posture — never + by a blanket boolean. + + Migration (delete the keys; nothing read them, so behavior is unchanged): + + - FROM `tenancy: { enabled: false, strategy: 'shared' }` → TO `tenancy: { enabled: false }` + - FROM `tenancy: { enabled: true, strategy: '...', tenantField: 'x', crossTenantAccess: false }` → TO `tenancy: { enabled: true, tenantField: 'x' }` + - Wanted per-tenant databases? Deploy per environment (EnvironmentKernelFactory) — not object metadata. + - Wanted cross-tenant visibility? Use sharing rules / OWD or `externalSharingModel`. + + The compile-time authorWarn for these fields (#2750) and their liveness-ledger + entries are retired with the removal; the schema itself now carries the + prescription. + +- 6c114c0: Retire the "ObjectOS" layer name from the spec's public surface — the control layer is the **Kernel**; ObjectOS now exclusively names the commercial runtime environment. + + Renames (deprecated aliases kept for one release, so existing imports keep compiling): + + - `ObjectOSCapabilitiesSchema` → `KernelCapabilitiesSchema` + - `ObjectOSCapabilities` (type) → `KernelCapabilities` + - `ObjectOSKernel` (interface) → `IKernel` (`PluginContext.os` is now typed as `IKernel`) + + Migration: replace the old names with the new ones — a find/replace of the three identifiers above is sufficient; runtime behavior, schema shapes, and JSON output are unchanged. TSDoc and generated reference docs now say "the ObjectStack runtime" / "Kernel" instead of "ObjectOS" (product mentions like ObjectOS Cloud in the Cloud protocol domain are unchanged). + +- 2973f7f: feat(spec,cli): enroll `view` in the liveness ledger (#2998 Track B) + + `view` joins the `GOVERNED` set of the spec property-liveness gate — the + rollout gap that let the objectui#1763/#2545 class of renderer/spec key drift + survive undetected. New `packages/spec/liveness/view.json` classifies all 83 + walkable properties (75 ledger entries + framework overlay fields): the `list` + and `form` containers are drilled one level via `children`. + + Seeded from the 2026-06 viewschema audit and **re-verified against objectui + HEAD** — four audit-era DEAD findings had since gone live and are classified + from current reads (`form.submitBehavior`, `list.sharing.lockedBy`, list-path + `ViewData` providers, and the post-ADR-0021 `list.chart` dataset shape — the + audit's "chart renderers never migrated" headline is resolved). Final tally: + 68 live, 2 experimental (`form.buttons`/`form.defaults`, #2998 Track A + awaiting objectui#2545), 5 dead (`list.responsive`, `list.performance`, + `form.data`, `form.defaultSort`, `form.aria`). All misleading dead props + carry `authorWarn` + `authorHint`. + + The CLI's compile-time liveness lint gains `view` coverage + (`TYPE_COLLECTIONS` + view containers labelled by `object`), so authoring a + dead prop — e.g. a spec-valid `chart` list view that renders empty — now warns + at `os build` with a corrective hint. + +### Patch Changes + +- fad8e49: docs(spec): retire the stale `renderViaSchema` forward-reference now that objectui#2546 landed (ADR-0085 PR4 follow-up, #2548) + + The `ObjectSchema` source comment forward-referenced `renderViaSchema` + retiring "together with the legacy monolith render path" — a promise about + work that had not yet shipped. That path, and the `detail.renderViaSchema` + kill-switch that was its only steering wheel, were removed in objectui#2546 + (ADR-0085 PR4). The comment now records the completed state with a breadcrumb + to that PR instead of a forward reference, closing the cleanup #2546 flagged. + + Comment-only change; no type, schema, or runtime behavior is affected. + +- d8f7f6a: feat(automation): descriptor-only contract + boot audit for declarative `connectors:` (#2612) + + Declarative `connectors:` stack entries never reach the automation engine's + connector registry — only plugins populate it via + `engine.registerConnector(def, handlers)` (ADR-0018 §Addendum) — so a declared + connector with actions and no plugin behind it _looked_ dispatchable but was + silently inert. + + The contract is now explicit and audited: + + - **Boot audit (service-automation).** At `kernel:ready` (and again on + `metadata:reloaded`), declared connectors with `actions` but no same-name + runtime registration log a loud warning naming each inert entry and + pointing at the fix (install the matching connector plugin, or mark a + deliberate catalog entry). Nothing is registered on your behalf — the + warning surfaces the gap `connector_action` would otherwise hit at + dispatch time. + - **`enabled: false` = deliberate catalog descriptor (spec).** Setting it on + a declarative entry documents "descriptor-only on purpose" and silences the + audit. Schema docs on `stack.zod.ts` (`connectors:`) and + `integration/connector.zod.ts` now state the descriptor-vs-registered + contract explicitly (including for AI stack authoring via `.describe()`). + + Declarative provider-bound connector _instances_ — entries a generic executor + (connector-openapi / connector-mcp) materializes into live connectors at boot, + upgrading this warning to a hard error — are specified in ADR-0096 and tracked + in #2977. + +- 929efdf: docs(security): document that `requireAuth` denies anonymous across ALL HTTP surfaces (#2567) + + The `api.requireAuth` schema description and JSDoc said the anonymous-deny + posture applied to REST `/data/*` only. Post-#2567 the same value is threaded to + every entry point that reaches object data — REST `/data`, the metadata + endpoints (`/meta`), the dispatcher GraphQL endpoint (`/graphql`), and the + raw-hono standard `/data` routes — sharing one decision (`shouldDenyAnonymous`). + The description now reflects the uniform, by-surface posture and the single + opt-out (`requireAuth: false`). Doc-only; no behavior change. + + (Accompanying hand-written docs — `permissions/authorization.mdx` and the + regenerated `references/api/rest-server.mdx` — are updated to match.) + +- 0f8db52: docs(spec): `readonly` is server-enforced on UPDATE, not a UI-only affordance (#3003) + + The `readonly` field property was described as "Read-only in UI", which #3003 + proved to be exactly how integrators read it — approval/status/amount columns + protected only by `readonly: true` were forged with a direct REST `PATCH`, + self-approving a multi-stage approval on the released 15.0.0. Since #2948 the + engine strips caller-supplied writes to statically-readonly fields from every + non-system UPDATE (single-id and multi-row, symmetric with `readonlyWhen`; + INSERT may still seed the column). The schema description and the field + liveness ledger now state the server-side contract, and a dogfood conformance + proof (`showcase-static-readonly.dogfood.test.ts` + an authz-matrix row) pins + it end-to-end so it cannot silently regress to renderer-only. + +- e7d5291: **Every feature-gated capability is now UI-gated, guardrailed by a flag registry and a declarative `requiresFeature` annotation (#2874, generalizing the create-user phone fix #2871).** + + `@objectstack/spec/kernel` gains `PUBLIC_AUTH_FEATURES` — a classification registry for all 13 boolean flags served at `/api/v1/auth/config`: consumption surface (crud/login/status), default semantics (opt-in `== true` vs default-on `!= false`), and the gated spec inputs or an exemption reason. A plugin-auth drift test pins the served key set to the registry, and a platform-objects completeness guard pins the registry to the actual gates in both directions. + + `ActionSchema`/`ActionParamSchema` gain `requiresFeature: ''` (enum-checked), lowered at parse time into the canonical `visible` CEL predicate per the flag's registered semantics, AND-composed with any explicit `visible`, and stripped from the output — renderers and lint see only `visible`, so objectui needs no changes. All 22 hand-written `features.*` gates migrated (behavior-locked by an exact-string matrix test), and the audit gated 17 previously naked capability-dependent actions: the six `sys_user` platform-admin actions, six 2FA actions, and five `sys_oauth_application` actions now hide when their plugin is off instead of rendering buttons that 404. + +- 59cd765: fix(security): pre-wiring identity admission for the GraphQL and realtime surfaces (#2992, ADR-0096 D4) + + Two latent execution surfaces — neither reachable by a client today — would + have fallen open the instant a real transport was wired, because both drop or + lack the caller's identity. Per ADR-0096, the identity story is fixed and + pinned in CI _before_ wiring, not after an adversarial review: + + - **GraphQL (surface 1 — latent context-drop, now threaded).** + `handleGraphQL` passed only `{ request }` to `kernel.graphql`, dropping the + resolved `ExecutionContext` — the moment a real engine resolved objects + through ObjectQL it would have run context-less (security middleware falls + OPEN on a missing principal = full authority). The entry point now resolves + the caller identity even on the direct dispatcher-plugin route and even when + `requireAuth` is off, and threads it as `options.context`; + `IGraphQLService.execute` documents that implementations MUST forward it to + every data-engine call. Unit-proven; the authz conformance matrix pins the + threading (`graphql-identity-thread` row) so removing it goes STALE and + fails CI. + + - **realtime (surface 2 — no per-recipient authz seam, posture registered).** + Delivery is a pure fan-out (subscriptions carry no principal, + `matchesSubscription` filters only by object+eventTypes, the engine + publishes the full `after` row), safe only while every subscriber is + server-internal. The posture is now registered as an `experimental` matrix + row (`realtime-delivery-authz`) stating the admission requirement + (per-recipient RLS/FLS/tenant re-check on delivery, or id-only payload + + client re-fetch), and transport TRIPWIRE probes turn any newly wired + WebSocket/SSE/subscribe/client transport into an UNCLASSIFIED surface → red + CI until the identity story ships with it. The `service-realtime` README — + which advertised `authorizeChannel`/`broadcastToUser`/presence auth that do + not exist — is rewritten to describe the real, trusted-internal-only + surface, and the contract docs carry the admission requirement at the seam. + +- d918c9f: fix(spec): keep `lazySchema` proxies identity-compatible with `z.toJSONSchema` (objectui#2561) + + zod's `toJSONSchema` keys its `seen` map on the node object it traverses — the `lazySchema` Proxy wherever a schema is referenced lazily (`z.lazy(() => X)` recursion getters, direct conversion roots) — while its wrapper-type processors (pipe/lazy/optional/default/…) look themselves up via the REAL instance captured at construction (`inst._zod.processJSONSchema = (ctx, …) => pipeProcessor(inst, …)`). The identity mismatch crashed conversion with `Cannot set properties of undefined (setting 'ref')`. + + This stayed latent while lazy-referenced schemas were plain objects (the object processor never looks itself up); ADR-0089 D3a turned `PageComponentSchema` / `FormFieldSchema` into `.strict().transform(…)` **pipes**, which broke ObjectUI Studio's spec-derived Page/View inspector JSONSchema derivation under spec 15. + + Fix: the proxy now serves a memoised `_zod` facade that prototype-delegates to the real internals and wraps only `processJSONSchema` to alias the proxy's `seen` entry onto the real instance before delegating. Parse behavior is unchanged; `OS_EAGER_SCHEMAS=1` remains the bypass. Regression tests cover the D3a pipe shape, recursion through `z.lazy(() => proxy)`, mixed proxy+real traversal, and the full `PageSchema` / `ViewSchema` Studio derivation paths. + +- aaec5db: fix(security): public-form submissions can no longer forge server-managed anchors (#3022) + + The anonymous public-form surface (ADR-0056 Option A, `POST /forms/:slug/submit`) + is authorized by the declaration-derived `publicFormGrant`, which short-circuits + the security middleware BEFORE every write gate (CRUD, FLS, the owner anchor + guard, the tenant CHECK). The only field-side defense was the route's + declared-field allow-list — and a FormView with zero declared section fields + fell back to merging the raw body wholesale, so an unauthenticated visitor + could `POST owner_id=` (or `organization_id`, audit columns, `id`) and + attach the record to another user or tenant — the #3004 insert-forge, with no + credentials at all. + + Server-managed anchors are now enforced on this surface at BOTH layers, from a + single shared definition (`PUBLIC_FORM_SERVER_MANAGED_FIELDS`, new in + `@objectstack/spec/security`): + + - **Data layer (authoritative)** — the `publicFormGrant` branch in + `@objectstack/plugin-security` strips `id` / `owner_id` / `organization_id` / + `tenant_id` / audit columns / soft-delete state / `__search` from every row + of a granted insert (batch included) before admitting the write, so the + boundary holds no matter what any route lets through. Ownership stays NULL + for object hooks / the first-admin bootstrap to assign, as for other + anonymous-seeded rows. + - **Route layer** — the submit allow-list excludes the same set + unconditionally: an explicitly declared `owner_id` section field no longer + passes, and the zero-declared-sections fallback keeps its documented + all-fields behavior for business columns while refusing the managed set. + The resolve route (`GET /forms/:slug`) drops the managed fields from the + rendered sections and the embedded object schema so a form never collects a + value the submit refuses, and `GET /forms/:slug/lookup/:field` refuses a + `publicPicker` declared on a managed anchor (which would have opened + anonymous `sys_user` search through `owner_id`). + + Authenticated writes are unaffected — this is the anonymous-surface rule only; + `owner_id` transfer semantics for signed-in callers stay governed by the + transfer grant (#3004 / PR #3018). + +- c5e68b2: Retire "ObjectOS" as the control-layer name in the published agent prompts (`prompts/`): the open control layer is now called the **Kernel**; **ObjectOS** exclusively names the commercial runtime environment. Layer vocabulary is now ObjectQL (data) / Kernel (control) / ObjectUI (view). Prompt text only — no schema changes. +- 28ba0c7: feat(plugin-sharing): sys_sharing_rule provenance + seed-not-clobber (#2909 P0/T1). The object gains readonly `managed_by` (unified A4 tri-state platform/package/admin) and `customized` columns; declared rules seed with `managed_by: 'package'`. defineRule in seed mode adopts pristine/legacy rows (package upgrades stay deliverable) but never overwrites admin-authored or customized rows — an admin's `active: false` on an over-sharing rule now survives redeploys instead of being resurrected at boot. A beforeUpdate hook stamps `customized` on any non-system edit of a seeded rule. Deliberately NO write gate: sharing rules remain a first-class admin authoring surface (ADR-0094 addendum tradeoff). +- 28ba0c7: docs(spec): rewrite the `isDefault` permission-set docs to describe the actual dual-track behavior (#2926 ②): app-level `isDefault` sets are resolved as the SecurityPlugin's fallback and idempotently auto-bound to the `everyone` anchor at boot (guarded by the high-privilege-bits check), while package-level sets are never auto-bound and instead materialize a `sys_audience_binding_suggestion` an admin confirms. The previous "never auto-bound" wording contradicted the shipped app-level track. + ## 15.0.0 ### Major Changes diff --git a/packages/spec/package.json b/packages/spec/package.json index ec45a81f01..360a40f943 100644 --- a/packages/spec/package.json +++ b/packages/spec/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/spec", - "version": "15.0.0", + "version": "15.1.0", "description": "ObjectStack Protocol & Specification - TypeScript Interfaces, JSON Schemas, and Convention Configurations", "license": "Apache-2.0", "main": "dist/index.js", diff --git a/packages/triggers/trigger-api/CHANGELOG.md b/packages/triggers/trigger-api/CHANGELOG.md index 2596bcf679..05b6746c22 100644 --- a/packages/triggers/trigger-api/CHANGELOG.md +++ b/packages/triggers/trigger-api/CHANGELOG.md @@ -1,5 +1,39 @@ # @objectstack/trigger-api +## 15.1.0 + +### Patch Changes + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [99755b5] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/core@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/triggers/trigger-api/package.json b/packages/triggers/trigger-api/package.json index e0a99eca06..5c8f1eec70 100644 --- a/packages/triggers/trigger-api/package.json +++ b/packages/triggers/trigger-api/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/trigger-api", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "Inbound HTTP/webhook flow trigger for ObjectStack — per-flow HMAC-verified endpoints with queue-backed ingestion (ADR-0041)", "main": "dist/index.js", diff --git a/packages/triggers/trigger-record-change/CHANGELOG.md b/packages/triggers/trigger-record-change/CHANGELOG.md index d0fbef0c2e..d936cb7e52 100644 --- a/packages/triggers/trigger-record-change/CHANGELOG.md +++ b/packages/triggers/trigger-record-change/CHANGELOG.md @@ -1,5 +1,39 @@ # @objectstack/plugin-trigger-record-change +## 15.1.0 + +### Patch Changes + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [99755b5] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/core@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/triggers/trigger-record-change/package.json b/packages/triggers/trigger-record-change/package.json index 7dd174f8c4..f11ccc1f7c 100644 --- a/packages/triggers/trigger-record-change/package.json +++ b/packages/triggers/trigger-record-change/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/trigger-record-change", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "Record-change flow trigger for ObjectStack — auto-launches flows on object insert/update/delete via ObjectQL lifecycle hooks (ADR-0018)", "main": "dist/index.js", diff --git a/packages/triggers/trigger-schedule/CHANGELOG.md b/packages/triggers/trigger-schedule/CHANGELOG.md index 63c8d37bf2..170af33b83 100644 --- a/packages/triggers/trigger-schedule/CHANGELOG.md +++ b/packages/triggers/trigger-schedule/CHANGELOG.md @@ -1,5 +1,39 @@ # @objectstack/plugin-trigger-schedule +## 15.1.0 + +### Patch Changes + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [99755b5] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/core@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/triggers/trigger-schedule/package.json b/packages/triggers/trigger-schedule/package.json index d0948772f9..130af5e49e 100644 --- a/packages/triggers/trigger-schedule/package.json +++ b/packages/triggers/trigger-schedule/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/trigger-schedule", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "Schedule flow trigger for ObjectStack — auto-launches flows on a cron/interval/once schedule via the IJobService (ADR-0018)", "main": "dist/index.js", diff --git a/packages/types/CHANGELOG.md b/packages/types/CHANGELOG.md index 73165fd319..60a4bf6127 100644 --- a/packages/types/CHANGELOG.md +++ b/packages/types/CHANGELOG.md @@ -1,5 +1,51 @@ # @objectstack/types +## 15.1.0 + +### Minor Changes + +- 1c58abd: Generic pinyin search recall (#2486, ADR-0098): a locale-gated + `OS_SEARCH_PINYIN_ENABLED` switch (auto-on when the stack configures any + `zh-*` locale) provisions a hidden `__search` companion column for each + object's display/name field at compile time, the new + `@objectstack/plugin-pinyin-search` fills it with full pinyin + initials + ("张伟" → "zhangwei zw") on before-save (plus boot backfill and a + `rebuildSearchCompanion` reconcile entry), and `$search` ORs the column in at + query time — so lookup pickers, list quick-search and ⌘K transparently match + `zhangwei` / `zw` against CJK names. Purely additive: `resolveSearchFields`, + `searchableFields`, drivers and non-Chinese deployments are untouched; FLS + restricted / secret / PII fields never feed the companion. + +### Patch Changes + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [4f8c2d1] +- Updated dependencies [c11e24b] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [23925e9] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/types/package.json b/packages/types/package.json index 1cd2887015..523d73a6f9 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/types", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "Shared interfaces describing the ObjectStack Runtime environment", "main": "dist/index.js", diff --git a/packages/verify/CHANGELOG.md b/packages/verify/CHANGELOG.md index 4a313d9927..a4cd66df5c 100644 --- a/packages/verify/CHANGELOG.md +++ b/packages/verify/CHANGELOG.md @@ -1,5 +1,110 @@ # @objectstack/verify +## 15.1.0 + +### Minor Changes + +- 86c0aea: feat(attachments): sys_file orphan lifecycle + parent-derived attachment access (#2755) + + **Orphan lifecycle (ADR-0057).** Deleting a `sys_attachment` join row used to + orphan the backing `sys_file` row and its storage bytes forever. `sys_file` + now declares a lifecycle (`ttl 30d` on a new `deleted_at` tombstone for + orphans; `retention 7d onlyWhen status=pending` for abandoned uploads), the + storage plugin's new hooks tombstone a file when its LAST join row is deleted + (attachments scope only — `Field.file`/`Field.image`/avatar scopes are never + touched) and un-tombstone on re-attach, and a new LifecycleService **reap + guard** seam (`registerReapGuard`) re-verifies zero references at sweep time + and deletes the storage bytes before confirming each row reap. A guarded + object is never blind-deleted; an erroring guard fails safe (rows retained). + + **Attachment access (ADR-0049, Salesforce parent-derived semantics).** + `sys_attachment` create now requires caller READ visibility of the parent + record (403 `ATTACHMENT_PARENT_ACCESS`) and server-stamps `uploaded_by` from + the session (client value ignored); delete requires uploader-or-parent-editor + (403 `ATTACHMENT_DELETE_DENIED`). The storage upload routes require an + authenticated session when an auth service is wired (401 `AUTH_REQUIRED`; + bare kernels stay open) and stamp `owner_id` on new files. + + **REMOVED — `sys_attachment.share_type` / `sys_attachment.visibility`.** + Both fields were modeled in v1 with zero runtime consumers (ADR-0049 + parsed-but-unenforced). There is no replacement key: attachment access is + derived from the parent record by the hooks above. Writers of these fields + should simply stop sending them (unknown-field validation will reject them); + existing DB columns are left as unmanaged leftovers, no migration needed. + + `@objectstack/verify` gains `BootOptions.extraPlugins` for booting optional + service pairs (e.g. storage + audit) in dogfood fixtures. + +### Patch Changes + +- Updated dependencies [7f68068] +- Updated dependencies [fad8e49] +- Updated dependencies [8fc1208] +- Updated dependencies [96a14d0] +- Updated dependencies [10a570a] +- Updated dependencies [93fd58e] +- Updated dependencies [4f8c2d1] +- Updated dependencies [86c0aea] +- Updated dependencies [541b96a] +- Updated dependencies [4c46ee0] +- Updated dependencies [99755b5] +- Updated dependencies [93bb7b4] +- Updated dependencies [c11e24b] +- Updated dependencies [e0b049a] +- Updated dependencies [e9a2885] +- Updated dependencies [ef50372] +- Updated dependencies [bf1720b] +- Updated dependencies [d8f7f6a] +- Updated dependencies [929efdf] +- Updated dependencies [0f8db52] +- Updated dependencies [e7d5291] +- Updated dependencies [7bc9e79] +- Updated dependencies [663e7d6] +- Updated dependencies [59cd765] +- Updated dependencies [aeb2110] +- Updated dependencies [464418e] +- Updated dependencies [d918c9f] +- Updated dependencies [3dc9fce] +- Updated dependencies [fae5dd0] +- Updated dependencies [23925e9] +- Updated dependencies [7f9a795] +- Updated dependencies [6613ad0] +- Updated dependencies [a16972b] +- Updated dependencies [c64ee8c] +- Updated dependencies [ddc2bad] +- Updated dependencies [1c58abd] +- Updated dependencies [aead168] +- Updated dependencies [dee7feb] +- Updated dependencies [aaec5db] +- Updated dependencies [f71d19a] +- Updated dependencies [28ba0c7] +- Updated dependencies [c5e68b2] +- Updated dependencies [6c114c0] +- Updated dependencies [fdfe1d9] +- Updated dependencies [8b27dd7] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [e07645c] +- Updated dependencies [464418e] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [28ba0c7] +- Updated dependencies [2973f7f] + - @objectstack/spec@15.1.0 + - @objectstack/runtime@15.1.0 + - @objectstack/objectql@15.1.0 + - @objectstack/rest@15.1.0 + - @objectstack/plugin-hono-server@15.1.0 + - @objectstack/service-automation@15.1.0 + - @objectstack/core@15.1.0 + - @objectstack/plugin-security@15.1.0 + - @objectstack/plugin-sharing@15.1.0 + - @objectstack/plugin-auth@15.1.0 + - @objectstack/driver-sqlite-wasm@15.1.0 + - @objectstack/service-analytics@15.1.0 + - @objectstack/service-datasource@15.1.0 + - @objectstack/service-settings@15.1.0 + ## 15.0.0 ### Patch Changes diff --git a/packages/verify/package.json b/packages/verify/package.json index 740ceb8e52..a37a104c26 100644 --- a/packages/verify/package.json +++ b/packages/verify/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/verify", - "version": "15.0.0", + "version": "15.1.0", "license": "Apache-2.0", "description": "Boot any ObjectStack app in-process and verify it through the real HTTP stack — auto-derived CRUD round-trip fidelity plus the cross-owner RLS invariant. Catches runtime regressions that static checks miss.", "type": "module", diff --git a/packages/vscode-objectstack/CHANGELOG.md b/packages/vscode-objectstack/CHANGELOG.md index e9dd8c50ba..516b2787bd 100644 --- a/packages/vscode-objectstack/CHANGELOG.md +++ b/packages/vscode-objectstack/CHANGELOG.md @@ -1,5 +1,7 @@ # objectstack-vscode +## 15.1.0 + ## 15.0.0 ## 14.8.0 diff --git a/packages/vscode-objectstack/package.json b/packages/vscode-objectstack/package.json index 4824266da7..95a7fc1d67 100644 --- a/packages/vscode-objectstack/package.json +++ b/packages/vscode-objectstack/package.json @@ -2,7 +2,7 @@ "name": "objectstack-vscode", "displayName": "ObjectStack", "description": "ObjectStack Protocol — Autocomplete, validation, and inline diagnostics for .object.ts, .view.ts, and objectstack.config.ts files", - "version": "15.0.0", + "version": "15.1.0", "publisher": "objectstack", "license": "Apache-2.0", "repository": {