From ce3780b0ca1398aa341e3629dd24e793b560155f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 21 Jul 2026 06:04:26 +0000 Subject: [PATCH] chore: version packages --- .changeset/action-body-org-identifier.md | 12 - .changeset/action-param-widget-config.md | 14 - .changeset/action-result-dialog-i18n.md | 33 - .../adr-0097-openapi-upstream-classify.md | 20 - .changeset/adr-0099-p0-equivalence-gate.md | 5 - .changeset/adr-0099-p1-layer0-reads-rung.md | 9 - .changeset/adr-0099-p2prime-two-axis-cells.md | 5 - .changeset/adr-0103-managedby-write-policy.md | 25 - .changeset/analytics-drill-raw-totals.md | 7 - .changeset/annotate-schema-only-enums.md | 16 - .changeset/approvals-decision-attachments.md | 18 - .changeset/approvals-viewer-can-act.md | 26 - .../approver-type-org-membership-level.md | 44 - .changeset/automation-schema-resolver-e2e.md | 13 - .../betterauth-adapter-system-writes.md | 27 - .changeset/bulk-update-validation-rules.md | 6 - .changeset/bulk-write-hardening.md | 31 - .changeset/cli-memory-driver-dispatch.md | 18 - .changeset/cli-strip-arg-separator.md | 30 - .changeset/cli-turso-loud-error.md | 19 - .changeset/client-batch-transaction-sdk.md | 19 - .changeset/collapse-hook-event-taxonomy.md | 13 - .changeset/console-0318118e02fd.md | 9 - .changeset/console-2e7d7f0f7ee7.md | 29 - .changeset/console-3b2e4d98d904.md | 11 - .changeset/console-69fa5d163a97.md | 9 - .changeset/console-94d4876df090.md | 14 - .changeset/console-9a5f016f7d5c.md | 13 - .changeset/console-af1b0db96e44.md | 24 - .changeset/console-e164196801bd.md | 25 - .changeset/console-fd45313b4d00.md | 10 - .changeset/create-user-result-dialog-phone.md | 12 - .../cross-object-batch-authz-hardening.md | 35 - .changeset/dashboard-widget-strict-3251.md | 36 - .changeset/date-equality-runtime-fix.md | 40 - .changeset/date-field-equality-guardrail.md | 41 - .changeset/dev-loop-dx-p2.md | 13 - ...iscovery-transactional-batch-capability.md | 20 - ...cument-validation-governance-crossfield.md | 10 - .changeset/drill-range-datetime-tz.md | 25 - .changeset/driver-sql-sqlite3-peer.md | 21 - .changeset/drop-deprecated-types-tar.md | 16 - .changeset/field-form-summary-subfields.md | 16 - .changeset/fix-explain-ownership-values.md | 21 - ...ow-registration-schema-aware-validation.md | 26 - .changeset/flow-trigger-observability.md | 20 - ...ormula-null-guard-floor-date-arith-3306.md | 15 - .changeset/hook-nested-write-timeout.md | 24 - .../i18n-inline-label-default-locale.md | 24 - .changeset/import-users-auto-policy.md | 20 - .changeset/keyvalue-node-config-schemas.md | 26 - .changeset/lint-hide-platform-baseline.md | 5 - ...ollab-notifications-and-storage-objects.md | 43 - .changeset/logger-esm-file-destination.md | 7 - .changeset/logger-honors-no-color.md | 15 - .changeset/loop-collection-xexpression.md | 25 - .changeset/managed-deny-registry-drift.md | 11 - .changeset/managedby-engine-owned-bucket.md | 19 - .changeset/map-node-config-schema.md | 22 - .changeset/mcp-dev-connect-hint.md | 13 - .changeset/mcp-stdio-principal-admission.md | 26 - ...stdio-switch-split-and-dev-connect-hint.md | 28 - .changeset/mcp-validate-expression-tool.md | 28 - .../metadata-list-package-aware-dedup.md | 19 - .../metadata-register-notifies-watchers.md | 15 - .../objectql-checkboxes-option-gating.md | 10 - .changeset/ownership-record-model-field.md | 27 - .changeset/perf-timing-admin-detail.md | 29 - .changeset/perf-timing-per-request-gating.md | 29 - .changeset/pre.json | 188 ----- .changeset/publish-drafts-org-scope.md | 11 - .changeset/readonly-static-insert-strip.md | 49 -- ...econcile-system-append-only-api-methods.md | 33 - .changeset/region-aware-history-compaction.md | 23 - .changeset/remove-agent-visibility.md | 31 - .changeset/remove-capability-aliases-3308.md | 22 - .changeset/remove-dead-metadata-props-2377.md | 55 -- .../remove-form-surfaced-dead-props-2377.md | 47 -- .changeset/remove-session-tenantid-alias.md | 31 - .changeset/report-drill-down-range-filter.md | 26 - .changeset/retire-feed-contracts.md | 53 -- .changeset/retire-feed-discovery-surface.md | 25 - .changeset/retire-js-expression-dialect.md | 27 - .changeset/sandbox-cpu-budget.md | 39 - .changeset/sandbox-drop-asyncify.md | 26 - .changeset/sandbox-pump-idle-backoff.md | 20 - .changeset/sandbox-timeout-env-override.md | 33 - .../scaffold-default-connector-executors.md | 28 - .../scaffold-gitignore-survives-publish.md | 13 - .changeset/scaffold-pnpm11-build-approvals.md | 35 - .changeset/scaffold-skill-catalog-boundary.md | 5 - .changeset/seed-replay-lookup-corruption.md | 22 - .changeset/seed-replayer-skipped.md | 19 - .changeset/server-timing-perf-spans-2408.md | 19 - .../service-automation-test-hardening.md | 12 - .changeset/state-machine-initial-states.md | 20 - .changeset/summary-rollup-filter.md | 32 - .changeset/systemfields-drop-owner-key.md | 18 - .../tenant-scope-platform-global-3249.md | 27 - .changeset/tidy-views-guard.md | 7 - .changeset/tier4-type-soundness-warnings.md | 36 - .changeset/time-relative-trigger.md | 49 -- .changeset/trim-validation-delete-event.md | 5 - .changeset/trim-webhook-dead-triggers.md | 11 - .../unify-org-identifier-hook-session.md | 11 - ...iew-metadata-type-schema-runtime-shapes.md | 15 - .../viewfilterrule-operator-enum-3373.md | 14 - examples/app-crm/CHANGELOG.md | 57 ++ examples/app-crm/package.json | 2 +- examples/app-showcase/CHANGELOG.md | 67 ++ examples/app-showcase/package.json | 2 +- examples/app-todo/CHANGELOG.md | 70 ++ examples/app-todo/package.json | 2 +- examples/embed-objectql/CHANGELOG.md | 49 ++ examples/embed-objectql/package.json | 2 +- packages/adapters/hono/CHANGELOG.md | 27 + packages/adapters/hono/package.json | 2 +- packages/apps/account/CHANGELOG.md | 45 + packages/apps/account/package.json | 2 +- packages/apps/setup/CHANGELOG.md | 45 + packages/apps/setup/package.json | 2 +- packages/apps/studio/CHANGELOG.md | 45 + packages/apps/studio/package.json | 2 +- packages/cli/CHANGELOG.md | 404 +++++++++ packages/cli/package.json | 2 +- packages/client-react/CHANGELOG.md | 51 ++ packages/client-react/package.json | 2 +- packages/client/CHANGELOG.md | 147 ++++ packages/client/package.json | 2 +- packages/cloud-connection/CHANGELOG.md | 65 ++ packages/cloud-connection/package.json | 2 +- .../connectors/connector-mcp/CHANGELOG.md | 49 ++ .../connectors/connector-mcp/package.json | 2 +- .../connectors/connector-openapi/CHANGELOG.md | 68 ++ .../connectors/connector-openapi/package.json | 2 +- .../connectors/connector-rest/CHANGELOG.md | 49 ++ .../connectors/connector-rest/package.json | 2 +- .../connectors/connector-slack/CHANGELOG.md | 49 ++ .../connectors/connector-slack/package.json | 2 +- packages/console/CHANGELOG.md | 123 +++ packages/console/package.json | 2 +- packages/core/CHANGELOG.md | 129 +++ packages/core/package.json | 2 +- packages/create-objectstack/CHANGELOG.md | 98 +++ packages/create-objectstack/package.json | 2 +- packages/formula/CHANGELOG.md | 187 +++++ packages/formula/package.json | 2 +- packages/lint/CHANGELOG.md | 208 +++++ packages/lint/package.json | 2 +- packages/mcp/CHANGELOG.md | 131 +++ packages/mcp/package.json | 2 +- packages/metadata-core/CHANGELOG.md | 80 ++ packages/metadata-core/package.json | 2 +- packages/metadata-fs/CHANGELOG.md | 8 + packages/metadata-fs/package.json | 2 +- packages/metadata-protocol/CHANGELOG.md | 254 ++++++ packages/metadata-protocol/package.json | 2 +- packages/metadata/CHANGELOG.md | 74 ++ packages/metadata/package.json | 2 +- packages/objectql/CHANGELOG.md | 295 +++++++ packages/objectql/package.json | 2 +- packages/observability/CHANGELOG.md | 106 +++ packages/observability/package.json | 2 +- packages/platform-objects/CHANGELOG.md | 105 +++ packages/platform-objects/package.json | 2 +- packages/plugins/driver-memory/CHANGELOG.md | 49 ++ packages/plugins/driver-memory/package.json | 2 +- packages/plugins/driver-mongodb/CHANGELOG.md | 49 ++ packages/plugins/driver-mongodb/package.json | 2 +- packages/plugins/driver-sql/CHANGELOG.md | 136 ++++ packages/plugins/driver-sql/package.json | 2 +- .../plugins/driver-sqlite-wasm/CHANGELOG.md | 53 ++ .../plugins/driver-sqlite-wasm/package.json | 2 +- packages/plugins/embedder-openai/CHANGELOG.md | 43 + packages/plugins/embedder-openai/package.json | 2 +- .../plugins/knowledge-memory/CHANGELOG.md | 50 ++ .../plugins/knowledge-memory/package.json | 2 +- .../plugins/knowledge-ragflow/CHANGELOG.md | 50 ++ .../plugins/knowledge-ragflow/package.json | 2 +- .../plugins/plugin-approvals/CHANGELOG.md | 157 ++++ .../plugins/plugin-approvals/package.json | 2 +- packages/plugins/plugin-audit/CHANGELOG.md | 100 +++ packages/plugins/plugin-audit/package.json | 2 +- packages/plugins/plugin-auth/CHANGELOG.md | 105 +++ packages/plugins/plugin-auth/package.json | 2 +- packages/plugins/plugin-dev/CHANGELOG.md | 82 ++ packages/plugins/plugin-dev/package.json | 2 +- packages/plugins/plugin-email/CHANGELOG.md | 56 ++ packages/plugins/plugin-email/package.json | 2 +- .../plugins/plugin-hono-server/CHANGELOG.md | 154 ++++ .../plugins/plugin-hono-server/package.json | 2 +- .../plugins/plugin-pinyin-search/CHANGELOG.md | 29 + .../plugins/plugin-pinyin-search/package.json | 2 +- packages/plugins/plugin-reports/CHANGELOG.md | 51 ++ packages/plugins/plugin-reports/package.json | 2 +- packages/plugins/plugin-security/CHANGELOG.md | 95 +++ packages/plugins/plugin-security/package.json | 2 +- packages/plugins/plugin-sharing/CHANGELOG.md | 81 ++ packages/plugins/plugin-sharing/package.json | 2 +- packages/plugins/plugin-webhooks/CHANGELOG.md | 57 ++ packages/plugins/plugin-webhooks/package.json | 2 +- packages/qa/dogfood/CHANGELOG.md | 70 ++ packages/qa/dogfood/package.json | 2 +- packages/qa/downstream-contract/CHANGELOG.md | 43 + packages/qa/downstream-contract/package.json | 2 +- packages/qa/http-conformance/CHANGELOG.md | 11 + packages/qa/http-conformance/package.json | 2 +- packages/rest/CHANGELOG.md | 140 ++++ packages/rest/package.json | 2 +- packages/runtime/CHANGELOG.md | 375 +++++++++ packages/runtime/package.json | 2 +- packages/sdui-parser/CHANGELOG.md | 2 + packages/sdui-parser/package.json | 2 +- .../services/service-analytics/CHANGELOG.md | 98 +++ .../services/service-analytics/package.json | 2 +- .../services/service-automation/CHANGELOG.md | 246 ++++++ .../services/service-automation/package.json | 2 +- packages/services/service-cache/CHANGELOG.md | 53 ++ packages/services/service-cache/package.json | 2 +- .../service-cluster-redis/CHANGELOG.md | 44 + .../service-cluster-redis/package.json | 2 +- .../services/service-cluster/CHANGELOG.md | 49 ++ .../services/service-cluster/package.json | 2 +- .../services/service-datasource/CHANGELOG.md | 49 ++ .../services/service-datasource/package.json | 2 +- packages/services/service-i18n/CHANGELOG.md | 49 ++ packages/services/service-i18n/package.json | 2 +- packages/services/service-job/CHANGELOG.md | 51 ++ packages/services/service-job/package.json | 2 +- .../services/service-knowledge/CHANGELOG.md | 49 ++ .../services/service-knowledge/package.json | 2 +- .../services/service-messaging/CHANGELOG.md | 70 ++ .../services/service-messaging/package.json | 2 +- .../services/service-package/CHANGELOG.md | 51 ++ .../services/service-package/package.json | 2 +- packages/services/service-queue/CHANGELOG.md | 51 ++ packages/services/service-queue/package.json | 2 +- .../services/service-realtime/CHANGELOG.md | 80 ++ .../services/service-realtime/package.json | 2 +- .../services/service-settings/CHANGELOG.md | 55 ++ .../services/service-settings/package.json | 2 +- packages/services/service-sms/CHANGELOG.md | 49 ++ packages/services/service-sms/package.json | 2 +- .../services/service-storage/CHANGELOG.md | 93 +++ .../services/service-storage/package.json | 2 +- packages/spec/CHANGELOG.md | 766 ++++++++++++++++++ packages/spec/package.json | 2 +- packages/triggers/trigger-api/CHANGELOG.md | 49 ++ packages/triggers/trigger-api/package.json | 2 +- .../trigger-record-change/CHANGELOG.md | 87 ++ .../trigger-record-change/package.json | 2 +- .../triggers/trigger-schedule/CHANGELOG.md | 93 +++ .../triggers/trigger-schedule/package.json | 2 +- packages/types/CHANGELOG.md | 132 +++ packages/types/package.json | 2 +- packages/verify/CHANGELOG.md | 88 ++ packages/verify/package.json | 2 +- packages/vscode-objectstack/CHANGELOG.md | 2 + packages/vscode-objectstack/package.json | 2 +- 259 files changed, 7553 insertions(+), 2599 deletions(-) delete mode 100644 .changeset/action-body-org-identifier.md delete mode 100644 .changeset/action-param-widget-config.md delete mode 100644 .changeset/action-result-dialog-i18n.md delete mode 100644 .changeset/adr-0097-openapi-upstream-classify.md delete mode 100644 .changeset/adr-0099-p0-equivalence-gate.md delete mode 100644 .changeset/adr-0099-p1-layer0-reads-rung.md delete mode 100644 .changeset/adr-0099-p2prime-two-axis-cells.md delete mode 100644 .changeset/adr-0103-managedby-write-policy.md delete mode 100644 .changeset/analytics-drill-raw-totals.md delete mode 100644 .changeset/annotate-schema-only-enums.md delete mode 100644 .changeset/approvals-decision-attachments.md delete mode 100644 .changeset/approvals-viewer-can-act.md delete mode 100644 .changeset/approver-type-org-membership-level.md delete mode 100644 .changeset/automation-schema-resolver-e2e.md delete mode 100644 .changeset/betterauth-adapter-system-writes.md delete mode 100644 .changeset/bulk-update-validation-rules.md delete mode 100644 .changeset/bulk-write-hardening.md delete mode 100644 .changeset/cli-memory-driver-dispatch.md delete mode 100644 .changeset/cli-strip-arg-separator.md delete mode 100644 .changeset/cli-turso-loud-error.md delete mode 100644 .changeset/client-batch-transaction-sdk.md delete mode 100644 .changeset/collapse-hook-event-taxonomy.md delete mode 100644 .changeset/console-0318118e02fd.md delete mode 100644 .changeset/console-2e7d7f0f7ee7.md delete mode 100644 .changeset/console-3b2e4d98d904.md delete mode 100644 .changeset/console-69fa5d163a97.md delete mode 100644 .changeset/console-94d4876df090.md delete mode 100644 .changeset/console-9a5f016f7d5c.md delete mode 100644 .changeset/console-af1b0db96e44.md delete mode 100644 .changeset/console-e164196801bd.md delete mode 100644 .changeset/console-fd45313b4d00.md delete mode 100644 .changeset/create-user-result-dialog-phone.md delete mode 100644 .changeset/cross-object-batch-authz-hardening.md delete mode 100644 .changeset/dashboard-widget-strict-3251.md delete mode 100644 .changeset/date-equality-runtime-fix.md delete mode 100644 .changeset/date-field-equality-guardrail.md delete mode 100644 .changeset/dev-loop-dx-p2.md delete mode 100644 .changeset/discovery-transactional-batch-capability.md delete mode 100644 .changeset/document-validation-governance-crossfield.md delete mode 100644 .changeset/drill-range-datetime-tz.md delete mode 100644 .changeset/driver-sql-sqlite3-peer.md delete mode 100644 .changeset/drop-deprecated-types-tar.md delete mode 100644 .changeset/field-form-summary-subfields.md delete mode 100644 .changeset/fix-explain-ownership-values.md delete mode 100644 .changeset/flow-registration-schema-aware-validation.md delete mode 100644 .changeset/flow-trigger-observability.md delete mode 100644 .changeset/formula-null-guard-floor-date-arith-3306.md delete mode 100644 .changeset/hook-nested-write-timeout.md delete mode 100644 .changeset/i18n-inline-label-default-locale.md delete mode 100644 .changeset/import-users-auto-policy.md delete mode 100644 .changeset/keyvalue-node-config-schemas.md delete mode 100644 .changeset/lint-hide-platform-baseline.md delete mode 100644 .changeset/localize-collab-notifications-and-storage-objects.md delete mode 100644 .changeset/logger-esm-file-destination.md delete mode 100644 .changeset/logger-honors-no-color.md delete mode 100644 .changeset/loop-collection-xexpression.md delete mode 100644 .changeset/managed-deny-registry-drift.md delete mode 100644 .changeset/managedby-engine-owned-bucket.md delete mode 100644 .changeset/map-node-config-schema.md delete mode 100644 .changeset/mcp-dev-connect-hint.md delete mode 100644 .changeset/mcp-stdio-principal-admission.md delete mode 100644 .changeset/mcp-stdio-switch-split-and-dev-connect-hint.md delete mode 100644 .changeset/mcp-validate-expression-tool.md delete mode 100644 .changeset/metadata-list-package-aware-dedup.md delete mode 100644 .changeset/metadata-register-notifies-watchers.md delete mode 100644 .changeset/objectql-checkboxes-option-gating.md delete mode 100644 .changeset/ownership-record-model-field.md delete mode 100644 .changeset/perf-timing-admin-detail.md delete mode 100644 .changeset/perf-timing-per-request-gating.md delete mode 100644 .changeset/pre.json delete mode 100644 .changeset/publish-drafts-org-scope.md delete mode 100644 .changeset/readonly-static-insert-strip.md delete mode 100644 .changeset/reconcile-system-append-only-api-methods.md delete mode 100644 .changeset/region-aware-history-compaction.md delete mode 100644 .changeset/remove-agent-visibility.md delete mode 100644 .changeset/remove-capability-aliases-3308.md delete mode 100644 .changeset/remove-dead-metadata-props-2377.md delete mode 100644 .changeset/remove-form-surfaced-dead-props-2377.md delete mode 100644 .changeset/remove-session-tenantid-alias.md delete mode 100644 .changeset/report-drill-down-range-filter.md delete mode 100644 .changeset/retire-feed-contracts.md delete mode 100644 .changeset/retire-feed-discovery-surface.md delete mode 100644 .changeset/retire-js-expression-dialect.md delete mode 100644 .changeset/sandbox-cpu-budget.md delete mode 100644 .changeset/sandbox-drop-asyncify.md delete mode 100644 .changeset/sandbox-pump-idle-backoff.md delete mode 100644 .changeset/sandbox-timeout-env-override.md delete mode 100644 .changeset/scaffold-default-connector-executors.md delete mode 100644 .changeset/scaffold-gitignore-survives-publish.md delete mode 100644 .changeset/scaffold-pnpm11-build-approvals.md delete mode 100644 .changeset/scaffold-skill-catalog-boundary.md delete mode 100644 .changeset/seed-replay-lookup-corruption.md delete mode 100644 .changeset/seed-replayer-skipped.md delete mode 100644 .changeset/server-timing-perf-spans-2408.md delete mode 100644 .changeset/service-automation-test-hardening.md delete mode 100644 .changeset/state-machine-initial-states.md delete mode 100644 .changeset/summary-rollup-filter.md delete mode 100644 .changeset/systemfields-drop-owner-key.md delete mode 100644 .changeset/tenant-scope-platform-global-3249.md delete mode 100644 .changeset/tidy-views-guard.md delete mode 100644 .changeset/tier4-type-soundness-warnings.md delete mode 100644 .changeset/time-relative-trigger.md delete mode 100644 .changeset/trim-validation-delete-event.md delete mode 100644 .changeset/trim-webhook-dead-triggers.md delete mode 100644 .changeset/unify-org-identifier-hook-session.md delete mode 100644 .changeset/view-metadata-type-schema-runtime-shapes.md delete mode 100644 .changeset/viewfilterrule-operator-enum-3373.md diff --git a/.changeset/action-body-org-identifier.md b/.changeset/action-body-org-identifier.md deleted file mode 100644 index 757e0861e5..0000000000 --- a/.changeset/action-body-org-identifier.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -"@objectstack/runtime": patch ---- - -**Extend the blessed `organizationId` org name to the action-body surface (follow-up to #3280).** Hooks now teach `ctx.user.organizationId` / `ctx.session.organizationId` as the blessed name for the caller's active org; action bodies — the sibling authoring surface that shares the same sandbox runner — were left behind: the REST dispatch path exposed only `ctx.user.tenantId` (the deprecated name) and no `ctx.session` at all, and the MCP `run_action` path exposed neither. - -Both action-dispatch sites (`handleActions`, MCP `runAction`) now populate: - -- **`ctx.user.organizationId`** — the blessed name (matches the `organization_id` column and `current_user.organizationId` in RLS); `ctx.user.tenantId` is kept as a deprecated alias with the identical value on the REST path. -- **`ctx.session`** (`{ userId, organizationId, tenantId, roles? }`) — mirrors the hook `ctx.session` shape, `undefined` for a context-less / self-invoked call. - -Action bodies execute trusted (the `ctx.engine` / `ctx.api` facade bypasses RLS/FLS), so a body that must scope by org has to read it from `ctx` — now under the same name a hook author uses. Additive and behavior-preserving; the objectstack-ui skill documents the action-body `ctx` and the `organizationId` read. diff --git a/.changeset/action-param-widget-config.md b/.changeset/action-param-widget-config.md deleted file mode 100644 index d19bee7b68..0000000000 --- a/.changeset/action-param-widget-config.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -"@objectstack/spec": minor ---- - -feat(spec): `ActionParamSchema` gains optional widget config — `multiple`, `accept`, `maxSize` - -The console now renders action params through the same field-widget renderer -the record form uses (objectui#2700, objectui ADR-0059), so inline params can -declare the widget config the form widgets consume: `multiple` (array value -shape, mirrors `FieldSchema.multiple`), and the upload constraints `accept` -(MIME types / extensions) and `maxSize` (bytes) for `file`/`image` params. -Field-backed params (`{ field }`) keep inheriting these from the referenced -field at runtime; inline values override. Purely additive — no existing -schema changes shape. diff --git a/.changeset/action-result-dialog-i18n.md b/.changeset/action-result-dialog-i18n.md deleted file mode 100644 index 2aef382865..0000000000 --- a/.changeset/action-result-dialog-i18n.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -"@objectstack/spec": minor -"@objectstack/cli": patch -"@objectstack/platform-objects": patch ---- - -feat(i18n): translation slot for action `resultDialog` copy — the one-shot secret-reveal dialogs are now localizable - -The post-success `resultDialog` (temporary passwords, 2FA backup codes, OAuth -client secrets) had no slot in the translation protocol, so its title / -description / acknowledge button / field labels always rendered the hardcoded -English metadata literals even on fully-translated locales. - -- **spec.** `_actions.` (object + object-first node) and - `globalActions.` gain an optional `resultDialog` translation node - (`ActionResultDialogTranslationSchema`): `title`, `description`, - `acknowledge`, and `fields` keyed by the **literal** result-field path - (e.g. `"user.email"` — keys may contain dots; resolvers index the record - directly, never split on `.`). New `resolveActionResultDialog` overlay - resolver, wired into `translateAction` for API-boundary translation. -- **cli.** `os i18n extract` emits the new `resultDialog.*` keys (title / - description / acknowledge / `fields.` for labelled fields), so - coverage and skeleton generation see them. -- **platform-objects.** en / zh-CN / ja-JP / es-ES bundles ship the - resultDialog copy for all six shipped dialogs: `sys_user.create_user`, - `sys_user.set_user_password`, `sys_two_factor.enable_two_factor`, - `sys_two_factor.regenerate_backup_codes`, - `sys_oauth_application.create_oauth_application`, and - `sys_oauth_application.rotate_client_secret`. - -Client-side rendering lands in objectui (`actionResultDialog` resolver in -`@object-ui/i18n` + result-dialog handlers). Purely additive — untranslated -locales keep falling back to the metadata literals. diff --git a/.changeset/adr-0097-openapi-upstream-classify.md b/.changeset/adr-0097-openapi-upstream-classify.md deleted file mode 100644 index 0c6cbb0d59..0000000000 --- a/.changeset/adr-0097-openapi-upstream-classify.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -'@objectstack/connector-openapi': minor ---- - -feat(connector-openapi): degrade + retry on an unreachable remote spec URL (#3049 follow-up) - -The `openapi` provider fetches `providerConfig.spec` when it is an http(s) URL. -That fetch previously threw plain on any failure, so a momentarily-unreachable -spec endpoint aborted the whole app boot. It now classifies the fault the same -way `connector-mcp` classifies its connect path (ADR-0097): - -- **Network error** (DNS / connection refused / timeout) or a **transient HTTP - status** (`408` / `429` / `5xx`, mirroring the `retryableStatusCodes` - convention) throws `ConnectorUpstreamUnavailableError` — the materializer - degrades the instance (`state: 'degraded'` on `GET /connectors`, dispatch - fails clearly) and retries with backoff plus on every `metadata:reloaded`. -- A **wrong URL** (non-retryable `4xx`) or an **unparseable document** stays a - plain, fatal configuration fault. - -Inline and file-path (`#3016`) specs do no boot I/O and are unaffected. diff --git a/.changeset/adr-0099-p0-equivalence-gate.md b/.changeset/adr-0099-p0-equivalence-gate.md deleted file mode 100644 index 8240d1676c..0000000000 --- a/.changeset/adr-0099-p0-equivalence-gate.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@objectstack/plugin-security': patch ---- - -ADR-0099 P0: land the probe-vs-carried-rung equivalence gate in the authz matrix (`authz-matrix-gate.test.ts`) — seeded-shape equivalence cells, two adversarial `KNOWN DIVERGENCE` pins (scoped `admin_full_access` grant; piecemeal platform-exclusive capability), the I2 nesting and I3 narrowing invariant cells, posture-blindness staging pins for the P1 flip, and the EXTERNAL dead-branch cell. Extracts the platform-admin capability probe as the exported pure `hasPlatformAdminCapability` (mechanical, behavior unchanged). Test-only gate; the ADR-0099 P1 flip lands behind it (#3211). diff --git a/.changeset/adr-0099-p1-layer0-reads-rung.md b/.changeset/adr-0099-p1-layer0-reads-rung.md deleted file mode 100644 index 034044e273..0000000000 --- a/.changeset/adr-0099-p1-layer0-reads-rung.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -'@objectstack/plugin-security': patch ---- - -ADR-0099 P1 (#3211 M2): the Layer 0 cross-tenant exemption gate now reads the carried `ctx.posture` rung (#2956) as authoritative, with the platform-admin capability probe demoted to a fallback for resolver-less contexts (delegated-admin bridge, sharing service, `getReadFilter`). The read and write (insert/update post-image) tenant checks share one decision (`computeLayeredRlsFilter`), so they cannot drift. A probe↔rung disagreement logs a defect breadcrumb and enforces the narrower rung verdict. - -**Behavior change (security narrowing, multi-org / `@objectstack/organizations` only):** a principal whose carried rung is not `PLATFORM_ADMIN` no longer crosses the tenant wall on private / platform-global / better-auth-managed objects, even when its resolved permission sets carry a platform-exclusive capability. Two shapes are affected: (a) a **scoped** `admin_full_access` grant (`sys_user_permission_set.organization_id` non-null), and (b) a custom set granting a platform capability (e.g. `studio.access`) piecemeal alongside a superuser bit. Both are now walled to their own org — the fail-safe direction (the carried rung is a strict subset of the probe). Single-org / env-per-database deployments are unaffected (Layer 0 is inert). - -**Upgrade check:** before upgrading, scan `sys_user_permission_set` for `admin_full_access` rows with a non-null `organization_id`, and custom permission sets whose `systemPermissions` intersect `{manage_metadata, manage_platform_settings, studio.access, manage_users}`. To restore cross-tenant operator access for such a principal, grant the **unscoped** `admin_full_access` instead. The `[authz/ADR-0099]` warn log names any principal hitting the divergence at runtime. diff --git a/.changeset/adr-0099-p2prime-two-axis-cells.md b/.changeset/adr-0099-p2prime-two-axis-cells.md deleted file mode 100644 index 2e176bd822..0000000000 --- a/.changeset/adr-0099-p2prime-two-axis-cells.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@objectstack/plugin-security': patch ---- - -ADR-0099 P2′ (#3211 M3′): pin the two-axis Amendment in the authz matrix. The original P2 (collapse the Layer 1 tier onto posture) was rejected — Layer 1's tier input is the per-object super-bit, a per-principal × per-object delegation primitive posture cannot represent. New cells pin: seeded-face agreement (seeded super-bit holders are already ≥ TENANT_ADMIN), the load-bearing delegation cell (a MEMBER with a delegated per-object `viewAllRecords`/`modifyAllRecords` short-circuits Layer 1 yet stays walled by Layer 0 — the auditor pattern), invariant I7 (the scope axis never crosses a boundary posture has not opened), and the contrast that the bit is a real grantable capability, not conditionally inert. Test-only; zero behavior change. diff --git a/.changeset/adr-0103-managedby-write-policy.md b/.changeset/adr-0103-managedby-write-policy.md deleted file mode 100644 index 2db0770de5..0000000000 --- a/.changeset/adr-0103-managedby-write-policy.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -"@objectstack/plugin-security": minor -"@objectstack/objectql": minor -"@objectstack/plugin-hono-server": minor -"@objectstack/spec": patch -"@objectstack/platform-objects": patch -"@objectstack/plugin-approvals": patch -"@objectstack/service-automation": patch -"@objectstack/service-messaging": patch -"@objectstack/plugin-sharing": patch -"@objectstack/rest": patch ---- - -**Split the overloaded `managedBy: 'system'` bucket into engine-owned vs. admin-writable, and enforce engine-owned writes (ADR-0103, #3220).** The `system` bucket conflated two incompatible write policies: rows a platform service owns end to end (never user-written), and platform-defined schema whose rows are legitimately admin/user-writable. It carried the same all-false affordance row as `better-auth`/`append-only` but, unlike `better-auth`, had no engine enforcement — a wildcard admin could raw-write these rows through the generic data API (ADR-0049 gap). - -Rather than add a new `managedBy` enum value (which would fall through to fully-editable `platform` defaults on already-deployed Console clients), the write policy is now the **resolved affordance** (`resolveCrudAffordances` = bucket default + `userActions`), and *engine-owned* is defined as a `system`/`append-only` object that grants no write: - -- **Writable set declares `userActions`** — the RBAC link tables (`sys_user_position`, `sys_user_permission_set`, `sys_position_permission_set`), `sys_user_preference`, `sys_approval_delegation`, and the messaging config grids (`sys_notification_preference` / `…_subscription` / `…_template`) now declare `userActions: { create, edit, delete: true }`. The affordance is a declaration only — the `DelegatedAdminGate` / RLS / permission sets remain the authz. -- **Engine-owned objects locked to reads** — `apiMethods: ['get','list']` added where absent (jobs, notifications, approval request/approver/token/action, `sys_record_share`, `sys_automation_run`, mail/settings/secret audit, the messaging delivery pipeline). `sys_secret` is explicitly read-locked (an empty `apiMethods` array fails open). -- **`sys_import_job`** stays engine-owned: the REST import route now writes its job rows `isSystem`-elevated (attribution preserved via the explicit `created_by` stamp) and the object is locked to `['get','list']`. -- **New engine write guard** (`assertEngineOwnedWriteAllowed`, plugin-security) fail-closed rejects user-context generic writes to engine-owned `system`/`append-only` objects, keyed off the resolved affordance; `isSystem` and context-less engine/service writes bypass by construction. Wired into the security middleware alongside the other data-layer gates. -- **`reconcileManagedApiMethods`** (objectql registry) now runs for **every** managed bucket, not just `better-auth`: any advertised write verb an object's resolved affordances forbid is stripped at registration with a warning (the drift backstop, ADR-0049). -- **`/me/permissions` clamp** (plugin-hono-server) now clamps `system`/`append-only` as well as `better-auth`, so the client hint reflects `permission ∩ guard`. - -**Potentially breaking:** a downstream/third-party `system` object that advertised generic write verbs relying on today's fail-open behaviour will have those verbs stripped (with a warning) and user-context generic writes to it rejected. Declare `userActions` opening the verbs the object legitimately takes from a user context. `better-auth` keeps plugin-auth's identity write guard unchanged; the row-level `managed_by` provenance vocabulary (ADR-0066) is a different axis and is untouched. diff --git a/.changeset/analytics-drill-raw-totals.md b/.changeset/analytics-drill-raw-totals.md deleted file mode 100644 index e05383d44d..0000000000 --- a/.changeset/analytics-drill-raw-totals.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"@objectstack/service-analytics": minor ---- - -Analytics drill metadata now snapshots raw grouped values for totals/subtotal rows too (#3214). The ADR-0021 D2 drill sidecar (`drillRawRows`, #2080) only covered `result.rows`, but the totals rows added in #1753 carry dimension values and go through the same label resolution — which overwrote their stored value (select option value, lookup/master_detail FK id) with the display label, leaving a subtotal drill nothing to exact-match on. - -`queryDataset` now also emits `drillRawTotals`, aligned to `result.totals` by index (`drillRawTotals[i][j]` ↔ `result.totals[i].rows[j]`), captured in the same pre-label-resolution pass. Each map is restricted to the drillable dimensions the grouping actually groups by, so the grand-total grouping (`[]`) contributes an empty map per row. Purely additive result props (same as #2080) — no spec-contract change. diff --git a/.changeset/annotate-schema-only-enums.md b/.changeset/annotate-schema-only-enums.md deleted file mode 100644 index ade2f65969..0000000000 --- a/.changeset/annotate-schema-only-enums.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -"@objectstack/spec": patch ---- - -Annotate the schema-only event/subscription/connector surfaces flagged by the #3197 audit with explicit "not yet enforced / not yet implemented" notes in their doc comments and `.describe()` texts, so authoring metadata against them is no longer silently swallowed. No runtime behavior or schema shape changes — documentation only. - -Surfaces annotated (each trace re-confirmed against the current tree before annotating): - -- `GraphQLSubscriptionConfigSchema` (`api/graphql.zod.ts`) — no subscription transport exists; the GraphQL HTTP entry serves query/mutation only. -- `WebSocketMessageType` + module header (`api/websocket.zod.ts`) — no WebSocket server is mounted (#2462); the protocol is a future wire contract. -- `RealtimeEventType` (`api/realtime.zod.ts`) — zero runtime importers; the engine emits `data.record.*` names (which don't match this enum's members) and nothing emits `field.changed`. -- Connector `webhooks`/`WebhookConfigSchema`/`WebhookEventSchema` and `triggers`/`ConnectorTriggerSchema` (`integration/connector.zod.ts`) — `AutomationEngine.registerConnector` reads only `actions`; webhook events and trigger definitions parse but are never dispatched or polled. -- Automation `ConnectorTriggerSchema`/`TriggerRegistrySchema` (`automation/trigger-registry.zod.ts`) — no runtime importer; the `stream` trigger mechanism exists only here. -- `NotificationChannelSchema` (`system/notification.zod.ts`) + the mirrored `NotificationChannel` contract type — implemented delivery channels are `inbox`/`email`/`sms`; `push`/`slack`/`teams`/`webhook` dead-letter, and the enum's `in-app` does not match the registered `inbox` channel id. - -The audit's sixth row (`SubscriptionEventType`, formerly `data/subscription.zod.ts`) needed no annotation — it was already removed outright by the feed-contract retirement (#1959). diff --git a/.changeset/approvals-decision-attachments.md b/.changeset/approvals-decision-attachments.md deleted file mode 100644 index 39ee142bc6..0000000000 --- a/.changeset/approvals-decision-attachments.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -"@objectstack/plugin-approvals": minor ---- - -feat(approvals): declare file attachments on approve/reject decisions - -The declared `approval_approve` / `approval_reject` actions on -`sys_approval_request` gain an optional multi-file `attachments` param -(`type: 'file'`, `multiple`). The console renders `type:'file'` action params -through the shared upload widget (objectui ADR-0059) and POSTs the resolved -`attachments: string[]`, so a reviewer can attach supporting files to a -decision through the generic declared-action dialog — letting the approvals -inbox retire its hand-wired attachment composer (objectui#2698). - -Purely additive metadata: the decision route already forwards -`body.attachments` to `ApprovalService.decide`, and the -`sys_approval_action.attachments` column (file, multiple) already persists them -(#3266/#3274). No service or route change. diff --git a/.changeset/approvals-viewer-can-act.md b/.changeset/approvals-viewer-can-act.md deleted file mode 100644 index b496d67485..0000000000 --- a/.changeset/approvals-viewer-can-act.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -"@objectstack/spec": minor -"@objectstack/plugin-approvals": minor ---- - -feat(approvals): server-computed `viewer` capability for precise decision-action gating - -`getRequest` / `listRequests` now attach a per-viewer block — -`viewer: { can_act, is_submitter }` — computed from the caller's context -(`ApprovalRequestRow.viewer`): - -- `can_act` — the caller is a *current pending approver* (their user id is in the - request's resolved `pending_approvers` while it is still `pending`). This is - the same check the decision methods authorize with, so it already reflects - position/team/manager resolution — strictly more accurate than a client-side - identity guess. -- `is_submitter` — the caller submitted the request. - -The declared decision actions on `sys_approval_request` now gate on it: approver -actions (approve/reject/reassign/send-back/request-info) use -`record.viewer.can_act`; submitter levers (remind/recall/resubmit) use -`record.viewer.is_submitter`. Previously approver actions only trimmed the -non-pending case, so a submitter viewing their own pending request saw buttons -they couldn't use (the backend 403'd); a position-addressed approver could be -wrongly hidden by the old client heuristic. Where `viewer` is absent (a row -surfaced outside a service read with a user context), the predicate fails closed. diff --git a/.changeset/approver-type-org-membership-level.md b/.changeset/approver-type-org-membership-level.md deleted file mode 100644 index d4e1899b0f..0000000000 --- a/.changeset/approver-type-org-membership-level.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -"@objectstack/spec": minor -"@objectstack/plugin-approvals": minor -"@objectstack/lint": minor ---- - -feat(approvals): rename the `role` approver type to `org_membership_level` (#3133) - -`ApproverType.role` was the last platform surface projecting the reserved word -"role" (ADR-0090 D3). It is not covered by D3's better-auth exception: that -exception protects better-auth's own `sys_member.role` **column**, which we do -not own — `ApproverType` is our own enum, an authoring surface, and D3 mandates -that the projection of that concept is spelled `org_membership_level` and -labelled "organization membership", **never "role"**. - -The sentence licensing the leak was also false: ADR-0090 D3 claims -`sys_member.role` is "already relabelled `org_membership_level` in the platform -projection", but `org_membership_level` existed nowhere in the codebase and -ADR-0057 D7 lists that relabel under "Deferred (evidence-gated, P4)". The -projection never landed, so the word reached authors. - -The name manufactured a real, silent failure — "hotcrm class": every other -surface renamed to `position` (`sys_role`, `ShareRecipientType.role`, -`ctx.roles[]`), so `{ type: 'role', value: 'sales_manager' }` reads as the -legacy spelling of a position. It resolves against the membership tier, finds -no member row, falls back to an inert `role:sales_manager` literal, and the -request waits forever on an approver that cannot exist. - -- **spec**: `ApproverType` gains `org_membership_level`; `role` stays as a - deprecated alias for one window (a published 15.x flow keeps loading) with - `DEPRECATED_APPROVER_TYPES` + `canonicalApproverType()` as the single source - for the mapping. Removed in the next major. -- **plugin-approvals**: resolves on the canonical type and warns on the - deprecated spelling. The `type:value` fallback literal keeps the **authored** - spelling — stored `sys_approval_approver` rows and `pending_approvers` slots - from 15.x carry `role:`, and rewriting it would orphan them. -- **lint**: `approval-role-not-membership-tier` → `approval-approver-not-membership-tier` - (the rule id carried the reserved word too), plus a new - `approval-approver-type-deprecated`. The two are mutually exclusive: a bad - *value* wins, because prescribing `org_membership_level` for a position name - would be wrong advice — the fix there is `position`. - -Authoring `type: 'role'` keeps working and now says so out loud. Rewrite it as -`org_membership_level`; if the value is an org position, the fix is `position`. diff --git a/.changeset/automation-schema-resolver-e2e.md b/.changeset/automation-schema-resolver-e2e.md deleted file mode 100644 index 3184276045..0000000000 --- a/.changeset/automation-schema-resolver-e2e.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -"@objectstack/service-automation": patch ---- - -test(automation): end-to-end coverage for the #1928 object-schema resolver wiring - -Adds a kernel-level integration test proving `AutomationServicePlugin` bridges -the engine's object-schema resolver to the live `objectql.registry.getObject` at -`start()` (fields + types resolved from the registry), and that a flow -registered through the running kernel with a text field misused in arithmetic -emits the tier-4 advisory — while a sound condition stays quiet. Locks in the -production integration point that the engine-level unit tests (which set the -resolver by hand) could not exercise. Test-only; no behavior change. diff --git a/.changeset/betterauth-adapter-system-writes.md b/.changeset/betterauth-adapter-system-writes.md deleted file mode 100644 index d9371813ca..0000000000 --- a/.changeset/betterauth-adapter-system-writes.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -"@objectstack/plugin-auth": patch ---- - -fix(plugin-auth): run better-auth adapter WRITES as system context so #2948 doesn't strip readonly identity columns (#3164) - -The better-auth ObjectQL adapter wrapped the engine so its READS carried -`isSystem` (to bypass the control-plane org-scope read hook), but its WRITES -passed through with no context. The static-`readonly` UPDATE strip (#2948) runs -on any non-system update — and since the adapter carries no caller context, -`!ctx?.isSystem` was `true`, so the strip silently DROPPED better-auth's own -writes to readonly `sys_user` columns: `email` (change-email), `banned` / -`ban_reason` / `ban_expires` (admin ban). Those operations returned success but -never persisted. - -`withSystemReadContext` is renamed to `withSystemContext` (a deprecated alias is -kept for one release) and now injects `isSystem` on `insert` / `update` / -`delete` as well as reads. This is correct because these are the identity -authority's own writes — user-context writes to `managedBy: 'better-auth'` tables -are already rejected upstream by the ADR-0092 identity write guard, so the -adapter path only ever carries better-auth's internal writes. - -Found while implementing #3043 (the INSERT-side readonly strip). This is its -UPDATE-side dual: #3043 relocated the insert strip to the external ingress -precisely because internal writers (this adapter included) don't declare -`isSystem`; the pre-existing engine-level UPDATE strip has no such relocation, so -the adapter had to declare its writes system. diff --git a/.changeset/bulk-update-validation-rules.md b/.changeset/bulk-update-validation-rules.md deleted file mode 100644 index a15675d285..0000000000 --- a/.changeset/bulk-update-validation-rules.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@objectstack/objectql": patch -"@objectstack/spec": patch ---- - -Enforce validation rules, `requiredWhen`, and per-option `visibleWhen` on multi-row updates (#3106). The bulk branch of `engine.update` (`options.multi` → `driver.updateMany`) previously never called `evaluateValidationRules`, so every object-level rule (`script`, `state_machine`, `format`, `cross_field`, `json_schema`, `conditional`), field-level `requiredWhen`, and per-option `visibleWhen` check was a silent no-op there. The engine now reads the row-scoped match set (the same AST the write binds, one query shared with the `readonlyWhen` bulk strip) and evaluates the payload against each matched row's prior state; any error-severity violation rejects the whole batch with `ValidationError` (annotated with the failing record id) before anything is written. Schemas needing no prior state (`format`/`json_schema`-only) are evaluated once against the payload with no fetch, and rule-free schemas are unaffected. Behavior change: bulk writes that previously slipped past declared rules now throw. Doc comments in `rule-validator.ts` and `validation.zod.ts` no longer overstate coverage and name the remaining `events: ['delete']` gap (tracked separately). diff --git a/.changeset/bulk-write-hardening.md b/.changeset/bulk-write-hardening.md deleted file mode 100644 index e32a7e46ba..0000000000 --- a/.changeset/bulk-write-hardening.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -"@objectstack/core": patch -"@objectstack/objectql": patch -"@objectstack/rest": patch -"@objectstack/metadata-protocol": patch ---- - -fix: harden the bulk-write path — retries, idempotency, contracts, and summary visibility (#3147–#3152) - -Six reliability fixes to the batched seed/import + `engine.insert(array)` path -introduced by the #2678 bulk-write rework: - -- **#3151** `bulkWrite` validates that `writeBatch` returns one record per input - row (a short/long/non-array return is degraded per-row, not backfilled as - phantom success); `engine.insert(array)` likewise rejects a short driver - `bulkCreate` return instead of padding afterInsert with `undefined`. -- **#3150** wraps the two remaining un-retried write points (seed - `writeRecord`/`resolveDeferredUpdates`, import's no-`createManyData` - fallback) in `withTransientRetry`; `defaultIsTransientError` short-circuits - definitive logical errors to non-transient. -- **#3148** import `resolveRef` flushes pending creates on a same-object miss so - a later row can reference an earlier same-file CREATE, and no longer - negatively caches a miss. -- **#3149** threads an `attempt` counter through `bulkWrite`; seed rechecks by - `externalId` and import by `matchFields` before re-writing, so a - commit-then-lost-response retry cannot duplicate a batch. -- **#3147** `recomputeSummaries` retries transient failures and, on exhaustion, - surfaces `SummaryRecomputeError` (`ERR_SUMMARY_RECOMPUTE`) instead of a - silent warn; seed/import recover it to a warning without re-writing. -- **#3152** autonumbers are assigned after validation, so a batch that dies in - validation consumes no sequence value (no number-range gaps). diff --git a/.changeset/cli-memory-driver-dispatch.md b/.changeset/cli-memory-driver-dispatch.md deleted file mode 100644 index 872a06dcfa..0000000000 --- a/.changeset/cli-memory-driver-dispatch.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -'@objectstack/cli': patch ---- - -fix(cli): honor `OS_DATABASE_DRIVER=memory` (mingo InMemoryDriver) (#3276) - -`os dev` / `os start` / `os serve` advertised a `memory` database driver -(`--database-driver memory`, `OS_DATABASE_DRIVER=memory`, and a `memory://` -URL scheme), but `serve.ts`'s driver dispatch had no `memory` branch — so it -silently fell through to the dev SQLite `:memory:` default (SQLite-in-memory, -a *different* engine) or, in production, registered no driver at all. - -The driver kind-resolution + construction is now extracted into -`utils/storage-driver.ts` (unit-testable in isolation) with the missing -`memory` branch: selecting it yields the mingo `InMemoryDriver` in dev AND -production. The `memory://` / `mingo://` URL scheme is now recognized too, -kept distinct from sqlite's `:memory:` pseudo-file. Telemetry-datasource -provisioning behavior is unchanged. diff --git a/.changeset/cli-strip-arg-separator.md b/.changeset/cli-strip-arg-separator.md deleted file mode 100644 index b7e8589055..0000000000 --- a/.changeset/cli-strip-arg-separator.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -"@objectstack/cli": patch ---- - -fix(cli): tolerate the `--` separator pnpm injects when forwarding script args (#3114) - -The AGENTS.md-documented backend-debug flow `pnpm dev -- --fresh -p ` failed at -the repo root with an opaque `Unexpected arguments: -p, 44637` (exit 2 + a help dump). - -pnpm appends forwarded args to a script **verbatim, including the `--`**, and each -nested `pnpm --filter` hop preserves it, so the showcase's `objectstack dev ---seed-admin` ran as `objectstack dev --seed-admin -- --fresh -p 44637`. oclif reads -`--` as POSIX end-of-flags, so everything after it became positional: `--fresh` was -silently swallowed as the `package` arg and `-p 44637` overflowed the arg list. Every -flag the user asked for was dropped — the failure was opaque precisely because the -`--` looks inert. - -A `preparse` hook now drops `--` separators before oclif parses argv, so the -npm-style `-- ` form and the bare form behave identically, for every command -and both bins (`run.js`, `run-dev.js`). No `os` command takes passthrough args (none -sets `strict = false`, none reads raw argv), so a `--` carries no meaning here and is -always a package-manager artifact. - -Note this is not fixable via oclif's `'--': false` parser option: that keeps -flag-parsing on past the separator but re-appends the `--` into argv, so strict -commands fail with `Unexpected argument: --` instead. - -Tradeoff: a `-`-prefixed token can no longer be forced to parse as a positional -value. Every `os` positional is a config path, a metadata / datasource / package -name, or an id — none start with `-`. diff --git a/.changeset/cli-turso-loud-error.md b/.changeset/cli-turso-loud-error.md deleted file mode 100644 index 7cc6d0bcdd..0000000000 --- a/.changeset/cli-turso-loud-error.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -'@objectstack/cli': patch ---- - -fix(cli): fail loudly when `turso`/libSQL is selected in the open-core CLI (#3276 follow-up) - -Same "declared ≠ enforced" class as the `memory` fix: the CLI advertised `turso` -(`--database-driver turso`, `OS_DATABASE_DRIVER=turso`, `libsql://` URLs) but the -driver dispatch had no `turso` branch, so it silently fell through to the SQLite -default and ignored the requested engine. - -`turso`/libSQL ships in the cloud / enterprise distribution -(`@objectstack/driver-turso`, composed by the cloud runtime's own kernel factory — -open-core's standalone stack deliberately does not consume it). Rather than pull an -EE driver into open-core, `createStorageDriver` now throws a typed -`UnsupportedDriverError` for `turso`/`libsql`, and `serve.ts` surfaces it as a -fatal, actionable boot error (naming the cloud/EE package and the open-core -alternatives) instead of silently degrading to SQLite. `libsql://` / `*.turso.*` -URLs stay classified as `turso` so they hit the same loud failure. diff --git a/.changeset/client-batch-transaction-sdk.md b/.changeset/client-batch-transaction-sdk.md deleted file mode 100644 index f505086309..0000000000 --- a/.changeset/client-batch-transaction-sdk.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -'@objectstack/client': minor ---- - -feat(client): typed `data.batchTransaction()` for the atomic cross-object batch (#1604 / ADR-0034 item 4) - -Adds `client.data.batchTransaction(operations)` (and the environment-scoped -`client.project(id).data.batchTransaction`) — a typed SDK surface for -`POST {basePath}/batch`, the all-or-nothing cross-object transactional batch -that master-detail saves go through. Reuses `CrossObjectBatchOperation` / -`CrossObjectBatchRequest` / `CrossObjectBatchResponse` from -`@objectstack/spec/api` (also re-exported from the client for convenience); -supports `{ $ref: }` intra-batch parent references. - -The method is always atomic and deliberately exposes no `atomic` flag — the -endpoint rejects `atomic: false` with `400 BATCH_NOT_ATOMIC`. Non-atomic -per-object bulk writes stay on `data.batch()` / `createMany` / `updateMany`, -so any best-effort fallback is isolated in the caller's adapter (the ObjectUI -`masterDetailTx` adapter), not in the SDK. diff --git a/.changeset/collapse-hook-event-taxonomy.md b/.changeset/collapse-hook-event-taxonomy.md deleted file mode 100644 index bfce023b9a..0000000000 --- a/.changeset/collapse-hook-event-taxonomy.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -"@objectstack/spec": patch -"@objectstack/objectql": patch ---- - -Collapse the hook event taxonomy from 18 declared events to the 8 the engine actually dispatches (#3195). The removed 10 (`beforeFindOne`/`afterFindOne`, `beforeCount`/`afterCount`, `beforeAggregate`/`afterAggregate`, `beforeUpdateMany`/`afterUpdateMany`, `beforeDeleteMany`/`afterDeleteMany`) were declared in `HookEvent` but never fired — the enum mirrored the engine method table instead of domain events, so a hook subscribing to them registered fine and then silently no-op'd. - -- `findOne` now fires the same `beforeFind`/`afterFind` hooks as `find` — the read event attaches to record materialization, not the engine method, so one subscription covers every read shape (no separate `beforeFindOne`/`afterFindOne`). -- Bulk (`multi: true`) updates/deletes already fire the singular `beforeUpdate`/`beforeDelete`/`afterUpdate`/`afterDelete` events with the row-scoping predicate in `ctx.input.ast`; this is now documented, and there is no `*Many` event. -- Read authorization / row filtering is the RLS/permission-rule layer's job and field masking is field-level metadata — neither is a hook every author must re-attach. -- `engine.registerHook` now warns when a hook subscribes to an event the engine never dispatches, so enum-vs-dispatch drift can't recur silently. - -No shipped hook or authored metadata used any of the removed events; authoring one now fails loudly at parse/validate time instead of registering a dead hook. Skills and docs updated to teach the 8 events and the declarative alternatives. diff --git a/.changeset/console-0318118e02fd.md b/.changeset/console-0318118e02fd.md deleted file mode 100644 index 8b827e55e6..0000000000 --- a/.changeset/console-0318118e02fd.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -"@objectstack/console": patch ---- - -Console (objectui) refreshed to `0318118e02fd`. Frontend changes in this range: - -- fix(app-shell): guard ActionParamDialog submit during file upload + map spec `autonumber` (ADR-0059 follow-ups) (#2707) - -objectui range: `94d4876df090...0318118e02fd` diff --git a/.changeset/console-2e7d7f0f7ee7.md b/.changeset/console-2e7d7f0f7ee7.md deleted file mode 100644 index 288cedd6ba..0000000000 --- a/.changeset/console-2e7d7f0f7ee7.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -"@objectstack/console": patch ---- - -Console (objectui) refreshed to `2e7d7f0f7ee7`. Frontend changes in this range: - -- feat(evaluator): route CEL-dialect component/action predicates to the canonical engine (#2664) -- fix(grid): explain the import wizard's disabled Next and silent downgrade (#2640, #2639) (#2646) -- fix(form+detail): single-file children stay inline grids; drop non-spec `attachment` (#2654, #2655) (#2656) -- feat(access): localize curated capability labels client-side (#2600 B5 follow-up) (#2657) -- feat(access): localize capability picker group headers (#2600 B5, objectui side) (#2653) -- fix(access): Studio permission matrix — stop clipping the Bulk column at narrow widths (#2600 B3) (#2652) -- feat(access): Studio permission matrix — field-level bulk + filter for wide objects (#2600 B4) (#2651) -- feat(access): Studio Explain panel — package-scoped object dropdown instead of free-text api-name (#2600 B2) (#2650) -- feat(access): Studio permission matrix — collapse identity + zero-grant capabilities so the matrix hits the first screen (#2600 B1) (#2649) -- feat(plugin-list): 列表工具栏增加手动刷新按钮 (#2634) (#2645) -- fix(studio): approver Type dropdown drops deprecated `role`, membership-tier picker (#2643) -- fix(components): route internal html-page links through the SPA navigation handler (#2642) -- feat(discovery): trust only handlerReady/available services (ADR-0076 D12) (#2637) -- feat(types)!: adopt @objectstack/spec 15.1.1; drop value-erased spec/ui `…Schema` re-exports (#2589) -- feat(console): dev-seeded admin credentials hint on the login page (#2635) -- fix(auth): 注册页去掉重复的「or」分隔线(与 #2629 登录页修复对齐) (#2633) -- feat(app-shell/react): adapt to framework 15.1 — atomic publish rendering + honest discovery (#2630) -- fix(chatbot): plan approval flips the card to a Building… badge immediately (#2632) -- fix(app-shell,components): welcome CTA deep-links into the environment create dialog (#2631) -- fix(auth): login-page config race + sign-in watchdog — never strand SSO-only users on a password wall (#2629) -- feat(types): derive ListViewSchema from @objectstack/spec/ui (#2231) (#2622) - -objectui range: `077e45b4bc55...2e7d7f0f7ee7` diff --git a/.changeset/console-3b2e4d98d904.md b/.changeset/console-3b2e4d98d904.md deleted file mode 100644 index eaaabf3961..0000000000 --- a/.changeset/console-3b2e4d98d904.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -"@objectstack/console": minor ---- - -Console (objectui) refreshed to `3b2e4d98d904`. Frontend changes in this range: - -- fix(list): route remaining system-field groupings through shared classifier (#2706) -- feat(console): user-import wizard defaults to the `auto` password policy (tracks framework#3236) (#2701) -- feat(flow-designer): schema-driven keyValue + numberList mapping (#3304) (#2708) - -objectui range: `0318118e02fd...3b2e4d98d904` diff --git a/.changeset/console-69fa5d163a97.md b/.changeset/console-69fa5d163a97.md deleted file mode 100644 index 704dfbe349..0000000000 --- a/.changeset/console-69fa5d163a97.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -"@objectstack/console": patch ---- - -Console (objectui) refreshed to `69fa5d163a97`. Frontend changes in this range: - -- fix(app-shell): mark notifications read via the REST surface, not direct receipt writes (#2743) - -objectui range: `af1b0db96e44...69fa5d163a97` diff --git a/.changeset/console-94d4876df090.md b/.changeset/console-94d4876df090.md deleted file mode 100644 index f64c90649c..0000000000 --- a/.changeset/console-94d4876df090.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -"@objectstack/console": minor ---- - -Console (objectui) refreshed to `94d4876df090`. Frontend changes in this range: - -- feat(dashboard): Studio authors the ADR-0021 dataset shape only (framework#3251) (#2703) -- feat(app-shell): render ActionParamDialog params through the shared form field-widget renderer (#2700, ADR-0059) (#2704) -- feat(app-shell): distinguish writable system objects from engine-owned in badge + empty-state (ADR-0103 / #3220) (#2705) -- fix(list): keep injected owner_id out of leading auto-derived list columns (#2702) -- feat(flow-designer): #2670 Phase 3 — nested container node selection + schema-driven editing (#2699) -- feat(approvals-inbox): retire hardcoded secondary buttons for server-declared actions (#2697) - -objectui range: `fd45313b4d00...94d4876df090` diff --git a/.changeset/console-9a5f016f7d5c.md b/.changeset/console-9a5f016f7d5c.md deleted file mode 100644 index 22c6c9dd66..0000000000 --- a/.changeset/console-9a5f016f7d5c.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -"@objectstack/console": patch ---- - -Console (objectui) refreshed to `9a5f016f7d5c`. Frontend changes in this range: - -- feat(flow-designer): nested-array columns in the node property form (#2678 P2-5) (#2761) -- fix: redo record-list "Add View" flow — empty-name 405, invisible drafts, canonical naming (#2768) -- feat(SchemaForm): field-type-aware operators + values for view filter (#2766) -- fix(plugin-charts): draw dashboard chart bars on first paint via isAnimationActive=false (#2756) (#2759) -- feat(data-objectstack): gate non-atomic batch fallback on discovery transactionalBatch capability (#2693) (#2755) - -objectui range: `69fa5d163a97...9a5f016f7d5c` diff --git a/.changeset/console-af1b0db96e44.md b/.changeset/console-af1b0db96e44.md deleted file mode 100644 index 602ca618b6..0000000000 --- a/.changeset/console-af1b0db96e44.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -"@objectstack/console": patch ---- - -Console (objectui) refreshed to `af1b0db96e44`. Frontend changes in this range: - -- feat(i18n): localize action result dialogs via _actions..resultDialog (#2736) -- feat(data): thread the host's authenticated fetch into provider:'api' data sources (#2725) (#2732) -- feat(managedBy): add explicit `engine-owned` lifecycle bucket (tracks framework ADR-0103 addendum, #3343) (#2739) -- feat(fields): CheckboxesField visibleWhen cascading + dependsOn gating (completes option-widget parity) (#2735) -- feat(fields): RadioField visibleWhen cascading + dependsOn gating; single-source the option resolver (#2728) -- fix(kanban,calendar): surface write failures instead of silently swallowing them (#2716) -- fix(plugin-charts): draw dashboard bars on first paint via one settle re-mount (#2727) -- feat(dashboard): retire pre-ADR-0021 inline-analytics renderer branches (framework#3320) (#2723) -- fix(data-objectstack): type the exportDownload test fetch mock so its type-check passes (#2726) -- feat(detail): related lists paginate by default with server-side $top/$skip windows (#2711) (#2722) -- fix(approvals-inbox): align participant gating with the server-computed viewer block (#2719) -- fix(plugin-view): coerce i18n tab-label helpers to string (TS2322) (#2721) -- feat(fields): MultiSelectField per-option visibleWhen cascading + dependsOn gating (#2715) (#2717) -- fix(site): make docs build resilient to remote badge fetch failures (#2695) (#2718) -- feat(approvals-inbox): retire the approve/reject composer for declared actions with file attachments (#2698) (#2710) -- feat(fields): select+multiple → multi-value chip picker; restore fields/core lint gates (#2709) - -objectui range: `3b2e4d98d904...af1b0db96e44` diff --git a/.changeset/console-e164196801bd.md b/.changeset/console-e164196801bd.md deleted file mode 100644 index 21e89bbbed..0000000000 --- a/.changeset/console-e164196801bd.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -"@objectstack/console": minor ---- - -Console (objectui) refreshed to `e164196801bd`. Frontend changes in this range: - -- fix(app-shell,plugin-detail): record History tab renders display values, not raw audit payloads (#2691) -- fix(plugin-gantt): mirror the row 「→」 slot in the task-list header (#2690) -- fix(plugin-detail): #2688 header Record-#id floor + raw audit user id in meta footer (#2689) -- feat(plugin-gantt)!: remove the mobile QR share (移动端二维码) context-menu feature (#2687) -- feat(plugin-gantt): dependencyTypes switch — hide the type switcher for id-only dependency stores (#2686) -- feat(approvals): decision attachments + progress display + deep link + designer sync (#2681) -- feat(studio): inline push-down expansion of loop/parallel/try_catch regions on the flow canvas (#2680) -- feat(plugin-gantt): ownership-aware reschedule + confirm-first auto-schedule, export fixes, business time zone (#2683) -- fix(app-shell): skip resultDialog fields whose path does not resolve (#2674) -- feat(studio): visualize loop/parallel/try_catch nested regions on the flow canvas (#2670) (#2675) -- feat(plugin-gantt): manual-scheduling summary bars, interaction switches, beforeTaskUpdate veto + tooltip/scrollbar/cursor fixes (#2677) -- fix(flow-designer): author the canonical config.schedule the runtime reads (#2671) -- feat(report): drill a date-bucket cell into its time range, not a superset (#1752) (#2672) -- feat(studio): filter editor for roll-up summary fields (framework#1868) (#2669) -- feat(flow-designer): first-class panel for the time-relative trigger (#1874) (#2668) -- feat(studio): nest per-iteration / per-region step logs in the flow Runs panel (#2667) -- fix(metadata-admin): dashboard label fallback + skill activation editors (#1878) (#2666) - -objectui range: `2e7d7f0f7ee7...e164196801bd` diff --git a/.changeset/console-fd45313b4d00.md b/.changeset/console-fd45313b4d00.md deleted file mode 100644 index 41b163863d..0000000000 --- a/.changeset/console-fd45313b4d00.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -"@objectstack/console": minor ---- - -Console (objectui) refreshed to `fd45313b4d00`. Frontend changes in this range: - -- feat(app-shell): DeclaredActionsBar — render server-declared object actions on bespoke pages (#2678 P2-4) (#2692) -- feat(data): unify master-detail saves behind DataSource.batchTransaction; isolate non-atomic fallback in the adapter (#2679) (#2684) - -objectui range: `e164196801bd...fd45313b4d00` diff --git a/.changeset/create-user-result-dialog-phone.md b/.changeset/create-user-result-dialog-phone.md deleted file mode 100644 index 35f305cd29..0000000000 --- a/.changeset/create-user-result-dialog-phone.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -'@objectstack/platform-objects': minor ---- - -feat(platform-objects): surface phone number in the create_user result dialog - -`sys_user`'s `create_user` action now declares `user.phoneNumber` in its -`resultDialog.fields`, so admins creating phone-based accounts see the -sign-in phone number alongside the email and temporary password. The -create-user response carries `phoneNumber` only for phone-based users; -objectui's ActionResultDialog skips declared fields whose path is absent -from the payload, so email-only users see no extra row. diff --git a/.changeset/cross-object-batch-authz-hardening.md b/.changeset/cross-object-batch-authz-hardening.md deleted file mode 100644 index 1fc4b36f93..0000000000 --- a/.changeset/cross-object-batch-authz-hardening.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -'@objectstack/rest': patch -'@objectstack/spec': minor ---- - -fix(rest): gate the cross-object transactional batch by the same per-object API rules as single-record writes (#1604) - -The `POST {basePath}/batch` route (issue #1604 / ADR-0034) wraps N cross-object -create/update/delete ops in one engine transaction, but it skipped the -per-object API-exposure gate every single-record route applies — an -authenticated caller could write to an `apiEnabled: false` object, or run an -operation outside an object's `apiMethods` whitelist, straight through the batch -surface (ADR-0049 / #1889 — the same "declared ≠ enforced" hole closed for the -generic write path in #3220 / #3213). - -The route now: - -- validates the body against a new `CrossObjectBatchRequestSchema` - (`@objectstack/spec/api`, Zod-First) — a malformed op, an unknown action, or a - missing `object` is a `400` instead of a `500`; -- enforces `enable.apiEnabled` / `enable.apiMethods` for **every** op (metadata - fetched once, each distinct `(object, action)` checked) BEFORE opening the - transaction — `404 OBJECT_API_DISABLED` / `405 OBJECT_API_METHOD_NOT_ALLOWED`; -- requires an `id` for `update` / `delete` (`400`); -- rejects an unresolvable `{ $ref }` with `400 BATCH_UNRESOLVED_REF` instead of - silently writing a `null` FK; -- rejects an explicit `atomic: false` (`400 BATCH_NOT_ATOMIC`) rather than - silently applying atomically — non-atomic per-object batches stay on - `POST /data/:object/batch`. - -`enforceApiAccess` is refactored to share the pure `apiAccessDenialFromEnable` -check + a `loadObjectItems` helper with the batch route (single-record behavior -unchanged). Adds `rest-batch-endpoint.test.ts` — the REST-boundary coverage -ADR-0034 flagged as missing (commit, `$ref`, rollback surfacing, API-access -denial, request validation). diff --git a/.changeset/dashboard-widget-strict-3251.md b/.changeset/dashboard-widget-strict-3251.md deleted file mode 100644 index b0f0858f76..0000000000 --- a/.changeset/dashboard-widget-strict-3251.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -"@objectstack/spec": minor -"@objectstack/lint": patch ---- - -feat(spec)!: `DashboardWidgetSchema.strict()` — reject undeclared widget keys (framework#3251) - -The ADR-0021 analytics endpoint. `DashboardWidgetSchema` now rejects any -undeclared top-level key instead of silently stripping it, moving a whole class -of author error (a hallucinated or legacy key that renders as a silent no-op) -from fallible human review to deterministic CI. `options: z.unknown()` remains -the escape hatch for renderer-specific extras. - -A custom error map names the offending key(s) and, when a key is a removed -pre-ADR-0021 inline-analytics key (`object` / `categoryField` / `valueField` / -`aggregate`, pivot `rowField` / `columnField`) or an objectui-internal prop -(`component`, inline `data`), points the author at the dataset shape -(`dataset` + `dimensions` + `values`). - -Recorded as protocol-16 migration `step16` -(`dashboard-widget-strict-unknown-keys`), mirroring protocol-15's `step15` -strict flip on the form/page schemas (ADR-0089 D3a). The inline-analytics shape -itself was already removed at protocol 9 (single-form cutover), so there is no -mechanical rewrite — the residue is the strictness, delegated to the author. - -**Breaking:** shipped as `minor` per the launch-window policy (a breaking change -does not burn a major while the stack is in lockstep), riding the already-pending -16.0.0 train. The release train's Version-Packages PR must set -`PROTOCOL_VERSION = '16.0.0'`; until then `step16` is inert -(`composeMigrationChain` caps at `PROTOCOL_MAJOR`). - -`@objectstack/lint` — the `widget-legacy-analytics-shape` / -`widget-legacy-analytics-unrenderable` rules are retained as the friendly, -suppressible bridge on the raw-config lint/doctor paths (strict preempts them on -the schema-parsed compile/validate paths); doc comment updated to explain the -interplay. diff --git a/.changeset/date-equality-runtime-fix.md b/.changeset/date-equality-runtime-fix.md deleted file mode 100644 index ec79d30d1d..0000000000 --- a/.changeset/date-equality-runtime-fix.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -"@objectstack/formula": minor ---- - -feat(formula): `dateField == today()` now matches — AST temporal-comparison rewrite (#3183) - -**Behavior change (the fix):** a `Field.date` compared with `==`/`!=` against a -temporal function now matches on the calendar day. Previously it **silently -returned the wrong answer** — `record.due_date == today()` was always `false` -(and `!= today()` always `true`) even for a same-day record, because a -`Field.date` reads back as a `YYYY-MM-DD` **string** (ADR-0053 Phase 1) and -cel-js's equality (`overloads.js` `isEqual`) treats a string and a timestamp as -unequal without consulting any overload. - -`celEngine.evaluate` now rewrites the parsed AST: for each `==`/`!=` whose one -operand is `today()`/`daysFromNow()`/`daysAgo()`/`now()`, the **field operand** -is wrapped in `date(...)` (the stdlib coercion), then the expression is -serialized and evaluated. So `record.due_date == today()` runs as -`date(record.due_date) == today()`. - -- **Per-occurrence**, not per-field: `record.d == "2026-06-20" || record.d == today()` - keeps the string-literal comparison intact while fixing the temporal one. -- **Type-blind-safe**: `date()` degrades gracefully — an already-`Date` - (`Field.datetime`) operand passes through; a non-date string or null → - `Invalid Date` → the comparison stays `false`, exactly as before. No - field-type information is needed, and no currently-correct result is worsened. -- **Cheap**: the rewrite only reserializes when such a comparison is present - (a plain-`includes` gate skips the rest), and is memoized per source string. - -Applies to every interpreter site — read-time `Field.formula`, default values, -validation rules, hook conditions, and flow conditions — since all route through -`celEngine.evaluate`. RLS/sharing conditions are unaffected: they compile via -`cel-to-filter`, which already rejects function calls as a loud authoring error. - -**Supersedes the #3192 advisory lint.** That build-time warning -(`checkTemporalDateEquality`) flagged `dateField == today()` as a silent-miss; -with the runtime fixed it would be a false alarm, so it (and the -`temporalEqualityFields` helper it used) is removed. Authors can now write the -natural `record.due_date == today()` directly; the `date(...)` / -`daysBetween(...) == 0` / range idioms all keep working. diff --git a/.changeset/date-field-equality-guardrail.md b/.changeset/date-field-equality-guardrail.md deleted file mode 100644 index a8e7ff3024..0000000000 --- a/.changeset/date-field-equality-guardrail.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -"@objectstack/formula": minor ---- - -feat(formula): warn when a `date` field is compared to a temporal function with `==`/`!=` (#3183) - -A `Field.date` deserializes as a `YYYY-MM-DD` **string** (ADR-0053 Phase 1), and -cel-js's equality hard-codes `string == ` to `false` — it returns -`false` for a string left operand without ever consulting a registered overload, -and refuses cross-type object equality (`@marcbachmann/cel-js` `overloads.js` -`isEqual`). So the most natural "is it due today" predicate — - -```cel -record.due_date == today() // silently false, even when due_date IS today -record.due_date != today() // silently true for a same-day record -``` - -— compiles clean, throws nothing, and silently never matches. Same silent-miss -family as #1928; **timezone-independent** (fails identically at UTC) and -cross-cutting (formulas, validation, RLS, flow/action/sharing/hook predicates). - -cel-js gives no operator-layer hook to fix the comparison, so this adds a -**build-time advisory warning** (the established ADR-0032 guardrail strategy) -rather than a runtime behavior change. `validateExpression` reuses the shared -`ExprSchemaHint.fieldTypes` (the same per-field type map the #1928 tier-4 -soundness check already threads through `@objectstack/lint`) to flag a `==`/`!=` -between a `date` field (`record.`/`previous.`/bare) and -`today()`/`daysFromNow()`/`daysAgo()`/`now()`, with a self-correcting message -pointing at the working idioms: `date(record.d) == today()`, a range -(`>= … && <= …`), or `daysBetween(today(), record.d) == 0`. - -Warning severity — never fails the build (the write/validation path may carry a -real `Date`). Restricted to `type: 'date'` (unambiguously a string); `datetime` -is excluded to avoid false positives. Ordering operators (`>=`/`<=`/`<`/`>`) -already work — cel-js *throws* for them, tripping the engine's existing -string-hydration retry — so they are not flagged. - -A runtime fix (normalizing the peer of a temporal operand in the data layer) -remains tracked in #3183; a naive "hydrate date fields to `Date`" version would -trade this silent-miss for another (breaking `dateField == "2026-06-20"`), so it -needs its own design. diff --git a/.changeset/dev-loop-dx-p2.md b/.changeset/dev-loop-dx-p2.md deleted file mode 100644 index 081ef6eaec..0000000000 --- a/.changeset/dev-loop-dx-p2.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -"@objectstack/objectql": patch -"@objectstack/metadata": patch -"@objectstack/runtime": patch -"@objectstack/plugin-auth": patch -"@objectstack/cli": patch ---- - -Dev-loop DX fixes from the 15.1 third-party evaluation (P2 batch): - -- **Hot-added objects are now queryable without a restart.** Adding a `*.object.ts` under `os dev` used to recompile "green" while every query answered `no such table` (or `not registered`) until a manual restart: the artifact reload never notified the ObjectQL registry, tables were only created at boot, and seeds only loaded from the boot-time bundle. The `metadata:reloaded` payload now carries the parsed artifact; ObjectQL ingests the object definitions and re-runs the idempotent schema sync (same `skipSchemaSync` opt-out as boot), and the runtime loads seeds for first-seen objects (dev, single-tenant). `os dev` also prints `✚ new object(s): …` on recompile. -- **Dev admin credentials stay visible.** The `os dev` startup banner only showed `admin@objectos.ai / admin123` on the boot that actually seeded it; with the persistent default DB every later boot hid it, and the Console login page never knew it existed. The hint now re-arms on every dev boot for as long as the account still verifies against the default password, and `GET /api/v1/auth/config` exposes a dev-gated `devSeedAdmin` field (never present outside `NODE_ENV=development`) so the login page can show it. -- **`os doctor` reference analysis understands current metadata shapes.** Objects bound through `defineView` containers (`list`/`listViews`/`form`/`formViews` → `data.object`, subform `childObject`, lookup form fields) and app navigation (`objectName`, nested `children`, `areas`) were reported as "defined but not referenced". The collector now walks the canonical shapes (plus flow node `config.object`/`objectName`) and the orphan-view check descends into containers. diff --git a/.changeset/discovery-transactional-batch-capability.md b/.changeset/discovery-transactional-batch-capability.md deleted file mode 100644 index 64afb764c1..0000000000 --- a/.changeset/discovery-transactional-batch-capability.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -"@objectstack/spec": minor -"@objectstack/metadata-protocol": minor -"@objectstack/rest": minor -"@objectstack/plugin-hono-server": minor -"@objectstack/client": minor ---- - -**Broadcast a `transactionalBatch` capability bit in discovery so clients negotiate the atomic cross-object batch declaratively, instead of runtime-probing 404/405/501 (#3298).** - -The atomic cross-object batch endpoint (`POST {basePath}/batch`, #1604 / ADR-0034 item 4) and its typed SDK surface (`client.data.batchTransaction`, #3271) already shipped, but discovery never told a client whether a backend actually supports it. Consumers (notably ObjectUI's `ObjectStackAdapter`) had to *probe*: fire a `/batch`, read `404`/`405` (no route) or `501` (no runtime transaction), and only then fall back to non-atomic client-side simulation. That is "find out by calling", not capability negotiation — it cannot be decided at connect time and cannot serve as the "minimum backend supports `/batch`" gate that blocks hard-deleting the non-atomic fallback downstream. - -`WellKnownCapabilitiesSchema` gains a required `transactionalBatch: boolean`, and **every** discovery producer fills it honestly (`declared === enforced`), so it never becomes a declared-but-unpopulated bit: - -- **`@objectstack/metadata-protocol`** (`getDiscovery`) — reports whether the runtime engine can honour a transaction (`typeof engine.transaction === 'function'`). The `/batch` handler runs its ops inside `engine.transaction()`, which degrades to a non-atomic passthrough (or 501) without one. -- **`@objectstack/rest`** (`/discovery`) — ANDs the engine signal with whether it actually mounts the route (`api.enableBatch`), so a server with batch disabled reports `false` even on a transaction-capable engine (never advertise an endpoint that would 404). -- **`@objectstack/plugin-hono-server`** (standalone discovery) — reports `false`: this minimal surface registers CRUD only and does not mount `/batch` (that ships with `@objectstack/rest`). Under-reporting is the safe direction — a client keeps its correct-but-slower fallback rather than losing atomicity. -- **`@objectstack/client`** — already normalizes hierarchical `capabilities` to flat booleans, so `client.capabilities.transactionalBatch` is exposed (and now typed) for declarative consumers. - -The bit follows the existing capability semantics: `true` ⟺ the `/batch` route is mounted **and** the runtime can honour a transaction — the exact condition under which the endpoint returns `200` rather than `404`/`405`/`501`. Additive and behavior-preserving; only the discovery payload gains a field. diff --git a/.changeset/document-validation-governance-crossfield.md b/.changeset/document-validation-governance-crossfield.md deleted file mode 100644 index 1bcb9417b5..0000000000 --- a/.changeset/document-validation-governance-crossfield.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -"@objectstack/spec": patch ---- - -Document two validation-rule facts surfaced by the 2026-06 liveness audit (follow-up to #3106 / #3184), and clean up a stale form-schema mirror — no runtime behavior change: - -- `label` / `description` / `tags` on validation rules are governance / editor metadata (surfaced to the Studio rule editor and rule listings), not evaluated on the write path. Documented as such on `BaseValidationSchema` rather than removed — they are set by nearly every example rule and feed the `/meta/types` editor form, so they are declared on purpose, not silent no-ops. -- `cross_field` evaluates identically to `script` (same CEL predicate path); only `fields[0]` is read, to target the violation at a field. Documented the overlap on the schema, its `fields` `.describe()`, and the validation docs so authors can choose between them; the variant is kept for the field-targeting affordance and backward compatibility. -- Removed dead form-field entries (`scope`, `caseSensitive`, `url`, `handler`) and the stale `type=unique` hint from the hand-written `HAND_CRAFTED_SCHEMAS['validation']` fallback in `@objectstack/metadata-protocol` — leftovers from the removed `unique`/`async`/`custom` variants. -- Added the missing `beforeDelete` lifecycle-hook pointer to the validation docs' "not a rule type" callout, so delete-time guards aren't stranded now that validation has no `delete` event (#3184). diff --git a/.changeset/drill-range-datetime-tz.md b/.changeset/drill-range-datetime-tz.md deleted file mode 100644 index a145c09cdc..0000000000 --- a/.changeset/drill-range-datetime-tz.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -'@objectstack/core': minor -'@objectstack/service-analytics': minor ---- - -feat(analytics): scope a datetime date-bucket drill to the reference-tz midnight instants (#1752 follow-up) - -Closes the one gap left by the initial #1752 change: a `datetime` date dimension -bucketed under a **non-UTC reference timezone** previously fell back to a superset -drill (its bucket boundary is that tz's midnight *instant*, which `YYYY-MM-DD` -calendar bounds can't express). - -- **`@objectstack/core`** adds `zonedDateStartToUtcMs(ymd, tz)` — the UTC instant - at which a calendar day begins in a reference timezone (the inverse of - `calendarPartsInTz`). DST-safe: the offset is read from the platform tz - database via `Intl`, with a two-pass resolution for the rare offset-boundary - case; an unset/`'UTC'`/invalid zone returns plain UTC midnight. -- **`@objectstack/service-analytics`** now emits `drillRanges` bounds per the - field's temporal type (ADR-0053): a `datetime` field → ISO **instant** bounds - at the reference tz's midnight (works under any tz, incl. DST); a `date` field - → `YYYY-MM-DD` calendar bounds (tz-naive, exact under any tz). An unknown field - type is still emitted only under UTC and omitted (superset) under a non-UTC tz. - -No objectui change is needed — the client already forwards whatever bound values -the server sends into the drill filter and the `filter[field][gte|lt]` URL. diff --git a/.changeset/driver-sql-sqlite3-peer.md b/.changeset/driver-sql-sqlite3-peer.md deleted file mode 100644 index eb21fc1db1..0000000000 --- a/.changeset/driver-sql-sqlite3-peer.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -"@objectstack/driver-sql": patch ---- - -fix(driver-sql): drop the vestigial `sqlite3` peerDependency — the SQLite path uses `better-sqlite3` (#3277) - -`package.json` advertised `peerDependencies.sqlite3: "^5.0.0"`, but the driver never -loads `sqlite3` at runtime. Every first-party SQLite construction site builds a -`client: 'better-sqlite3'` Knex driver (`resolveSqliteDriver` in -`@objectstack/service-datasource`, the datasource driver factory, and the whole -driver test suite), and the README already tells consumers to `pnpm add better-sqlite3`. -`better-sqlite3` is auto-provided as an `optionalDependency` (with the native → wasm → -memory step-down of #2229 covering a failed native build), so the SQLite requirement is -already satisfied without the consumer installing anything. - -The stale `sqlite3` peer only misled: a consumer resolving peer deps could `pnpm add -sqlite3` (never used) while believing they'd satisfied the SQLite requirement. Removing -it aligns the declared contract with the code and the docs. The `sqlite3` string alias -still maps to `better-sqlite3` in the driver factory and dialect detection, so -`driver: 'sqlite3'` config keeps working — it just resolves to `better-sqlite3` like -everything else. diff --git a/.changeset/drop-deprecated-types-tar.md b/.changeset/drop-deprecated-types-tar.md deleted file mode 100644 index 28c417bb53..0000000000 --- a/.changeset/drop-deprecated-types-tar.md +++ /dev/null @@ -1,16 +0,0 @@ ---- ---- - -chore(create-objectstack): drop the deprecated `@types/tar` devDependency - -`tar` v7 ships its own TypeScript declarations (its `types` field points at -`dist/commonjs/index.d.ts`), so the standalone `@types/tar` package is -deprecated and redundant — it only emitted a `WARN deprecated @types/tar` line -on every `pnpm install`. `import * as tar from 'tar'` in `src/index.ts` now -resolves against tar's bundled types, and the package still builds and -type-checks clean. - -devDependency-only change: it is not shipped to consumers and `dist/` is -byte-identical, so this releases nothing (empty changeset, no version bump — -`create-objectstack` is in the lockstep `fixed` group and must not drag the -whole stack up a patch for a no-op). diff --git a/.changeset/field-form-summary-subfields.md b/.changeset/field-form-summary-subfields.md deleted file mode 100644 index ac65df8db6..0000000000 --- a/.changeset/field-form-summary-subfields.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -'@objectstack/spec': patch ---- - -fix(spec): declare `summaryOperations` sub-fields in the Field metadata form (#3257) - -`fieldForm` (the registered metadata form for editing a Field) previously -declared `summaryOperations` as a bare `composite` with no sub-fields, so a -protocol-driven renderer had to fall back to a raw JSON editor. It now declares -the inner shape explicitly — `object` (`ref:object`), `function` (select), -`field`, `relationshipField`, and `filter` (bound to `widget: 'filter-condition'`) -— mirroring the `summaryOperations` Zod schema and surfacing the roll-up `filter` -added in #1868. Also gates the block to `data.type == 'summary'`. - -Small step toward #3257 (making the Studio field designer metadata-driven rather -than hand-coded); the live objectui inspector already edits these fields. diff --git a/.changeset/fix-explain-ownership-values.md b/.changeset/fix-explain-ownership-values.md deleted file mode 100644 index 4d7d48f0d1..0000000000 --- a/.changeset/fix-explain-ownership-values.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -"@objectstack/cli": patch ---- - -fix(cli): `os explain object` documented `ownership` with the wrong allowed values (#3244) - -The schema catalog described the object `ownership` field as the package -*contribution* kind (`"own" | "extend"`, the `ObjectOwnershipEnum` set via -`registerObject`). But `ObjectSchema.ownership` is the **record-ownership -model** — `z.enum(['user', 'org', 'none'])` — a distinct concept the spec -explicitly warns not to conflate despite the shared word. - -`os explain object` now prints: - - ownership 'user' | 'org' | 'none' Record-ownership model: user (default, - injects a reassignable owner_id) | org | none (no per-record owner). - Distinct from the package own/extend contribution kind. - -A regression test (`packages/cli/test/commands.test.ts`) pins the documented -values to the record-ownership enum so the two concepts can't drift back -together. Found during the #1880 docs implementation-accuracy audit. diff --git a/.changeset/flow-registration-schema-aware-validation.md b/.changeset/flow-registration-schema-aware-validation.md deleted file mode 100644 index 935bbb5579..0000000000 --- a/.changeset/flow-registration-schema-aware-validation.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -"@objectstack/service-automation": minor ---- - -feat(automation): schema-aware flow-condition validation at registration (#1928) - -`registerFlow` now runs the same schema-aware condition checks as -`objectstack build` — so a flow registered dynamically (via the API / Studio, -bypassing the build lint) still gets the guardrail. When the host wires an -object-schema resolver, a flow condition that references an unknown field, -likely-typos a field name, or does arithmetic/ordering on a text/boolean field -against a number is surfaced as an **advisory warning** (logged), pointing at -the object's real schema. - -- New `AutomationEngine.setObjectSchemaResolver(resolver)` bridge (mirrors - `setFunctionResolver`); `AutomationServicePlugin` wires it to - `objectql.registry.getObject` in `start()`, before the flow pull, so - registry-sourced flows are covered too. -- **Strictly additive / zero regression**: the fatal set is unchanged (syntax, - brace-in-CEL, unknown-function still throw); everything the schema pass adds is - logged, never thrown, and the whole thing is a no-op when no resolver is wired. - Flow conditions bind fields flat, so the check runs in `flattened` scope - (flow variables stay `dyn` and are never flagged; equality is runtime-safe). - -Builds on the tier-4 type-soundness check in `@objectstack/formula` / -`@objectstack/lint` (#1928). diff --git a/.changeset/flow-trigger-observability.md b/.changeset/flow-trigger-observability.md deleted file mode 100644 index 5f4cf09e68..0000000000 --- a/.changeset/flow-trigger-observability.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -'@objectstack/service-automation': minor -'@objectstack/trigger-record-change': minor -'@objectstack/lint': minor -'@objectstack/cli': minor -'@objectstack/objectql': patch -'@objectstack/runtime': patch -'@objectstack/plugin-audit': patch ---- - -Flow trigger observability — kill the four-layer silence around record-change flows that never fire (2026-07-17 third-party eval). - -A misauthored auto-launched flow (wrong `objectName`, missing `requires: ['automation','triggers']`, failing start condition) produced ZERO output at every layer: the engine's own registration/binding logs land inside the CLI's boot-quiet stdout window (which swallows debug/info/warn — only error/fatal reach stderr), and each "didn't happen" path was itself silent. Fixes: - -- **Startup banner `Flows:` section** (`os serve`/`os dev`/`os start`): flow count, bound-to-trigger count, registered trigger types, draft count — plus loud `⚠` lines for flows declared with no automation engine enabled (`requires` missing), flows whose trigger type has no registered trigger, and bound record-change flows targeting an unknown object (dead binding). Printed after stdout is restored, so it is immune to the boot-quiet window. -- **Trigger-fired run failures now log at ERROR** (stderr — always visible): the automation engine no longer drops the AutomationResult of a trigger-fired execution; condition-evaluation faults and node failures surface with the flow name. Condition-not-met skips stay at debug (high-frequency, intentional). -- **`RecordChangeTrigger` probes object existence at bind time** and warns when a flow's `objectName` matches no registered object (exact-name matching), instead of silently arming a hook that can never fire. -- **`kernel:bootstrapped` binding audit** in the automation plugin: warns per enabled-but-unbound triggered flow with the reason, and reports registered/bound/draft counts (`AutomationEngine.getTriggerBindingAudit()`, extended `getFlowRuntimeStates()` with `status`/`triggerType`/`object`). -- **`os validate` flow-wiring advisories** (`@objectstack/lint` `validateFlowTriggerReadiness`): warns when a record-triggered flow targets an object the stack does not define, and when an auto-triggered flow's status is `draft` (authored or defaulted — draft flows still fire; declare `active` or `obsolete`). -- Removed leftover boot-debug writes (`registerApp`/`AppPlugin`/`StandaloneStack`/`AuditPlugin` stderr noise) that previous debugging of this same silence had left behind. diff --git a/.changeset/formula-null-guard-floor-date-arith-3306.md b/.changeset/formula-null-guard-floor-date-arith-3306.md deleted file mode 100644 index 70438c1dfc..0000000000 --- a/.changeset/formula-null-guard-floor-date-arith-3306.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -"@objectstack/formula": minor ---- - -**Stored `Field.formula` fields that compute dates/durations no longer silently evaluate to `null` (#3306).** Three independent CEL gaps made shipped template formulas (e.g. `hr_employee.tenure_years`, `hr_time_off_request.days`) return `null` with no parse/build/runtime error: - -1. **The null-guard idiom `cond ? : null` now compiles and evaluates.** cel-js's ternary type-unifier rejects a concrete `int`/`double`/`string` branch against `null` — so even `true ? 5 : null` faulted *"Ternary branches must have the same type"* and the whole formula nulled. A `Field.formula` is inherently nullable and the catalog blesses both ternary and `== null`, so this is the canonical "compute value, else blank" shape. An AST pre-pass (mirroring the #3183 temporal-equality rewrite) wraps the non-null branch in `dyn(...)` — value-preserving, null-branch-only, idempotent — so it type-checks and runs. Applied in `compile()`, `evaluate()`, and the build soundness check alike. - -2. **`floor(x)` / `ceil(x)` are now registered** (parallel to `round`/`abs`) and advertised in the catalog. They round toward −∞ / +∞, so `floor(-1.2) == -2` — NOT interchangeable with integer division's round-toward-zero. Previously `floor(...)` faulted `found no matching overload` and the formula nulled. - -3. **Date arithmetic is now a build-time ERROR instead of a silent runtime `null`.** `record.end_date - record.start_date + 1`, `today() + 30`, `record.date + n` type-check clean (operands are `dyn`) but always fault at runtime and never recover (a date string is not numeric, so hydration can't rescue it). The build soundness check now types `date`/`datetime` fields as `google.protobuf.Timestamp` and flags date/duration **arithmetic against a number** with a corrective message pointing at `daysBetween(a, b)` / `daysFromNow(n)` / `addDays(d, n)` / `addMonths(d, n)`. Sound by construction — ordering (`date < today()`, `date < "2026-01-01"` string-lex), equality (#3183), and string concatenation (`"Due: " + date`) are all runtime-tolerated and never flagged; only arithmetic against a number is. A `!= null` guard on a date field no longer masks the inner fault (`== null` no-op overloads registered in the check-only env). - -> **Heads-up for downstream:** (3) adds a NEW build-time error. A stored formula or predicate doing arithmetic on a `date`/`datetime` field (`end - start + 1`, `today() + 30`) that previously built (and nulled at runtime) will now fail `objectstack build` / `validateStackExpressions` with a message telling you to use `daysBetween` / `daysFromNow` / `addDays`. This only fires for genuinely-broken expressions that already returned `null`. - -Fixes #3306. diff --git a/.changeset/hook-nested-write-timeout.md b/.changeset/hook-nested-write-timeout.md deleted file mode 100644 index 877c580246..0000000000 --- a/.changeset/hook-nested-write-timeout.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -'@objectstack/runtime': patch ---- - -fix(runtime): honor a hook body's declared `timeoutMs` so nested cross-object writes aren't clamped to 250ms (#1867) - -Hook bodies run in the QuickJS sandbox with a default 250ms timeout. The runner -folded that engine default straight into `Math.min(...)` when resolving the -effective timeout, so it *always* dominated for hooks: a body that declared a -larger `timeoutMs` (the spec permits up to 30_000ms — `ScriptBody.timeoutMs`) to -give a legitimate nested write — "when a child changes, update the parent" — -room to settle was silently clamped back to 250ms and killed mid-flight. The -declared knob was never enforced. - -The engine default is now a FALLBACK used only when no explicit timeout is -supplied, not a hard ceiling. An explicit `body.timeoutMs` (and/or an enclosing -hook/action timeout) is honored; when both are present the smaller wins. Bodies -that declare nothing still get the 250ms hook / 5000ms action default, and a -body may still LOWER its own timeout below the default. - -This clears the last reliability blocker for nested cross-object writes from -hooks — the sandbox crash itself (`memory access out of bounds`) was already -fixed by the deferred-promise host-call model — so header/rollup fields no -longer need denormalized, hand-maintained workarounds. diff --git a/.changeset/i18n-inline-label-default-locale.md b/.changeset/i18n-inline-label-default-locale.md deleted file mode 100644 index 076e60d2cf..0000000000 --- a/.changeset/i18n-inline-label-default-locale.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -"@objectstack/cli": patch ---- - -fix(cli): treat an inline `label:` as the default-locale source in i18n coverage - -A fresh `npm create objectstack` scaffold reported 4 `i18n/missing-object` / -`i18n/missing-field` errors for its own `_note` object, even though the -template authors `label: 'Note'`, `pluralLabel: 'Notes'`, `label: 'Title'` and -`label: 'Body'` inline. The only way to silence them was to commit an `en` -bundle restating strings the metadata already carries. - -The inline `label:` *is* the default-locale text: the runtime resolver falls -back to it when a bundle has no entry (`translateObject`), and `os i18n -extract` seeds bundles from it. Coverage now honours that contract — an inline -label satisfies the default locale, and a bundle is what *other* locales need. -Keys with no source string anywhere are no longer reported as i18n gaps; a -missing label is already `required/label`'s finding. - -Non-default locales are unaffected: they still warn for every untranslated key -(`os lint` on `examples/app-todo` reports the same 79 warnings as before, with -its 39 default-locale errors gone). `os lint --include-platform` drops the -platform baseline's default-locale errors for the same reason — the platform -ships English labels inline — while keeping its non-default-locale warnings. diff --git a/.changeset/import-users-auto-policy.md b/.changeset/import-users-auto-policy.md deleted file mode 100644 index 253badb864..0000000000 --- a/.changeset/import-users-auto-policy.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -"@objectstack/plugin-auth": minor ---- - -**Bulk user import defaults to `auto` — prefer invite per row, temporary only for undeliverable rows (#3236).** The identity import endpoint (`POST /api/v1/auth/admin/import-users`) gains a fourth `passwordPolicy`, **`auto`**, and it is now the **default** (was `none`). - -`auto` decides **per row** instead of forcing one policy on the whole batch: - -- a row with a deliverable channel — a **real email + a wired email service**, or a **phone + a wired SMS-invite path** — is **invited** (a set-your-password email, or an invitation SMS for phone-only rows), so no shared secret ever leaves the server; -- a row with **no** deliverable channel (placeholder email, phone-only without SMS, or an email row when no email service is wired) falls back to a **temporary password**, returned once in the response with `must_change_password` stamped. - -This shrinks the temporary-password blast radius from "the whole batch" to "only the rows that genuinely can't be reached", and — unlike `invite` — `auto` **never rejects the request for missing infrastructure**: with nothing wired, every row simply degrades to temporary. The per-row outcome is surfaced on `rows[].delivery` (`email` / `sms` / `temporary`) with a batch breakdown on `summary.delivery` (also recorded in the run audit). - -The three existing policies are unchanged and still selectable explicitly: - -- `invite` — force the invite path for every row; unreachable rows are **failed** per-row (never downgraded). Pick this when a temporary-password fallback is unacceptable. -- `temporary` — force a generated temporary password for every row. -- `none` — identity only, no password and no invitation. - -**Behavior change to note:** callers that **omit** `passwordPolicy` previously got `none` (no credential, no outbound message); they now get `auto`, which proactively sends invitations to deliverable rows (and returns temporary passwords for the rest). Callers that want the old identity-only behavior must pass `passwordPolicy: 'none'` explicitly. Every call that already passes an explicit policy is unaffected, and the response is a strict superset (adds the `delivery` fields). diff --git a/.changeset/keyvalue-node-config-schemas.md b/.changeset/keyvalue-node-config-schemas.md deleted file mode 100644 index 207d432c85..0000000000 --- a/.changeset/keyvalue-node-config-schemas.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -"@objectstack/service-automation": patch ---- - -feat(automation): publish configSchemas for the keyValue-capable nodes (flow designer parity, #3304) - -The `assignment`, `create_record` / `update_record` / `delete_record` / -`get_record`, and `screen` nodes shipped no `configSchema`, so the flow designer -had no server-driven form for them. Each descriptor now carries one that mirrors -the objectui hardcoded field group field-for-field: object references as `xRef`, -the screen repeater's `visibleWhen` as `xExpression: 'expression'`, and the -free-form maps (`fields` / `filter` / `assignments` / `defaults`) as JSON-Schema -open objects (`additionalProperties: true`, no fixed `properties`) — the shape -the designer's schema adapter renders with its flat keyValue editor. Values stay -fully permissive because real metadata carries operator objects (`{"$ne": null}`), -`{var}` templates, and non-string literals. - -Deliberately still schemaless (no online/offline divergence exists for a node -with no configSchema, and a partial schema would drop editors): `decision` -(virtual Target column derived from edges), `wait` (top-level `waitEventConfig`), -`script` (actionType-conditional form), `subflow` (top-level `timeoutMs`). - -Additive and backward-compatible: descriptor metadata only, no runtime behavior -change. Requires an objectui with the keyValue schema mapping (objectui #2708) -for the maps to render as structured editors; older designers keep their -hardcoded forms. diff --git a/.changeset/lint-hide-platform-baseline.md b/.changeset/lint-hide-platform-baseline.md deleted file mode 100644 index ebb8f8b241..0000000000 --- a/.changeset/lint-hide-platform-baseline.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@objectstack/cli": patch ---- - -`os lint` no longer buries the user's own signal under the platform i18n baseline. A fresh scaffold reported 800+ `i18n/missing-metadataForm` errors — translation keys for platform built-in metadata forms (email_template, …) that the platform packages already ship at runtime. Those are now hidden by default and folded into one summary line (`platform built-ins: N i18n issue(s) hidden`); pass `--include-platform` to audit them, and read `hiddenPlatform` in `--json` output. User-authored metadata coverage is reported unchanged. diff --git a/.changeset/localize-collab-notifications-and-storage-objects.md b/.changeset/localize-collab-notifications-and-storage-objects.md deleted file mode 100644 index b05ea42502..0000000000 --- a/.changeset/localize-collab-notifications-and-storage-objects.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -"@objectstack/plugin-audit": patch -"@objectstack/service-storage": patch -"@objectstack/runtime": patch ---- - -feat(i18n): localize collaboration notification titles and the storage objects; wire the notifications REST routes - -Three gaps behind one report (a `sys_file "repro.png" assigned to you` -notification that was English on an all-Chinese workspace, opened an English -detail page, and never cleared its unread state): - -- **plugin-audit** — the assignment (`collab.assignment`) and @mention - (`collab.mention`) bell titles were hardcoded English literals built from the - raw object API name. They now resolve through the i18n service with the same - key shapes as the activity summaries (framework#3039): new - `messages.assignedToYou` / `messages.mentionedYou` / - `messages.mentionedYouAnonymous` templates (en / zh-CN / ja-JP / es-ES), the - object named by its translated label (`objects.{name}.label` → authored def - label → API name), and the locale resolved for the **recipient** (they read - the bell), not the acting user. Every step stays best-effort: no locale / no - i18n / key miss degrades to the English literal — which now also prefers the - authored object label over the API name. - -- **service-storage** — `sys_file` / `sys_upload_session` had no translation - bundle at all, so the file detail page (labels, and the Pending Upload / - Committed / Deleted status pipeline) rendered English on every locale. The - service now ships its own ADR-0029 D8 bundle (en / zh-CN / ja-JP / es-ES, - `src/translations` + `scripts/i18n-extract.config.ts`) and contributes it via - `i18n.loadTranslations` on `kernel:ready`, matching service-messaging. - (`sys_attachment` stays in platform-objects' bundles pending the - storage-domain decomposition.) - -- **runtime** — the in-app notifications REST surface (`GET - /api/v1/notifications`, `POST /api/v1/notifications/read`, `POST - /api/v1/notifications/read/all`; ADR-0030) had its `handleNotification` - dispatch branch and discovery entry, but no `server.()` mount in - `dispatcher-plugin`, so only the cloud hosts' hono catch-all reached it — the - standalone / `os dev` server 404'd every request. That left mark-read with no - working endpoint (the console's direct `sys_notification_receipt` write is - rejected by ADR-0103's engine-owned gate), so unread notifications could never - clear. The three routes are now mounted explicitly, guarded by the - route-registration regression test. diff --git a/.changeset/logger-esm-file-destination.md b/.changeset/logger-esm-file-destination.md deleted file mode 100644 index 0ae3935b2c..0000000000 --- a/.changeset/logger-esm-file-destination.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"@objectstack/core": patch ---- - -**`createLogger({ file })` now actually writes the file under ESM.** `openFileStream` loaded `fs` with a lazy `require()` to keep the browser-safe logger entry out of the `fs` bundle graph; esbuild rewrites that to its `__require` shim in the ESM output, which throws `Dynamic require of "fs" is not supported`, and a bare `catch {}` swallowed it. Since the workspace is `type: module`, every Node ESM consumer — `os serve`, `os dev` — silently got no file logging at all, while the CJS build kept working. The builtin now loads via `process.getBuiltinModule` (opaque to bundlers, works in both module systems, with a `require` fallback for Node < 20.16), and a `file` destination that cannot be opened reports itself on stderr instead of disappearing. - -Turning the destination back on also fixed three faults that were unreachable while it never opened: `child()` opened a second stream per child and orphaned it, destroying a child logger closed the stream its parent and siblings were still writing to, and an async open failure (e.g. an unwritable path) hit an `'error'` event with no listener and took the process down. diff --git a/.changeset/logger-honors-no-color.md b/.changeset/logger-honors-no-color.md deleted file mode 100644 index 5b95d7482e..0000000000 --- a/.changeset/logger-honors-no-color.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -"@objectstack/core": patch ---- - -fix(core): ObjectLogger honors NO_COLOR and TTY detection before emitting ANSI colors - -The kernel/plugin logger (`ctx.logger`, wired by `os serve` / `os dev`) colorized its -`pretty`-format level tags unconditionally, so `NO_COLOR=1` runs and piped/CI output -still carried ANSI escapes (e.g. `\x1b[31m…ERROR\x1b[0m`), breaking plain-text log -scanners (see scripts/publish-smoke.sh, which had to strip ANSI before grepping). - -Per the no-color.org convention, color is now emitted only when the destination stream -(stdout, or stderr for error/fatal) is an interactive TTY **and** `NO_COLOR` is unset or -empty — any non-empty `NO_COLOR` value disables color. Interactive terminals keep the -existing colorized output. The optional file destination now always receives plain text. diff --git a/.changeset/loop-collection-xexpression.md b/.changeset/loop-collection-xexpression.md deleted file mode 100644 index 1b1914d91a..0000000000 --- a/.changeset/loop-collection-xexpression.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -"@objectstack/spec": patch -"@objectstack/service-automation": patch ---- - -feat(automation): mark the loop `collection` config field as an interpolate() template so designer forms render it correctly (#3304) - -The flow designer generates a node's config form from its published -`configSchema` (ADR-0018). A string property can now carry an `xExpression: -'expression' | 'template'` marker — riding the same Zod `.meta()` → JSON-Schema -channel as `xRef` / `xEnumDeprecated` — that declares whether the string is bare -CEL or an `interpolate()` single-brace `{var}` template. - -The `loop` node's `collection` (e.g. `{tasks}`) is a template, so it is now -marked `xExpression: 'template'` on both the canonical `LoopConfigSchema` and the -shipped descriptor's `configSchema` literal (service-automation loop-node). -Without the marker the designer rendered `collection` as plain text online while -the offline hardcoded form rendered it as a mono expression editor, and the CEL -brace-trap false-flagged `{tasks}` as a malformed condition. The marker closes -that divergence — objectui #2670 Phase 3 (#2699) already consumes it. - -Additive and backward-compatible: an unknown `xExpression` value is ignored by -the designer, and runtime behavior is unchanged. Filling the same marker in on -the remaining node types (map/decision/script and the node types that publish no -`configSchema` yet) is tracked as follow-up in #3304. diff --git a/.changeset/managed-deny-registry-drift.md b/.changeset/managed-deny-registry-drift.md deleted file mode 100644 index 32acb14c6a..0000000000 --- a/.changeset/managed-deny-registry-drift.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -"@objectstack/plugin-security": patch ---- - -**Derive the better-auth managed-object write denies from the live registry (#3325, follow-through of ADR-0092 / ADR-0103).** The default permission sets deny generic writes on better-auth identity tables via a hand-maintained `BETTER_AUTH_MANAGED_OBJECTS` list — exactly the drift ADR-0092 forbids, and it had already drifted (the list carried 17 names while 22 schemas declare `managedBy: 'better-auth'`, leaving `sys_scim_provider`, `sys_sso_provider`, and three `sys_oauth_*` tables wildcard-granted for writes at the permission-evaluator layer; the identity write guard still 403'd the actual write, so this was a defense-in-depth gap, not a live hole). - -- New `applyManagedWriteDenies` (`managed-object-write-denies.ts`) injects a read-only-write deny for every registered `managedBy: 'better-auth'` object into the four write-granting default sets (`organization_admin`, `member_default`, `viewer_readonly`, MCP write) at `kernel:ready`, mutating the shared in-memory `bootstrapPermissionSets` in place (the array the evaluator resolves and the seeder serializes — a DB-row-only fix would be dead code). Never touches `admin_full_access`, never overrides an existing explicit entry, ignores `userActions` (the better-auth bucket is hard-denied — `sys_user`'s `userActions.edit` opens only a field-level whitelist the identity guard enforces). -- The static `BETTER_AUTH_MANAGED_OBJECTS` list is completed to 22 and kept as a compile-time baseline (covers the pre-`kernel:ready` window), now pinned bidirectionally against the `@objectstack/platform-objects` schemas by a test so it cannot silently rot again. -- Engine-owned `system`/`append-only` objects are deliberately NOT given deny entries — a per-object entry overrides the wildcard and would drop `viewAllRecords`; their writes are already rejected by the ADR-0103 engine guard. - -No public API change; the helper is internal. Behavior is byte-preserving for the 17 already-listed tables and closes the gap on the 5 that had drifted. diff --git a/.changeset/managedby-engine-owned-bucket.md b/.changeset/managedby-engine-owned-bucket.md deleted file mode 100644 index a1a81d1e3b..0000000000 --- a/.changeset/managedby-engine-owned-bucket.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -"@objectstack/spec": minor -"@objectstack/plugin-security": minor -"@objectstack/plugin-hono-server": minor -"@objectstack/metadata-core": patch -"@objectstack/platform-objects": patch -"@objectstack/plugin-approvals": patch -"@objectstack/plugin-sharing": patch -"@objectstack/service-automation": patch -"@objectstack/service-messaging": patch ---- - -**Split the overloaded `managedBy: 'system'` bucket with an explicit `engine-owned` value (ADR-0103 addendum, #3343).** ADR-0103 deferred the enum split ("revisitable later as a rename") because a new `managedBy` value would fall through to the fully-editable `platform` default on deployed Console clients. Both reasons against it are now retired — the server-side write guard / `apiMethods` reconciliation / `/me/permissions` clamp make that fallthrough cosmetic (the write is rejected regardless of what the client renders), and objectui#2712 closed the UI union — so v16 lands it, **additively**. - -- **New enum value `engine-owned`** with the same all-locked default affordance row as `system` (`create/import/edit/delete: false`, `exportCsv: true`). It joins `ENGINE_OWNED_BUCKETS` (the engine write guard) and `GUARDED_WRITE_BUCKETS` (the `/me/permissions` clamp); the guard, `reconcileManagedApiMethods`, and the clamp mechanisms are unchanged — `engine-owned` is an explicit member of the set they already covered by resolved affordance. -- **20 objects relabelled `system → engine-owned`** — the ones the engine owns end to end and that declared no write-opening `userActions` (the metadata store, jobs, approval runtime rows, sharing rows, `sys_automation_run`, the messaging delivery/receipt pipeline, `sys_secret`, settings). One-line, behaviour-identical per object. -- **8 admin/user-writable objects keep `managedBy: 'system'`** (the RBAC link tables, `sys_user_preference`, `sys_approval_delegation`, the messaging config grids) — `system` now reads as "engine-managed schema, writable via `userActions`". - -Behaviour-, enforcement- and wire-identical: resolved affordances, the guard verdict, the 405 `apiMethods` reconciliation, and the permissions clamp are the same before and after — this is a self-documenting relabel, not a policy change. No data migration (`managedBy` is schema metadata) and no code branches on the `'system'` literal. Retiring the overloaded `system` entirely (moving the 8 writable objects to a dedicated bucket) is a breaking rename deferred to v17. diff --git a/.changeset/map-node-config-schema.md b/.changeset/map-node-config-schema.md deleted file mode 100644 index 0cea361c3b..0000000000 --- a/.changeset/map-node-config-schema.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -"@objectstack/service-automation": patch ---- - -feat(automation): publish a configSchema for the `map` node (flow designer parity, #3304) - -The `map` (sequential multi-instance) node shipped no `configSchema`, so the flow -designer fell back to its hardcoded field group online and to raw Advanced-JSON -where that wasn't present. Its descriptor now carries a structured `configSchema` -that mirrors the objectui hardcoded `map` field group field-for-field — -`collection` (marked `xExpression: 'template'`, an `interpolate()` `{items}` -template, same as `loop.collection`), `flowName` + `itemObject` as typed -references (`xRef`), and `iteratorVariable` / `outputVariable` as plain text — so -the online (schema-driven) and offline forms match. - -`map` is the one previously-schemaless flow node whose fields are all scalars and -typed references, so it maps cleanly through objectui's `jsonSchemaToFlowFields` -with zero regression. The remaining schemaless nodes lean on editor kinds the -schema→fields adapter does not yet reproduce (`keyValue` maps, the decision -virtual `target` column, `wait`'s top-level block), and are deferred to #3304 -until that adapter is extended. Additive and backward-compatible: no runtime -behavior change; an older designer that ignores the schema is unaffected. diff --git a/.changeset/mcp-dev-connect-hint.md b/.changeset/mcp-dev-connect-hint.md deleted file mode 100644 index 348a2cb110..0000000000 --- a/.changeset/mcp-dev-connect-hint.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -'@objectstack/cli': patch ---- - -feat(cli): surface the MCP endpoint in the server-ready banner (#3167) - -The MCP server (`/api/v1/mcp`) is a default-on core capability, but nothing in -the `os dev` / `os serve` boot output pointed to it — a developer had to already -know it was there to connect an AI client. The server-ready banner now prints -the MCP URL and the `SKILL.md` pointer whenever the surface is enabled -(`isMcpServerEnabled()`, the same switch that auto-loads the plugin and gates -the route), so an agent can operate the running app straight from the dev loop. -Hidden when `OS_MCP_SERVER_ENABLED=false`. diff --git a/.changeset/mcp-stdio-principal-admission.md b/.changeset/mcp-stdio-principal-admission.md deleted file mode 100644 index e653f556b7..0000000000 --- a/.changeset/mcp-stdio-principal-admission.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -'@objectstack/mcp': minor ---- - -feat(mcp)!: stdio transport requires an API-key principal — fail-closed, no unscoped bridge (ADR-0101, #3246) - -The long-lived MCP **stdio** transport no longer reads data unscoped. It now runs -under an env-supplied identity, closing the platform's last identity-less -execution surface (the `mcp-stdio-authority` conformance row graduates -`experimental` → `enforced`). - -- `OS_MCP_STDIO_API_KEY=osk_...` supplies the stdio identity, resolved through - the SAME `@objectstack/core` verify + authorization chain as the HTTP/REST - surfaces; the `record_by_id` resource reads via `ql.find(obj, { where:{id}, - context })`, so RLS/FLS/tenant apply exactly as on REST `/data`. Re-resolved - per read, so a revoked/expired key stops working on a live session. -- **Fail-closed** — enabling stdio auto-start (`OS_MCP_STDIO_ENABLED=true` / - `autoStart`) without a resolvable key throws and refuses to start. There is no - unscoped fallback and deliberately no `system` bypass; full authority is a key - minted on a platform-admin or dedicated service identity. - -**BREAKING (stdio auto-start only):** previously `OS_MCP_STDIO_ENABLED=true` -(or the plugin `autoStart` option) started stdio with full, unscoped authority -and no credential. It now requires `OS_MCP_STDIO_API_KEY`; without it, boot -fails closed. The default-on HTTP surface and any deployment that never enables -stdio auto-start are unaffected. diff --git a/.changeset/mcp-stdio-switch-split-and-dev-connect-hint.md b/.changeset/mcp-stdio-switch-split-and-dev-connect-hint.md deleted file mode 100644 index 45bcd645f5..0000000000 --- a/.changeset/mcp-stdio-switch-split-and-dev-connect-hint.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -'@objectstack/types': minor -'@objectstack/mcp': minor -'@objectstack/cli': minor -'create-objectstack': patch ---- - -feat(mcp): decouple the stdio auto-start switch from the HTTP surface + surface the MCP endpoint on `os dev` boot (#3167) - -The MCP HTTP surface (`/api/v1/mcp`) and the long-lived stdio transport used to -share one env var: `OS_MCP_SERVER_ENABLED=true` turned the HTTP surface on **and** -silently auto-started the stdio transport — which bridges the raw metadata service -+ data engine with no per-request principal (unscoped). An operator setting it to -"make sure MCP is on" got an unscoped transport as a side effect. - -- **`@objectstack/types`** — new `resolveMcpStdioAutoStart()`. Stdio auto-start is - now its own switch, `OS_MCP_STDIO_ENABLED` (default off); `OS_MCP_SERVER_ENABLED` - governs only the HTTP surface. The legacy `OS_MCP_SERVER_ENABLED=true` trigger - still starts stdio for one release, flagged as deprecated. `=false` is unchanged - (it only ever gated HTTP). -- **`@objectstack/mcp`** — `MCPServerPlugin.start()` gates stdio on the new switch - and logs a one-time deprecation warning when started via the legacy alias. -- **`@objectstack/cli`** — `os dev` now prints the MCP endpoint, the agent-skill - URL, and a ready-to-paste `claude mcp add` command on boot (gated on the HTTP - surface being on), so the "an agent operates the app it's building" loop is - discoverable at dev time. -- **`create-objectstack`** — the blank scaffold README documents that the app is - itself an MCP server (the serve side), distinct from the consume-side connector. diff --git a/.changeset/mcp-validate-expression-tool.md b/.changeset/mcp-validate-expression-tool.md deleted file mode 100644 index 6bda9ef262..0000000000 --- a/.changeset/mcp-validate-expression-tool.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -"@objectstack/mcp": minor ---- - -feat(mcp): `validate_expression` tool — validate a CEL expression against a schema before authoring (#1928) - -Adds an agent-callable MCP tool that runs the same build-time expression checks -as `objectstack build`, so an AI can validate a formula / predicate / flow -condition **while authoring** instead of shipping one that silently evaluates to -`null`. Given `{ objectName, expression, site? }` it resolves the object's real -schema (field names + types, via the principal-bound `describeObject` bridge) -and returns: - -- **errors** — bare field refs (`amount` → `record.amount`), unknown fields - (with a did-you-mean), unknown functions; -- **warnings** — text/boolean fields misused in arithmetic, date-equality - pitfalls; -- **inScope** — the fields, stdlib functions, and namespace roots available, so - the model can self-correct; -- **inferredType** for a `formula` site. - -`site` (`formula` | `validation` | `flow_condition` | `template`, default -`formula`) maps to the validator's role + scope — `flow_condition` binds fields -bare, the rest bind `record.`. Read-only, gated by the `data:read` OAuth -scope, and fail-closed on `sys_*` objects like the other schema tools. This is -the authoring-time surface the guardrail series (#1928) always pointed at; -`@objectstack/mcp` gains a `@objectstack/formula` dependency (acyclic; formula is -a leaf). diff --git a/.changeset/metadata-list-package-aware-dedup.md b/.changeset/metadata-list-package-aware-dedup.md deleted file mode 100644 index b972e05216..0000000000 --- a/.changeset/metadata-list-package-aware-dedup.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -'@objectstack/metadata-protocol': patch ---- - -fix(metadata-protocol): unscoped metadata list dedupes package-aware, not by bare name (ADR-0048 #1828) - -`getMetaItems` merged registry items, `sys_metadata` overlay rows, draft-preview -rows, and MetadataService items into `Map`s keyed by bare `name`, so two installed -packages shipping the same `type/name` (e.g. `page/home`) collapsed to one row -(last-write-wins) on an unscoped `GET /meta/:type` whenever either package had an -overlay — and the frontend prefer-local resolution, which reads that list, could -no longer tell the two packages' rows apart. - -The three merge sites (plus the env/org pre-merge) now key by `(package, name)`, -mirroring `getMetaItem`'s scoped-then-global-fallback resolution: colliding rows -stay distinct each with its own `_packageId`, a package-less (env-wide) overlay -still wins over the single artifact it customizes (ADR-0005 precedence and -single-package behaviour unchanged), and the registry-hydration artifact graft is -scoped to each row's own `package_id` so a collision no longer mislabels provenance. diff --git a/.changeset/metadata-register-notifies-watchers.md b/.changeset/metadata-register-notifies-watchers.md deleted file mode 100644 index f77d1ad864..0000000000 --- a/.changeset/metadata-register-notifies-watchers.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -"@objectstack/spec": patch -"@objectstack/metadata": patch -"@objectstack/objectql": patch ---- - -**`MetadataManager.register()` / `unregister()` now announce to `subscribe()` watchers.** Both updated the registry, persisted to writable loaders and published to realtime, but never fired the watch callbacks — so `subscribe()` looked like it covered every write while silently missing all of them. Only the `saveMetaItem` path (via the repository watch stream) and the filesystem watcher ever reached a subscriber. Runtime consumers that cache metadata — notably ObjectQL's SchemaRegistry bridge, the component that decides what is queryable — went stale on every other write until the process restarted. - -Announcing is now the **default**, so a new call site is correct without knowing this contract exists. This is a contract fix rather than a bug fix: the one live behavior change is that runtime datasource writes (`datasource-admin`) now reach the HMR SSE stream, which subscribes to every registered type. `unregisterPackage()` / `bulkUnregister()` also announce their deletes now — correct, but latent, since neither has a production caller today. - -Bulk ingest opts out explicitly with the new `MetadataWriteOptions` (`{ notify: false }`) — boot-time filesystem priming, artifact ingest, and ObjectQL's registry bridge, each of which either runs before consumers cache anything or announces the whole batch once (as the artifact reload path does via `metadata:reloaded`). The bridge in particular MUST stay silent: it copies objects out of the SchemaRegistry, and announcing would feed them back through a handler that re-registers under `_packageId ?? 'metadata-service'`, overwriting the true package provenance of every object whose body carries no `_packageId`. - -Additive only — `register(type, name, data)` and `unregister(type, name)` keep working unchanged. - -Fixes #3112. diff --git a/.changeset/objectql-checkboxes-option-gating.md b/.changeset/objectql-checkboxes-option-gating.md deleted file mode 100644 index e1de36fa02..0000000000 --- a/.changeset/objectql-checkboxes-option-gating.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -"@objectstack/objectql": patch ---- - -**Enforce per-option `visibleWhen` on `checkboxes` fields, and match option values by string form (objectui#2729).** Server-side per-option gating already covered `select` / `multiselect` / `radio`, but two holes let gated values through on write: - -- **`checkboxes` was not enforced.** `CHOICE_FIELD_TYPES` omitted `checkboxes`, so a gated `checkboxes` option (whose client widget cascades identically to `multiselect` since objectui#2715) was hidden in the UI but accepted from a crafted write. Added `checkboxes` to the enforced set — its picked values are now re-evaluated against each option's `visibleWhen` (record + `current_user`) on insert/update/bulk-update, element-wise, like `multiselect`. -- **Numeric option values could slip the gate.** Option matching used strict `===`, but the enum-membership validator compares by `String(...)`. A numeric option value submitted as a string (a normal REST/JSON round-trip) passed the enum check yet missed its `visibleWhen` gate (fail-open). Matching now coerces both sides with `String(...)`, so the two validators agree on which option a written value denotes. - -Behavior for `select` / `multiselect` / `radio` is unchanged. Fail-open on unbound `current_user` / unevaluable predicates is preserved. diff --git a/.changeset/ownership-record-model-field.md b/.changeset/ownership-record-model-field.md deleted file mode 100644 index 1fab1f596d..0000000000 --- a/.changeset/ownership-record-model-field.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -"@objectstack/spec": minor -"@objectstack/cli": patch ---- - -fix(spec): declare `ownership` as a first-class ObjectSchema field (#3175) - -The object-level record-ownership model — `ownership: 'user' | 'org' | 'none'`, -which drives the registry's `owner_id` auto-provisioning (`applySystemFields`) — -was read by the engine via `(schema as any).ownership` while `ObjectSchema.create()` -**rejected** it as an unknown top-level key (ADR-0032 / #1535). So a tested engine -opt-out (`ownership: 'org' | 'none'` on catalog / junction tables) could not be -set through the sanctioned authoring path, and the same `ownership` word was read -elsewhere as the unrelated package-contribution kind (`own` / `extend`). - -- **spec**: `ObjectSchema` now declares `ownership: z.enum(['user','org','none']).optional()`. - Authoring the record-ownership opt-out validates cleanly; the registry reads it - off the typed schema (no `as any`). A retired `ownership: 'own'` / `'extend'` - value fails with guidance pointing at the record-ownership model and noting that - `own`/`extend` is the contribution kind (`registerObject`), not an object-schema value. -- **cli**: the `object` scaffold no longer emits the now-invalid `ownership: 'own'` - (owner injection is the default), and `objectstack info` labels the record model - with the correct `user` default. - -No runtime behavior change: `applySystemFields` and its `owner_id` injection logic -are unchanged — this makes the property the engine already honors legally authorable -and consistently typed. diff --git a/.changeset/perf-timing-admin-detail.md b/.changeset/perf-timing-admin-detail.md deleted file mode 100644 index c5822ad5de..0000000000 --- a/.changeset/perf-timing-admin-detail.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -"@objectstack/observability": minor -"@objectstack/plugin-hono-server": minor -"@objectstack/driver-sql": minor ---- - -feat(observability): admin-only richer per-request timing detail via `X-OS-Debug-Timing: json` (#2408) - -Completes the optional "richer JSON" diagnostic from #2408. In addition to the -basic `Server-Timing` header, an admin/service caller can now request a -per-query breakdown — the slowest SQL statements and a query count — by sending -`X-OS-Debug-Timing: json`. The detail is returned in a separate -`X-OS-Debug-Timing-Detail` response header (compact JSON) and is **admin-only, -even under global mode**: an ordinary caller never sees SQL shapes. - -- **observability**: `PerfTiming` gains opt-in per-event detail capture - (`enableDetail` / `recordDetail` / `details`) plus the ambient - `recordServerTimingDetail`. The disclosure gate gains a `privileged` level - (set by `allowPerfDisclosure`, read via `isPerfDisclosurePrivileged`) so the - richer detail can be gated independently of the basic header. -- **driver-sql**: when detail capture is on, the query listener additionally - records each query's **parametrized** statement (knex's `q.sql`, `?` - placeholders) — never the bindings, so no literal row value ever enters the - collector. Zero overhead when detail is off. -- **plugin-hono-server**: `X-OS-Debug-Timing: json` enables detail capture; the - middleware emits `X-OS-Debug-Timing-Detail` (slowest queries, capped and - sanitized to header-safe ASCII) only when the principal is a proven admin. - -Basic and global behavior are unchanged; `json` is purely additive. diff --git a/.changeset/perf-timing-per-request-gating.md b/.changeset/perf-timing-per-request-gating.md deleted file mode 100644 index 946787a947..0000000000 --- a/.changeset/perf-timing-per-request-gating.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -"@objectstack/observability": minor -"@objectstack/plugin-hono-server": minor -"@objectstack/runtime": minor ---- - -feat(observability): admin-gated per-request `Server-Timing` via `X-OS-Debug-Timing` (#2408) - -Perf-tuning mode was previously global-only (`serverTiming` option / -`OS_SERVER_TIMING`), which discloses internal phase durations — a mild -backend-fingerprinting surface — to every caller. This adds the per-request -gating path from the design so an operator can pull a single request's -`Server-Timing` breakdown on a live environment without turning the header on -for everyone. - -- **observability**: a request-scoped disclosure gate (`runWithPerfDisclosure`, - `allowPerfDisclosure`, `isPerfDisclosureAllowed`, `PerfDisclosureGate`) kept - separate from the pure `PerfTiming` collector and pinned to its own - `Symbol.for` store so the middleware and dispatcher share it across module - copies. -- **plugin-hono-server**: the Server-Timing middleware is registered by default - (unless `serverTiming: false`). It runs the collector when timing is global - **or** the request sends `X-OS-Debug-Timing: 1`, and emits the header only - when the gate is open. `OS_PERF_TIMING=1` now also enables global mode. -- **runtime**: after resolving the execution context, the dispatcher opens the - gate for admin/service/system principals, so ordinary callers never receive - the header even if they send the debug header. - -Existing global-mode behavior is unchanged. diff --git a/.changeset/pre.json b/.changeset/pre.json deleted file mode 100644 index 675e9c4096..0000000000 --- a/.changeset/pre.json +++ /dev/null @@ -1,188 +0,0 @@ -{ - "mode": "exit", - "tag": "rc", - "initialVersions": { - "@objectstack/docs": "4.2.1", - "@objectstack/example-crm": "4.0.89", - "@objectstack/example-showcase": "0.3.11", - "@objectstack/example-todo": "4.0.89", - "@objectstack/example-embed-objectql": "0.0.29", - "@objectstack/hono": "15.1.1", - "@objectstack/account": "15.1.1", - "@objectstack/setup": "15.1.1", - "@objectstack/studio": "15.1.1", - "@objectstack/cli": "15.1.1", - "@objectstack/client": "15.1.1", - "@objectstack/client-react": "15.1.1", - "@objectstack/cloud-connection": "15.1.1", - "@objectstack/connector-mcp": "15.1.1", - "@objectstack/connector-openapi": "15.1.1", - "@objectstack/connector-rest": "15.1.1", - "@objectstack/connector-slack": "15.1.1", - "@objectstack/console": "15.1.1", - "@objectstack/core": "15.1.1", - "create-objectstack": "15.1.1", - "@objectstack/formula": "15.1.1", - "@objectstack/lint": "15.1.1", - "@objectstack/mcp": "15.1.1", - "@objectstack/metadata": "15.1.1", - "@objectstack/metadata-core": "15.1.1", - "@objectstack/metadata-fs": "15.1.1", - "@objectstack/metadata-protocol": "15.1.1", - "@objectstack/objectql": "15.1.1", - "@objectstack/observability": "15.1.1", - "@objectstack/platform-objects": "15.1.1", - "@objectstack/driver-memory": "15.1.1", - "@objectstack/driver-mongodb": "15.1.1", - "@objectstack/driver-sql": "15.1.1", - "@objectstack/driver-sqlite-wasm": "15.1.1", - "@objectstack/embedder-openai": "15.1.1", - "@objectstack/knowledge-memory": "15.1.1", - "@objectstack/knowledge-ragflow": "15.1.1", - "@objectstack/plugin-approvals": "15.1.1", - "@objectstack/plugin-audit": "15.1.1", - "@objectstack/plugin-auth": "15.1.1", - "@objectstack/plugin-dev": "15.1.1", - "@objectstack/plugin-email": "15.1.1", - "@objectstack/plugin-hono-server": "15.1.1", - "@objectstack/plugin-pinyin-search": "15.1.1", - "@objectstack/plugin-reports": "15.1.1", - "@objectstack/plugin-security": "15.1.1", - "@objectstack/plugin-sharing": "15.1.1", - "@objectstack/plugin-webhooks": "15.1.1", - "@objectstack/dogfood": "0.0.37", - "@objectstack/downstream-contract": "0.0.35", - "@objectstack/http-conformance": "0.0.3", - "@objectstack/rest": "15.1.1", - "@objectstack/runtime": "15.1.1", - "@objectstack/sdui-parser": "15.1.1", - "@objectstack/service-analytics": "15.1.1", - "@objectstack/service-automation": "15.1.1", - "@objectstack/service-cache": "15.1.1", - "@objectstack/service-cluster": "15.1.1", - "@objectstack/service-cluster-redis": "15.1.1", - "@objectstack/service-datasource": "15.1.1", - "@objectstack/service-i18n": "15.1.1", - "@objectstack/service-job": "15.1.1", - "@objectstack/service-knowledge": "15.1.1", - "@objectstack/service-messaging": "15.1.1", - "@objectstack/service-package": "15.1.1", - "@objectstack/service-queue": "15.1.1", - "@objectstack/service-realtime": "15.1.1", - "@objectstack/service-settings": "15.1.1", - "@objectstack/service-sms": "15.1.1", - "@objectstack/service-storage": "15.1.1", - "@objectstack/spec": "15.1.1", - "@objectstack/trigger-api": "15.1.1", - "@objectstack/trigger-record-change": "15.1.1", - "@objectstack/trigger-schedule": "15.1.1", - "@objectstack/types": "15.1.1", - "@objectstack/verify": "15.1.1", - "objectstack-vscode": "15.1.1" - }, - "changesets": [ - "action-body-org-identifier", - "action-param-widget-config", - "action-result-dialog-i18n", - "adr-0097-openapi-upstream-classify", - "adr-0099-p0-equivalence-gate", - "adr-0099-p1-layer0-reads-rung", - "adr-0099-p2prime-two-axis-cells", - "adr-0103-managedby-write-policy", - "analytics-drill-raw-totals", - "annotate-schema-only-enums", - "approvals-decision-attachments", - "approvals-viewer-can-act", - "approver-type-org-membership-level", - "automation-schema-resolver-e2e", - "betterauth-adapter-system-writes", - "bulk-update-validation-rules", - "bulk-write-hardening", - "cli-memory-driver-dispatch", - "cli-strip-arg-separator", - "cli-turso-loud-error", - "client-batch-transaction-sdk", - "collapse-hook-event-taxonomy", - "console-0318118e02fd", - "console-2e7d7f0f7ee7", - "console-3b2e4d98d904", - "console-69fa5d163a97", - "console-94d4876df090", - "console-af1b0db96e44", - "console-e164196801bd", - "console-fd45313b4d00", - "create-user-result-dialog-phone", - "cross-object-batch-authz-hardening", - "dashboard-widget-strict-3251", - "date-equality-runtime-fix", - "date-field-equality-guardrail", - "dev-loop-dx-p2", - "discovery-transactional-batch-capability", - "document-validation-governance-crossfield", - "drill-range-datetime-tz", - "driver-sql-sqlite3-peer", - "field-form-summary-subfields", - "fix-explain-ownership-values", - "flow-registration-schema-aware-validation", - "flow-trigger-observability", - "formula-null-guard-floor-date-arith-3306", - "hook-nested-write-timeout", - "i18n-inline-label-default-locale", - "import-users-auto-policy", - "keyvalue-node-config-schemas", - "lint-hide-platform-baseline", - "localize-collab-notifications-and-storage-objects", - "logger-esm-file-destination", - "logger-honors-no-color", - "loop-collection-xexpression", - "managed-deny-registry-drift", - "managedby-engine-owned-bucket", - "map-node-config-schema", - "mcp-dev-connect-hint", - "mcp-stdio-principal-admission", - "mcp-stdio-switch-split-and-dev-connect-hint", - "mcp-validate-expression-tool", - "metadata-list-package-aware-dedup", - "metadata-register-notifies-watchers", - "objectql-checkboxes-option-gating", - "ownership-record-model-field", - "perf-timing-admin-detail", - "perf-timing-per-request-gating", - "publish-drafts-org-scope", - "readonly-static-insert-strip", - "reconcile-system-append-only-api-methods", - "region-aware-history-compaction", - "remove-agent-visibility", - "remove-capability-aliases-3308", - "remove-dead-metadata-props-2377", - "remove-form-surfaced-dead-props-2377", - "remove-session-tenantid-alias", - "report-drill-down-range-filter", - "retire-feed-contracts", - "retire-feed-discovery-surface", - "retire-js-expression-dialect", - "sandbox-cpu-budget", - "sandbox-drop-asyncify", - "sandbox-pump-idle-backoff", - "sandbox-timeout-env-override", - "scaffold-default-connector-executors", - "scaffold-gitignore-survives-publish", - "scaffold-pnpm11-build-approvals", - "scaffold-skill-catalog-boundary", - "seed-replay-lookup-corruption", - "seed-replayer-skipped", - "server-timing-perf-spans-2408", - "service-automation-test-hardening", - "state-machine-initial-states", - "summary-rollup-filter", - "systemfields-drop-owner-key", - "tenant-scope-platform-global-3249", - "tidy-views-guard", - "tier4-type-soundness-warnings", - "time-relative-trigger", - "trim-validation-delete-event", - "trim-webhook-dead-triggers", - "unify-org-identifier-hook-session", - "view-metadata-type-schema-runtime-shapes" - ] -} diff --git a/.changeset/publish-drafts-org-scope.md b/.changeset/publish-drafts-org-scope.md deleted file mode 100644 index be88095c76..0000000000 --- a/.changeset/publish-drafts-org-scope.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -"@objectstack/metadata-protocol": patch ---- - -Publish/discard package drafts in the draft's own org scope, fixing `no_draft` after saving a draft via Studio. - -Studio "Save Draft" (`PUT /meta/:type/:name?mode=draft`) never threads the session's `activeOrganizationId`, so the draft row is written env-wide (`organization_id = NULL`). "Publish" (`POST /packages/:id/publish-drafts`) resolves the active org and passed it to `promoteDraft`, which looked the draft up with a strict `organization_id = ` equality — so it 404'd (`[no_draft] No pending draft exists …`) on the env-wide row it could never match, even though `listDrafts` had already surfaced that draft to the publish CTA (PR #1852's `$or`). `discardPackageDrafts` had the same latent gap. - -`listDrafts` now projects each draft's own `organizationId`, and `publishPackageDrafts` / `discardPackageDrafts` promote / delete each draft in that scope (env-wide stays env-wide, per-org stays per-org). Seed-body capture and the ADR-0067 revert-plan pre-state read are scoped the same way. - -Fixes #3115. diff --git a/.changeset/readonly-static-insert-strip.md b/.changeset/readonly-static-insert-strip.md deleted file mode 100644 index fdd40f79e7..0000000000 --- a/.changeset/readonly-static-insert-strip.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -"@objectstack/metadata-protocol": patch -"@objectstack/spec": patch ---- - -fix(metadata-protocol): strip static `readonly` on INSERT at the data-write ingress (#3043) - -#2948/#3003 made static `readonly: true` fields server-enforced on UPDATE (a -non-system PATCH forging `approval_status: 'approved'` is silently stripped in -the engine), but INSERT was exempt. For approval/status/verdict columns that -exemption was the *shorter* attack: instead of the #3003 draft-then-PATCH move, a -non-system caller could `POST` a record already `approval_status: 'approved'` in -one step — and the UPDATE-only strip never reached it. - -The strip now also runs on INSERT, but at the **external data-write ingress** -(`DataProtocol.createData` / `createManyData` / `batchData` / `cloneData`) rather -than in the engine. That seam is the single point every external programmatic -create funnels through — the REST CRUD route, the GraphQL/MCP dispatcher -(`bridge.create` → `callData` → `createData`), and bulk import — while **trusted -internal writers** (better-auth's adapter, the metadata repository, the seed -loader) call `engine.insert` directly and bypass it. Enforcing at the ingress -protects every caller/agent path at once without stripping the internal writers -that legitimately seed read-only columns on create (identity provisioning, -provenance stamps, event-log cursors) — the blast radius an engine-level insert -strip would have. - -- **Caller-forged only, at the ingress.** The payload here is raw caller input - (the security middleware stamps `owner_id` / `organization_id` later, inside - `engine.insert`), so only keys the caller actually sent are dropped; server - stamps are added afterwards and are unaffected. -- **Re-derives the default.** A stripped field falls back to its declared - `defaultValue` in the engine (a forged `approval_status` becomes `draft`, not - NULL). -- **System-context exempt.** `isSystem` writes still seed read-only columns. -- **Silent** (HTTP 2xx), per-row on batch/import. `readonlyWhen` stays - INSERT-exempt (a conditional lock needs a prior record). -- **Author-defined business objects only.** Platform objects (`managedBy` set, - or the `sys_` namespace) carry their own field-write governance that a silent - strip must not pre-empt — e.g. ADR-0086 REJECTS (403) a forged - `managed_by:'package'` on `sys_permission_set`, and #3004 rejects a forged - `owner_id`; several of those columns are `readonly`, so stripping them here - would swallow the payload the guard is meant to reject. The #3043 threat is app - approval/status fields, never `sys_` — the same boundary `applySystemFields` - uses for ownership. - -Behavior change: a non-system create through the data API (REST / GraphQL / MCP / -import) can no longer seed a `readonly` column from the payload. Flows that -legitimately write read-only columns at creation must run with a system context -(`isSystem`), the same requirement the UPDATE strip already imposes. diff --git a/.changeset/reconcile-system-append-only-api-methods.md b/.changeset/reconcile-system-append-only-api-methods.md deleted file mode 100644 index 3f664ef217..0000000000 --- a/.changeset/reconcile-system-append-only-api-methods.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -"@objectstack/service-realtime": patch -"@objectstack/metadata-core": patch ---- - -fix(identity): close the generic-write apiMethods hole on sys_presence and sys_metadata (#3220) - -Follow-through on #1591/#3213 (better-auth apiMethods reconciliation) for two -non-better-auth managed objects that shipped the same contradiction: their -`enable.apiMethods` advertised generic `create`/`update`/`delete` while their -`managedBy` bucket forbids user-context writes, leaving the generic `/data` -route open to a write the bucket does not permit. - -- `sys_presence` (`managedBy: 'append-only'`) advertised `create`/`update`/`delete` - (update/delete on an append-only object at that) but is written only over the - realtime websocket/in-memory path, never through ObjectQL. Narrowed to - `['get', 'list']`. -- `sys_metadata` (`managedBy: 'system'`) advertised full CRUD but customization - overlays are authored only through the metadata-protocol RPC (engine writes - carry a transaction context, not a user session); neither the framework nor - the Console (objectui) POSTs `/data/sys_metadata`. Narrowed to `['get', 'list']`. - -Reads stay open. The metadata-protocol / realtime write paths are engine-level -and bypass the HTTP exposure gate, so they are unaffected — verified by the -metadata-authoring dogfood and the objectql overlay tests. - -A blast-radius audit confirmed the broader `system`/`append-only` buckets are NOT -safe to guard wholesale: several `system` objects (`sys_user_position`, -`sys_user_permission_set`, `sys_position_permission_set`, `sys_user_preference`, -`sys_import_job`) are legitimately user-writable by design (delegated -administration, user preferences, imports). Generalizing the engine write guard -to those buckets is intentionally NOT done here — see #3220 for the bucket-taxonomy -root cause. diff --git a/.changeset/region-aware-history-compaction.md b/.changeset/region-aware-history-compaction.md deleted file mode 100644 index 367b7e0c80..0000000000 --- a/.changeset/region-aware-history-compaction.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -'@objectstack/service-automation': minor ---- - -fix(automation): region-aware run-history compaction keeps loop containers + early failures (#3234) - -`compactStepsForHistory` bounded a terminal run's persisted step log to the last -`MAX_PERSISTED_HISTORY_STEPS` entries with a plain tail-slice. With the ADR-0031 -structured-region step logs (#1505) a single `loop` can emit -`iterations × body-steps` entries, so the tail-slice dropped the -`loop`/`parallel`/`try_catch` **container** step (it precedes all its body steps) -and every early iteration — leaving `getRun`/`listRuns` (after a process restart -or ring-buffer eviction) with body steps the Runs surface could no longer nest, -and silently hiding an early failure. - -Compaction is now region-aware (new exported `compactStepLogForHistory`): over -budget it keeps the run's structural backbone — every top-level step (including -the region container steps) and every failure, each pulled in with its ancestor -container chain — plus the most recent body steps, order-preserving and -hard-capped at `max` so `steps_json` stays bounded (#2585). Every retained body -step keeps its enclosing container(s), so the compacted log never contains an -orphan and the observability surface's per-iteration / per-region nesting still -reconstructs. diff --git a/.changeset/remove-agent-visibility.md b/.changeset/remove-agent-visibility.md deleted file mode 100644 index 173ad6ba8b..0000000000 --- a/.changeset/remove-agent-visibility.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -'@objectstack/spec': minor ---- - -refactor(spec): remove unenforced agent `visibility` field (ADR-0056 D8, #1901) - -The agent `visibility` (`global`/`organization`/`private`) field is **removed** -from `AgentSchema`. It was never enforced: the chat-access evaluator excluded it -and the agent list route did not filter by it, so setting `private` never hid an -agent. Per ADR-0049 / ADR-0056 D8 ("design+enforce or remove"), a security-shaped -field with no runtime consumer is a liability — authors who set `private` believe -they've restricted an agent when they have not. - -Unlike `field-encryption` (kept `[EXPERIMENTAL]` — it has a stable schema shape on -a real roadmap), correct `visibility` enforcement is undesigned: it needs -owner/org anchors that do not exist today. `agent.tenantId` was already removed -(#2377), agents carry no owner field, and the `EXTERNAL` posture rung is defined -but never derived — so `organization` vs `global` is runtime-indistinguishable. -The semantics, not just the plumbing, are unresolved, so the field is dropped -rather than carried marked. - -- `AgentSchema` is not `.strict()`, so existing metadata that still sets - `visibility` parses cleanly — the unknown key is stripped, not rejected. -- Use `access` / `permissions` to restrict who can use an agent — both **enforced** - at the chat route (#1884). -- Re-introduce `visibility` when the agent listing surface gains real owner/org - semantics; tracked in #1901. - -Also updated: authoring form (`agent.form.ts`), liveness ledger -(`liveness/agent.json`), the ADR-0056 D10 authz-conformance matrix (moved from -`experimental` to `removed`), and the generated schema reference docs. diff --git a/.changeset/remove-capability-aliases-3308.md b/.changeset/remove-capability-aliases-3308.md deleted file mode 100644 index 690bf6d9f1..0000000000 --- a/.changeset/remove-capability-aliases-3308.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -"@objectstack/spec": minor -"@objectstack/cli": minor ---- - -feat(spec)!: remove deprecated `aiStudio`/`aiSeat` capability aliases (#3308) - -**BREAKING** (shipped as minor per the launch-window convention). The one-cycle -deprecation window from #3265 is over: the legacy camelCase `requires` spellings -`aiStudio`/`aiSeat` are no longer canonicalized to `ai-studio`/`ai-seat` — they -are now plain unknown tokens, rejected by `defineStack` like any other typo. - -- Removed exports `DEPRECATED_PLATFORM_CAPABILITY_ALIASES` and - `canonicalizePlatformCapability` from `@objectstack/spec`; `isKnownPlatformCapability` - no longer canonicalizes. -- `defineStack` no longer rewrites aliases (the `canonicalizeStackRequires` pass - is gone); the serve resolver no longer canonicalizes raw-artifact `requires`. - -Migration: use the canonical kebab-case tokens `ai-studio` / `ai-seat`. All -first-party configs were migrated in #862/#863; only stacks still carrying the -legacy spelling are affected. Cloud's `objectos-runtime` (pinned to an older -framework) follows on its next `.framework-sha` bump. diff --git a/.changeset/remove-dead-metadata-props-2377.md b/.changeset/remove-dead-metadata-props-2377.md deleted file mode 100644 index 24fb4240ce..0000000000 --- a/.changeset/remove-dead-metadata-props-2377.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -"@objectstack/spec": minor ---- - -feat(spec)!: remove dead author-facing metadata properties (#2377, ADR-0049 enforce-or-remove) - -Breaking spec-surface removal, versioned as `minor` per the launch-window changeset -policy (a `major` would promote the whole fixed-group monorepo; breaking cleanups ride -the minor line, as with #2402 → 11.1.0). - -Removes a batch of spec properties that parsed but had **no runtime consumer** — -authoring them was a false affordance (especially dangerous for AI-authored -metadata). Verified dead against the liveness ledger (`packages/spec/liveness/*.json`) -and a repo-wide grep of readers. This is the follow-up slice to #2402. - -## Removed (each was `dead` + no reader anywhere) - -- **field** (`field.zod.ts`): `vectorConfig` (+ `VectorConfigSchema` + types), - `fileAttachmentConfig` (+ `FileAttachmentConfigSchema` + types), `dependencies`. - Vector fields keep the live flat `dimensions` prop; file/image fields keep the - live flat `multiple`/`accept`/`maxSize` siblings. -- **object** (`object.zod.ts`): `versioning` (+ `VersioningConfigSchema`), - `softDelete` (+ `SoftDeleteConfigSchema`), `search` (+ `SearchConfigSchema`), - `recordName`, `keyPrefix`. Each is now a **rejecting tombstone** in - `UNKNOWN_KEY_GUIDANCE` carrying the upgrade prescription. -- **action** (`action.zod.ts`): `timeout` (server uses `body.timeoutMs`; no - action-level timeout is enforced). -- **agent** (`agent.zod.ts`): `planning.strategy`, `planning.allowReplan` - (only `planning.maxIterations` is read by the runtime). -- **dataset** (`dataset.zod.ts`): `measures.certified` (declared-but-unenforced - governance flag — never compiled into the Cube). - -Liveness ledgers, the ledger README table, and `api-surface.json` are updated; -the removed sub-schema keys are dropped from `json-schema.manifest.json`. - -## Migration - -- **field/agent/dataset/action props**: authoring them is now silently stripped - (they never did anything). Remove them. Vector → set flat `dimensions`; - file/image → set flat `multiple`/`accept`/`maxSize`. -- **object props**: `ObjectSchema.create()` now throws a located error naming the - replacement — `versioning`/`softDelete` → hard deletes + `Field.trackHistory` / - `lifecycle`; `search` → `searchableFields`; `recordName` → an `autonumber` - `Field` designated as `nameField`; `keyPrefix` → remove (never had an effect). - -## Deliberately NOT removed (dead, but entangled — a scoped follow-up) - -`field.index`/`columnName`/`referenceFilters` and object -`tags`/`active`/`isSystem`/`abstract`/`enable.searchable`/`enable.trash`/`enable.mru` -and `agent.tenantId` are surfaced in the Studio metadata-authoring forms -(`*.form.ts`) — removing them cascades into i18n bundle regeneration, so they are -deferred. `action.type:'form'` has a dedicated build-time lint (`lint-view-refs.ts`) -and a first-party showcase usage, so it needs a UX decision. `field.columnName` -additionally has an ADR-0062 D7 lint. These stay `dead` + `authorWarn` in the -ledgers. diff --git a/.changeset/remove-form-surfaced-dead-props-2377.md b/.changeset/remove-form-surfaced-dead-props-2377.md deleted file mode 100644 index 45e85bb380..0000000000 --- a/.changeset/remove-form-surfaced-dead-props-2377.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -"@objectstack/spec": minor ---- - -feat(spec)!: remove form-surfaced dead metadata props + correct 3 misclassified-live entries (#2377, ADR-0049) - -The next enforce-or-remove slice of #2377. Versioned `minor` per the launch-window -policy (the fixed group makes a `major` promote the whole monorepo). - -## Removed (dead, no runtime reader — verified in both framework and objectui) - -- **field**: `columnName`, `index`, `referenceFilters`. This empties the field - dead-prop set. `columnName` also removed its now-moot **ADR-0062 D7** lint - (`validate-expressions.ts`), the dead `StorageNameMapping.resolveColumnName` / - `buildColumnMap` / `buildReverseColumnMap` helpers, and closes ADR-0062 R10 — - external physical-column mapping is `external.columnMap` only. -- **object**: `tags`, `active`, `abstract` — now rejecting tombstones in - `UNKNOWN_KEY_GUIDANCE`. -- **agent**: `tenantId`. - -The removed props are dropped from the authoring forms (`field/object/agent.form.ts`) -and the regenerated metadata-forms i18n bundles. - -## Corrected to `live` (the ledger was wrong — readers existed) - -- **object `isSystem`** — `plugin-sharing` `effectiveSharingModel` defaults a - no-`sharingModel` `isSystem` object to public; also read by the security-posture - lint. KEPT. -- **object `enable.searchable`** — `metadata-protocol` global search (`searchAll`) - uses `enable.searchable === false` as an opt-out. KEPT. -- **action `type:'form'`** — objectui `ActionRunner.executeForm` routes it to the - FormView at `/forms/:target`; a build-time lint validates the target. KEPT. - -## Deliberately deferred - -`object.enable.trash` / `enable.mru` — dead, but inert `default(true)` flags set by -~35 `sys-*.object.ts` files; removing them is high-churn / low-value. Left `dead` -(authorWarn-skipped). - -## Migration - -- field/agent props: authoring them was already a no-op; they now strip silently. - `columnName` → the physical column is always the field key (rename the field, or - use `external.columnMap` for external objects); `index` → declare it in object - `indexes[]`; `referenceFilters` → `lookupFilters`. -- object `tags`/`active`/`abstract`: `ObjectSchema.create()` now throws a located - error naming the removal. None gated anything at runtime — remove them. diff --git a/.changeset/remove-session-tenantid-alias.md b/.changeset/remove-session-tenantid-alias.md deleted file mode 100644 index f5e9ffe79d..0000000000 --- a/.changeset/remove-session-tenantid-alias.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -"@objectstack/spec": major -"@objectstack/objectql": major -"@objectstack/runtime": major -"@objectstack/trigger-record-change": patch ---- - -**BREAKING: remove the deprecated `ctx.session.tenantId` / `ctx.user.tenantId` alias from the hook & action authoring surface — converge on `organizationId` (#3290).** - -#3280 made `organizationId` the blessed developer-facing name for the caller's active org across the JS authoring surface and kept `tenantId` as a `@deprecated` alias carrying the identical value. That alias is now **removed** from the hook `ctx.session`, the action-body `ctx.session`, and the action-body `ctx.user`. Read the caller's active org under the single blessed name: - -```diff -- const org = ctx.session.tenantId; // hook or action body -+ const org = ctx.user?.organizationId ?? ctx.session?.organizationId; -``` - -**FROM → TO migration** (in any `*.hook.ts` / `*.action.ts` body): - -- `ctx.session.tenantId` → `ctx.session.organizationId` -- `ctx.user.tenantId` (action body) → `ctx.user.organizationId` - -The value is unchanged — `organizationId` is the same active-org id, matching the `organization_id` column and `current_user.organizationId` in RLS/sharing. `ctx.user` is `undefined` for system / unauthenticated writes, so read `ctx.session?.organizationId` when a hook or action must work regardless of a resolved user. - -What changed internally: - -- **`@objectstack/spec`** — `HookContextSchema.session` drops the `tenantId` field (only `organizationId` remains). A stray `tenantId` on a constructed session is now stripped by the schema. -- **`@objectstack/objectql`** — the engine's `buildSession()` no longer emits `session.tenantId`; the audit-stamp plugin sources the `tenant_id` column from `session.organizationId`. -- **`@objectstack/runtime`** — `buildActionSession()` and the REST action `ctx.user` no longer emit `tenantId`. -- **`@objectstack/trigger-record-change`** — reads `session.organizationId` (was `session.tenantId`) when forwarding the writer's org to a `runAs:'user'` flow; behavior is identical. - -**Explicit non-goal (unchanged):** the generic **driver-layer** tenancy abstraction is *not* touched — `ExecutionContext.tenantId`, `DriverOptions.tenantId`, `SqlDriver.applyTenantScope` / `TenancyConfig.tenantField`, and `ExecutionLog.tenantId`. That isolation column is configurable and legitimately carries an *environment* id in database-per-tenant kernels; it is a distinct axis from the developer-facing org. The build-time `check:org-identifier` guard now also covers `packages/**` to keep reference bodies off the removed name. diff --git a/.changeset/report-drill-down-range-filter.md b/.changeset/report-drill-down-range-filter.md deleted file mode 100644 index 0de1b7e12a..0000000000 --- a/.changeset/report-drill-down-range-filter.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -'@objectstack/core': minor -'@objectstack/service-analytics': minor ---- - -feat(analytics): emit a half-open date-range drill scope for granularity-bucketed date dimensions (#1752) - -A report/dashboard cell grouped by a `dateGranularity` date dimension ("2026-Q2") -covers a SPAN of records, so drilling it needs a range (`>= start AND < nextStart`), -which the equality drill contract (`drillRawRows`) can't express — date dims were -therefore excluded from drill metadata and a drill landed on an unscoped superset. - -- **`@objectstack/core`** adds `bucketKeyToCalendarRange(key, granularity)`, the - inverse of `bucketDateValue`: it turns a canonical bucket key into its half-open - `[start, end)` calendar span (`YYYY-MM-DD`, `end` exclusive). Pure, timezone-naive - calendar arithmetic; returns `null` for unbucketable / out-of-range keys so the - caller falls back to an unscoped (superset) drill rather than emit a wrong bound. -- **`@objectstack/service-analytics`** emits a `drillRanges` sidecar (aligned to - `rows` by index — the range companion to `drillRawRows`) for `date` + - `dateGranularity` dimensions, computed from the canonical bucket key in the - pre-label-resolution snapshot pass. A `datetime` field under a non-UTC reference - timezone is omitted (host drills a superset) until instant-boundary support - lands; a tz-naive `date` field is exact under any timezone (ADR-0053). - -Consumed by objectui's report drill-through to scope the drilled record list to the -clicked time bucket. diff --git a/.changeset/retire-feed-contracts.md b/.changeset/retire-feed-contracts.md deleted file mode 100644 index 91021af2ec..0000000000 --- a/.changeset/retire-feed-contracts.md +++ /dev/null @@ -1,53 +0,0 @@ ---- -"@objectstack/spec": minor -"@objectstack/metadata-protocol": minor -"@objectstack/client": minor -"@objectstack/objectql": patch ---- - -**Breaking (npm type surface): retire the vestigial feed contracts + protocol surface (ADR-0052 §5 follow-up, #1959).** - -The `service-feed` runtime was deleted in #1955; `sys_comment` / `sys_activity` -are the canonical record-collaboration/timeline backend. This removes the dead -type surface that still pointed at the deleted runtime — every removed method was -already unreachable (the feed REST route was never mounted → 404; the protocol -implementation was never wired with a feed service, so `requireFeedService()` -could only throw). No behavior changes. - -No authorable metadata key is removed (the `feeds:` object capability flag and -the `RecordActivity` UI component config are unchanged), so `PROTOCOL_MAJOR` -stays 15 and this ships as `minor` rather than a protocol major. - -FROM → TO migration for every removed export: - -- `@objectstack/spec/contracts` — `IFeedService`, `CreateFeedItemInput`, - `UpdateFeedItemInput`, `ListFeedOptions`, `FeedListResult` → **removed, no - replacement**. Comments/activity are plain records: write `sys_comment` / read - `sys_activity` via the data engine or the REST data API. -- `@objectstack/spec/api` — `FeedApiContracts`, `FeedApiErrorCode`, - `FeedProtocol`, and all feed request/response schemas + types (`GetFeed*`, - `CreateFeedItem*`, `UpdateFeedItem*`, `DeleteFeedItem*`, `AddReaction*`, - `RemoveReaction*`, `PinFeedItem*`, `UnpinFeedItem*`, `StarFeedItem*`, - `UnstarFeedItem*`, `SearchFeed*`, `GetChangelog*`, `ChangelogEntry`, - `SubscribeRequest/Response`, `FeedUnsubscribeRequest`, `UnsubscribeResponse`, - `FeedPathParams`, `FeedItemPathParams`, `FeedListFilterType`) → **removed**. Use - the data API against `sys_comment` / `sys_activity` (`/api/v1/data/sys_comment/…`); - reactions and threaded replies are fields on `sys_comment`. -- `@objectstack/spec/data` — `FeedItemSchema`/`FeedItem`, `FeedActorSchema`/`FeedActor`, - `MentionSchema`/`Mention`, `ReactionSchema`/`Reaction`, - `FieldChangeEntrySchema`/`FieldChangeEntry`, `FeedVisibility`, - `RecordSubscriptionSchema`/`RecordSubscription`, `SubscriptionEventType`, and the - `data`-namespace `NotificationChannel` → **removed**. `FeedItemType` and - `FeedFilterMode` are **kept** (live UI activity-timeline config). For notification - channels use `NotificationChannelSchema` from `@objectstack/spec/system`. -- `@objectstack/client` — `client.feed.*` (`list` / `create` / `update` / `delete` / - `addReaction` / `removeReaction` / `pin` / `unpin` / `star` / `unstar` / `search` / - `getChangelog` / `subscribe` / `unsubscribe`) and the re-exported feed response - types → **removed**. One-line fix: use `client.data.*` on `sys_comment` / - `sys_activity`, e.g. `client.data.create('sys_comment', { object, record_id, body })` - and `client.data.find('sys_activity', { filters: [['record_id', '=', id]] })`. -- `@objectstack/metadata-protocol` — `ObjectStackProtocolImplementation` no longer - implements the 14 feed methods; its constructor - `(engine, getServicesRegistry?, getFeedService?, environmentId?)` becomes - `(engine, getServicesRegistry?, environmentId?)`. One-line fix: delete the third - argument. diff --git a/.changeset/retire-feed-discovery-surface.md b/.changeset/retire-feed-discovery-surface.md deleted file mode 100644 index bdad54d6fe..0000000000 --- a/.changeset/retire-feed-discovery-surface.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -"@objectstack/spec": minor -"@objectstack/metadata-protocol": patch -"@objectstack/client": patch ---- - -**Breaking (discovery response shape): retire the residual feed capability surface (#3180, follow-up to #1959 / ADR-0052 §5).** - -The feed backend was retired long ago; #1959 removed the feed contracts + SDK. This -removes the last discovery/dispatcher references to it, and fixes a real bug where the -`comments` capability was permanently `false`. - -- `@objectstack/spec` — `WellKnownCapabilitiesSchema.feed` and `ApiRoutesSchema.feed` - (`routes.feed`) are **removed**, and the `/api/v1/feed` entry is dropped from - `DEFAULT_DISPATCHER_ROUTES`. FROM → TO: clients reading `discovery.capabilities.feed` - or `discovery.routes.feed` → use `discovery.capabilities.comments`; comments/activity - are served by the generic data API on `sys_comment` / `sys_activity` - (`/api/v1/data/sys_comment/…`). -- `@objectstack/metadata-protocol` — `getDiscovery()` no longer emits the always-`false` - `feed` service/capability. **Bug fix:** the `comments` capability previously keyed off - the deleted `'feed'` service (so it was permanently `false` after #1955); it now tracks - the presence of the `sys_comment` object (provided by the always-on audit slate), so - `declared === enforced`. -- `@objectstack/client` — the internal `feed: '/api/v1/feed'` route constant is removed - (it only existed to satisfy the now-removed `ApiRoutes.feed` type; no client code used it). diff --git a/.changeset/retire-js-expression-dialect.md b/.changeset/retire-js-expression-dialect.md deleted file mode 100644 index bb296e5d9c..0000000000 --- a/.changeset/retire-js-expression-dialect.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -'@objectstack/spec': patch -'@objectstack/formula': patch ---- - -fix(formula): retire the `js` expression dialect and fix the `hasDialect` false-positive (#3278) - -The `js` **expression** dialect was declared in `ExpressionDialect` but never -shipped — it existed only as a registry stub with no engine and no author helper -(`cel`/`F`/`P` → CEL, `tmpl` → template, `cron` → cron; nothing ever emitted -`js`). Per ADR-0049 (enforce-or-remove) it is removed from the enum; the set is -now `{cel, cron, template}`. - -Procedural JavaScript is unaffected: it remains the **L2** authoring surface — -the sandboxed, capability-gated `ScriptBody { language: 'js' }` in hook/action -bodies — which is a separate enum (`hook-body.zod.ts`), not an expression -dialect. - -Also fixes a latent bug in `hasDialect`: it detected stubs via -`dialect.startsWith('stub:')`, but stubs were registered under their real name, -so the check was dead code and `hasDialect('js')` returned a false-positive -`true`. With the stub removed, `hasDialect` reports only registered real -engines, and the registry test now asserts the negative case (`hasDialect('js') -=== false`) so the gate can actually go red. - -No runtime behavior changes for any valid persisted artifact — no producer ever -emitted `dialect: 'js'`. See the ADR-0058 addendum. diff --git a/.changeset/sandbox-cpu-budget.md b/.changeset/sandbox-cpu-budget.md deleted file mode 100644 index 86c8ec0fb9..0000000000 --- a/.changeset/sandbox-cpu-budget.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -'@objectstack/runtime': minor -'@objectstack/types': minor ---- - -feat(runtime): sandbox budget is script CPU-time, not wall clock (ADR-0102 D1, #3295) - -The QuickJS sandbox now meters each hook/action invocation against how much -**VM-active (CPU) time** the body burns, not wall clock. Idle host-await time and -a nested hook's own execution (which runs host-side while the caller's VM is -parked) are no longer charged to the caller — so a slow/loaded host or a deep -nested-write chain can't trip the budget while a script is merely waiting (the -root cause of the #3259 CI flake). A separate, generous **wall-clock ceiling** -(default 30s, `max(ceiling, cpuBudget)`) remains as the backstop for a body stuck -on a host call that never settles. - -What changes for consumers (behaviour, not API signatures): - -- **Meaning of the timeout knobs.** `body.timeoutMs`, the `hookTimeoutMs` / - `actionTimeoutMs` runner options, and `OS_SANDBOX_HOOK_TIMEOUT_MS` / - `OS_SANDBOX_ACTION_TIMEOUT_MS` keep their **names, defaults (250ms / 5000ms), - and precedence** — but now bound CPU-time instead of wall-clock. In practice - this only *loosens* legitimate slow/nested work; a runaway synchronous script - is still cut at the same budget. -- **Error messages.** `exceeded timeout of Nms` → either `exceeded CPU budget of - Nms` (script burned its CPU budget) or `exceeded wall-clock ceiling of Nms - while awaiting host calls` (stuck on a never-settling host call). Update any - code/tests matching the old string. - -New knobs (additive): - -- `QuickJSScriptRunner` option `wallCeilingMs` and env `OS_SANDBOX_WALL_CEILING_MS` - — tune the wall ceiling (explicit option › env › 30s). -- `resolveSandboxTimeoutMs` (`@objectstack/types`) gains a `'wallCeiling'` kind. - -Also fixes a latent init bug in the new accounting where the interrupt handler -could fire during `installCtx` and corrupt ctx marshalling. The nested-write -integration suites now run at the stock 250ms budget (previously forced to 10s), -which is itself the regression guard for the nested-charging fix. diff --git a/.changeset/sandbox-drop-asyncify.md b/.changeset/sandbox-drop-asyncify.md deleted file mode 100644 index eb566bd444..0000000000 --- a/.changeset/sandbox-drop-asyncify.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -'@objectstack/runtime': patch ---- - -perf(runtime): drop asyncify — sandbox runs on the sync QuickJS variant (ADR-0102 D2, #3296) - -Phase 2 of #3275. The QuickJS sandbox switches from the asyncify build -(`newAsyncContext`) to the already-installed sync release variant -(`newQuickJSWASMModule().newContext()`), keeping one physically isolated WASM -module per invocation (ADR-0102 D2/D4). Asyncify's only justification — suspending -the WASM stack across a host call — disappeared with the #1867 deferred-promise + -pump redesign, so nothing depended on it. Wins: smaller binary, faster -compile/instantiate, faster per-instruction, and removal of an entire class of -suspended-stack failure modes. - -Also fixes a latent resource leak surfaced by the stricter sync teardown: host -`ctx.api` calls hand the VM a `vm.newPromise()` deferred that was never -`dispose()`d (the newPromise contract requires it). The asyncify build tolerated -the leak; the sync build's `JS_FreeRuntime` aborted (`Assertion failed: -list_empty`) when a context was torn down with a pending, never-settled host call -(the timeout path). Deferreds are now tracked and disposed before context -teardown. - -Memory: the sync `QuickJSWASMModule` has no `dispose()`; its WebAssembly instance -+ linear memory are GC-reclaimed when the reference is dropped. A new RSS soak -test guards that per-invocation modules don't ratchet RSS. diff --git a/.changeset/sandbox-pump-idle-backoff.md b/.changeset/sandbox-pump-idle-backoff.md deleted file mode 100644 index 459f597466..0000000000 --- a/.changeset/sandbox-pump-idle-backoff.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -'@objectstack/runtime': patch ---- - -perf(runtime): stop the sandbox pump loop from idle-spinning while awaiting a host call (#3233) - -The QuickJS hook/action runner drives a script's async continuations with a -pump loop that, on every iteration, yielded via `setImmediate` and then drained -the VM job queue. While the body was only *waiting* on an in-flight host promise -(a slow `ctx.api` read/write, or one call that settles after many event-loop -turns), that queue was empty every iteration, so the loop woke ~200k times/sec -doing nothing — a ~50,000-iteration burn for a 250ms wait. - -The yield is now adaptive: it stays on `setImmediate` (near-zero latency) while -the script is making progress, and once a pump executes zero VM jobs it ramps up -to a small capped `setTimeout` (≤8ms). Any executed job — a settled host call, a -resumed continuation — resets it to the fast path, so sequential host calls and -multi-turn work keep their low latency; only a genuinely idle wait backs off. -Deadline enforcement and every existing pump-budget/timeout/transaction -guarantee are unchanged. diff --git a/.changeset/sandbox-timeout-env-override.md b/.changeset/sandbox-timeout-env-override.md deleted file mode 100644 index 2b49d8b7d1..0000000000 --- a/.changeset/sandbox-timeout-env-override.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -'@objectstack/runtime': minor -'@objectstack/types': minor ---- - -feat(runtime): env-overridable sandbox hook/action timeout default (#3259) - -The QuickJS sandbox enforces a wall-clock deadline on every hook/action -invocation (250ms hooks / 5000ms actions). Each invocation compiles a fresh -WASM module, and a nested hook compiles ANOTHER one inside the parent's budget, -so on a heavily loaded or slow host — an oversubscribed CI runner, constrained -production hardware — that fixed VM-creation cost alone can trip the hook -default even while the VM is still making progress. On CI this surfaced as an -intermittent `hook '…' exceeded timeout of 250ms` flake on PRs that never -touched the sandbox path. - -The per-invocation timeout DEFAULT is now resolvable from the environment via -`resolveSandboxTimeoutMs` (`@objectstack/types`), which `QuickJSScriptRunner` -consults, so an operator can raise the floor once, deployment-wide, instead of -re-tuning every call site: - -- `OS_SANDBOX_HOOK_TIMEOUT_MS` — default hook budget (ms) -- `OS_SANDBOX_ACTION_TIMEOUT_MS` — default action budget (ms) - -Precedence is unchanged: an explicit `hookTimeoutMs` / `actionTimeoutMs` passed -to the runner still wins over the env var, and a body's own declared `timeoutMs` -still wins over the resolved default (the smaller of the explicit values). Only -a positive integer is honored; unset / empty / non-numeric / non-positive keeps -the built-in 250ms / 5000ms defaults, so behaviour is byte-for-byte unchanged -when the vars are absent — production is unaffected unless it opts in. - -CI's Test Core now sets `OS_SANDBOX_HOOK_TIMEOUT_MS=10000` so the shared-runner -load flake can't recur; genuine hangs stay bounded by each test's own timeout. diff --git a/.changeset/scaffold-default-connector-executors.md b/.changeset/scaffold-default-connector-executors.md deleted file mode 100644 index ca3707fd43..0000000000 --- a/.changeset/scaffold-default-connector-executors.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -"create-objectstack": minor ---- - -feat(create-objectstack): the blank scaffold ships the three generic connector executors by default - -`npm create objectstack` now generates an `objectstack.config.ts` that wires the -`rest`, `openapi`, and `mcp` connector executor plugins (ADR-0022/0023/0024 + -ADR-0097) into `plugins:`, alongside `requires: ['automation']`. This closes the -last authoring gap in the ADR-0097 promise that integrations are expressible -**and executable** as pure metadata: an author (human or AI) can now add a -declarative `connectors:` entry naming `provider: 'rest' | 'openapi' | 'mcp'` -and have it materialize into a live, dispatchable connector at boot — with no -host-code edit. - -- `plugins:` — `new ConnectorRestPlugin()`, `new ConnectorOpenApiPlugin()`, - `new ConnectorMcpPlugin()` (zero-arg = contribute the provider factory only). -- `requires: ['automation']` — the automation service performs the - materialization and owns the registry the executors register into. It is also - a hard dependency of the connector plugins, so a scaffold that lists them in - `plugins:` without it fails boot; automation ships transitively via - `@objectstack/cli`. -- deps — `@objectstack/connector-rest`, `@objectstack/connector-openapi`, - `@objectstack/connector-mcp`. -- Security (#3055): declarative `mcp` stdio transports stay denied by default — - opt in per host with `new ConnectorMcpPlugin({ declarativeStdio: ['node'] })`. - -Brand connectors (Slack, …) remain marketplace/opt-in. diff --git a/.changeset/scaffold-gitignore-survives-publish.md b/.changeset/scaffold-gitignore-survives-publish.md deleted file mode 100644 index 67fb4c2b0f..0000000000 --- a/.changeset/scaffold-gitignore-survives-publish.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -"create-objectstack": patch ---- - -Scaffolded projects ship with a `.gitignore` again — `npx create-objectstack` produced none, leaving `node_modules/` and `.env` un-ignored for every new user. - -`npm pack` / `pnpm pack` strip `.gitignore` from a tarball unconditionally, at every depth. The blank template committed one at `src/templates/blank/.gitignore` and the build faithfully copied it to `dist/templates/blank/.gitignore`, but `files: ["dist"]` publishing dropped it on the way to the registry — so the file was present in the repo, present in every local build, and absent from all 11 files of a real scaffold. Verified against the published 15.1.1 tarball, which ships `dist/templates/blank/.dockerignore` and no `.gitignore`. - -The template is now committed as `_gitignore` (a name npm does not strip) and restored to `.gitignore` when the template is copied, via a `TEMPLATE_FILE_ALIASES` map in the new `template-copy.ts`. Only `.gitignore` is aliased: the strip list is `.gitignore` and `.npmrc`, not "every dotfile" — `.dockerignore` packs fine and stays literal. - -The restored ignore rules also cover `.env` / `.env.*`, which they never did. The template README has users write `OS_AUTH_SECRET` and `OS_SECRET_KEY` into a `.env`, and `docker-compose.yml` calls that file "never committed" — but only the prose said so, and `.dockerignore` was the only file that listed it. - -A packing ratchet in `template-consistency.test.ts` guards both halves: it packs the real package, scaffolds from the extracted tarball with the real copy logic, and asserts every template file lands under its intended name. Source-level assertions cannot see this class of bug — the file only vanishes at publish. diff --git a/.changeset/scaffold-pnpm11-build-approvals.md b/.changeset/scaffold-pnpm11-build-approvals.md deleted file mode 100644 index 5472aa39a8..0000000000 --- a/.changeset/scaffold-pnpm11-build-approvals.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -"create-objectstack": patch ---- - -fix(create-objectstack): the blank scaffold declares pnpm build approvals, so a fresh `pnpm install` no longer exits 1 on pnpm 11 - -pnpm 11 turned an unapproved dependency build script from a warning into a hard -error. The blank template declared no build approvals, so the very first command -a new user runs failed on any current pnpm: - -``` -npx create-objectstack myapp && cd myapp && pnpm install -# [ERR_PNPM_IGNORED_BUILDS] Ignored build scripts: better-sqlite3@12.11.1, esbuild@0.28.1 -# exit 1 -``` - -The scaffold now ships a `pnpm-workspace.yaml` approving the two packages it -actually depends on building — `better-sqlite3` (the native sqlite driver behind -`@objectstack/driver-sql`) and `esbuild` (compiles `objectstack.config.ts`). - -Both approval keys are present because pnpm reads them by version, and neither -alone covers the supported range: - -- `allowBuilds` (a package → boolean map) — the only key pnpm 11 honors, and - understood back to pnpm 10.31. `onlyBuiltDependencies` alone still errors. -- `onlyBuiltDependencies` (a list) — pnpm 10.0–10.30, which ignore `allowBuilds`. - -npm and yarn ignore the file, so the npm install path is unaffected. Both -packages ship prebuilt binaries, so this was an install-time hard stop rather -than a runtime defect — the project ran fine once installed. - -This is the #3091 failure class (in-repo settings masking what users resolve) -and was caught by the publish smoke gate added in #3100, which installs the -release candidate the way a user does — on whatever pnpm corepack hands a fresh -machine. diff --git a/.changeset/scaffold-skill-catalog-boundary.md b/.changeset/scaffold-skill-catalog-boundary.md deleted file mode 100644 index 5d4168b7d6..0000000000 --- a/.changeset/scaffold-skill-catalog-boundary.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"create-objectstack": patch ---- - -Stop leaking repo-internal skills into scaffolded projects. The scaffolder (and the docs) advertised `npx skills add objectstack-ai/framework --all`, and the skills CLI's `--all` implies `--skill '*'` — which includes even `metadata.internal` skills — so repo-internal tooling like `.claude/skills/dogfood-verification` landed in every new project's `.agents/skills/`. All install commands are now scoped to the published catalog via the `/skills` subpath (`npx skills add objectstack-ai/framework/skills --all`), the internal skill is additionally marked `metadata.internal: true` to hide it from interactive discovery, and a template-consistency ratchet plus a scaffold-e2e assertion keep the boundary from regressing. diff --git a/.changeset/seed-replay-lookup-corruption.md b/.changeset/seed-replay-lookup-corruption.md deleted file mode 100644 index 230f31fd6b..0000000000 --- a/.changeset/seed-replay-lookup-corruption.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -"@objectstack/metadata-protocol": patch -"@objectstack/runtime": patch ---- - -fix(seed): replaying seeds no longer corrupts lookup natural keys on the upsert update path - -Every dev-server restart replayed package seeds in upsert mode, and any record whose -lookup/master_detail was authored as a natural key could have that reference overwritten -with NULL on the update path (`NOT NULL constraint failed` on required columns; silent -link loss on nullable ones). Four fixes: - -- An unresolved reference now leaves the column untouched (deferred to pass 2) or drops - the record loudly — it is never written as NULL over an existing row. -- DB-side reference resolution probes the target dataset's declared `externalId` (e.g. - `email`) before falling back to `name` and `id`, matching how in-memory resolution - already keyed records. -- A rejected update (e.g. a `state_machine` rule vetoing the replay) no longer severs - natural-key resolution for downstream child datasets. -- Replays are idempotent: an upsert/update whose declared fields already match the - existing row is skipped instead of rewritten (no more `updated_at` churn or lifecycle - re-validation on every boot). diff --git a/.changeset/seed-replayer-skipped.md b/.changeset/seed-replayer-skipped.md deleted file mode 100644 index 2e583096d7..0000000000 --- a/.changeset/seed-replayer-skipped.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -"@objectstack/runtime": patch ---- - -feat(runtime): seed-replayer reports `skipped` so hosts can stamp seed-once on progress - -The `seed-replayer` kernel service returned `{ inserted, updated, errors }` but -not `skipped`. A cloud host therefore could not tell an **all-skip replay** -(the env's seed data is already present — a no-op) apart from the -zero-summary early-returns that never ran the loader (no organization, no -metadata service, no datasets). Both looked like `inserted = updated = 0`, so -the host could not safely stamp its seed-once record for the all-skip case and -re-ran the full remote replay on every cold boot. - -Add `skipped: result.summary.totalSkipped` to the replayer's return; the -early-returns report `skipped: 0`. This lets a host (cloud#853's -`decideSeedStamp`) stamp on progress — including an all-skip replay — while -still declining to stamp a genuine no-loader zero-summary. Additive and -backward compatible; existing consumers ignore the new field. diff --git a/.changeset/server-timing-perf-spans-2408.md b/.changeset/server-timing-perf-spans-2408.md deleted file mode 100644 index a9f86aaf0a..0000000000 --- a/.changeset/server-timing-perf-spans-2408.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -"@objectstack/observability": patch -"@objectstack/driver-sql": patch -"@objectstack/runtime": patch -"@objectstack/plugin-hono-server": patch ---- - -feat(observability): decompose `Server-Timing` into auth / db / hooks / serialize spans (perf-tuning mode) - -The opt-in `Server-Timing` header now breaks a request's server time into the phases that actually explain it, so an operator can open DevTools → Network → Timing and see where the time went without standing up an external tracing backend: - -- **`db`** — total SQL time with a **query count**. The SQL driver wires knex's `query` / `query-response` events (keyed by `__knexQueryUid`) and folds each query into one aggregate member (`db;dur=210;desc="6 queries"`) — the query count is the number most useful for spotting N sequential round-trips. Timing is attributed to the originating request via `AsyncLocalStorage`, so it is correct under concurrency and never cross-attributes. SQL text is never emitted, only durations and a count. -- **`auth`** — identity / session resolution in the dispatcher, the prime suspect for unexplained data-API overhead. -- **`hooks`** — total business-hook execution time with a hook count, fed through the engine's existing `HookMetricsRecorder` seam (wired from the runtime, so `@objectstack/objectql`'s lean `core` tier stays observability-free). -- **`serialize`** — response JSON encoding in the HTTP adapter. - -Adds `countServerTiming(name, dur, unit)` (and `PerfTiming.count`) to fold high-frequency phases into a single aggregate member instead of flooding the header. Every phase is a no-op when perf-tuning is off (`serverTiming: true` / `OS_SERVER_TIMING=true`), so there is zero measurable overhead on the normal path. - -Closes #2408. diff --git a/.changeset/service-automation-test-hardening.md b/.changeset/service-automation-test-hardening.md deleted file mode 100644 index 61cd4ee360..0000000000 --- a/.changeset/service-automation-test-hardening.md +++ /dev/null @@ -1,12 +0,0 @@ ---- ---- - -test(automation): big-loop run-history integration test + fix pre-existing test-file type errors - -Test-only; no runtime/API change (nothing under `src/` behaviour is touched), so -this releases nothing. Adds an end-to-end `run-history.test.ts` case exercising -the region-aware history compaction (#3234) through the real engine -(execute → recordTerminal → restart → getRun) with a >MAX-step loop, and clears -the pre-existing `tsc --noEmit` errors across the package's test files (missing -`maturity`, implicit `any`, `ConnectorProviderFactory` test-double defs, an -unused param, a `{}`-typed output access) so the whole package type-checks clean. diff --git a/.changeset/state-machine-initial-states.md b/.changeset/state-machine-initial-states.md deleted file mode 100644 index 4556cf1ce3..0000000000 --- a/.changeset/state-machine-initial-states.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -"@objectstack/spec": minor -"@objectstack/objectql": minor ---- - -feat(validation): `state_machine.initialStates` enforces the FSM entry point on INSERT (#3165) - -A `state_machine` rule's `transitions` only governs UPDATE — on INSERT the rule -was a no-op, and a `select` field permits ANY declared option as the initial -value. So a record could be born mid-flow (created already `approved`), skipping -the whole state machine. This was the gap #3043's mitigation idea assumed didn't -exist (declared ≠ enforced, ADR-0049). - -`state_machine` rules gain an optional `initialStates: string[]` — the states a -record may be CREATED in. When set, an insert whose (defaulted) state-field value -is outside the list is rejected server-side with `code: 'invalid_initial_state'`. -Omit it to keep the legacy behavior (no initial-state check on insert). A missing -/ empty value is left to required-validation; `transitions` (UPDATE) is -unaffected. Enforced at the same `evaluateValidationRules(..., 'insert')` seam the -engine already runs after field defaults. diff --git a/.changeset/summary-rollup-filter.md b/.changeset/summary-rollup-filter.md deleted file mode 100644 index a97e2b0800..0000000000 --- a/.changeset/summary-rollup-filter.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -'@objectstack/spec': minor -'@objectstack/objectql': minor ---- - -feat(objectql): roll-up `summary` fields can filter which child rows they aggregate (#1868) - -`summaryOperations` gains an optional `filter` — a query `where` FilterCondition -evaluated against each child row, so a summary aggregates only the matching -children instead of the whole collection. This is what lets a single child object -feed several distinct parent totals, which the cross-object rollup templates need: - -```typescript -// One `engagement` child → distinct filtered totals. -total_signups: { - type: 'summary', - summaryOperations: { object: 'engagement', field: 'id', function: 'count', filter: { type: 'signup' } }, -} -// Sum only received receipt lines (3-way match). -received_amount: { - type: 'summary', - summaryOperations: { object: 'procurement_receipt', field: 'amount', function: 'sum', filter: { status: 'received' } }, -} -``` - -The engine ANDs the predicate with the parent-FK match when it recomputes, and -because the whole filtered aggregate is re-run on every child write, a child that -moves in or out of the predicate (e.g. a status change) keeps the parent current -with no extra wiring. Operator and compound forms work too -(`filter: { type: { $in: ['signup', 'trial'] }, amount: { $gte: 100 } }`). - -Purely additive: omitting `filter` aggregates every child exactly as before. diff --git a/.changeset/systemfields-drop-owner-key.md b/.changeset/systemfields-drop-owner-key.md deleted file mode 100644 index 7901cc8f9f..0000000000 --- a/.changeset/systemfields-drop-owner-key.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -"@objectstack/spec": minor ---- - -fix(spec): drop the dead `systemFields.owner` key (#3175 follow-up) - -`ObjectSchema.systemFields` exposed an `owner?: boolean` opt-out key that nothing -read — the registry (`applySystemFields`) only consumes `systemFields.tenant` and -`systemFields.audit`, and `owner_id` provisioning is governed by the object-level -`ownership` property (`'user' | 'org' | 'none'`, made first-class in #3185). The -key was declared but wired to nothing. - -Removed it so the schema only advertises the two opt-outs it actually honors -(`tenant`, `audit`). Backward-compatible at runtime: the key was ignored before and -is stripped now (both no-ops). A TypeScript author who set `systemFields.owner` -will now see an excess-property error — the fix is to delete the key (it never did -anything) or use `ownership: 'org' | 'none'` to skip `owner_id`. Also corrected the -stale `objectql/security` doc that called `audit` "reserved" (it is active). diff --git a/.changeset/tenant-scope-platform-global-3249.md b/.changeset/tenant-scope-platform-global-3249.md deleted file mode 100644 index 2ad5329b59..0000000000 --- a/.changeset/tenant-scope-platform-global-3249.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -'@objectstack/spec': minor -'@objectstack/objectql': patch -'@objectstack/driver-sql': patch ---- - -fix(tenancy): platform-global (`tenancy.enabled:false`) objects are never driver-org-scoped (#3249) - -An org-context read of a platform-global object (e.g. `sys_license`, ADR-0066) -could return 0 rows for an authenticated caller while an anonymous read saw the -data: the engine stamped `execCtx.tenantId` into driver options unconditionally, -and the SQL driver's tenant-field cache could be re-corrupted to -`organization_id` by a partial re-registration (lifecycle archive `syncSchema`, -schema-drift re-sync) whose schema omitted the `tenancy` block. - -- New `isTenancyDisabled(schema)` export from `@objectstack/spec/data` — the - single source of truth for the ADR-0066 platform-global posture, now shared by - the registry (tenant-column injection), the ObjectQL engine, and the SQL - driver. -- `ObjectQL.buildDriverOptions` no longer stamps `tenantId` for objects whose - registered schema declares `tenancy.enabled: false` (an explicitly-passed - options `tenantId` still wins — deliberate caller intent). -- `SqlDriver` (and `SqliteWasmDriver`) now keep a sticky record of an explicit - `tenancy.enabled:false` declaration: a later registration without a `tenancy` - block preserves the opt-out instead of re-scoping via the implicit - `organization_id` heuristic; a registration that carries a `tenancy` - declaration stays authoritative. diff --git a/.changeset/tidy-views-guard.md b/.changeset/tidy-views-guard.md deleted file mode 100644 index 29236abdd2..0000000000 --- a/.changeset/tidy-views-guard.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"@objectstack/spec": patch -"@objectstack/lint": patch -"@objectstack/cli": patch ---- - -Reject view containers that define no views. A flat list-view object (`{ name, label, type, columns, ... }`) parses to an empty `ViewSchema` container because Zod strips unknown keys — zero views register and the Console silently renders nothing. `defineView()` now throws on a zero-view container, and `os validate` gains a `view-container-shape` check (`validateViewContainers` in `@objectstack/lint`) that reports flat or empty `views: []` entries pre-parse with a wrap-it fix hint. diff --git a/.changeset/tier4-type-soundness-warnings.md b/.changeset/tier4-type-soundness-warnings.md deleted file mode 100644 index 628827e21a..0000000000 --- a/.changeset/tier4-type-soundness-warnings.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -"@objectstack/formula": minor -"@objectstack/lint": minor ---- - -feat(formula,lint): advisory type-soundness warnings for formula/predicate expressions (#1928 tier 4) - -Closes the last open guardrail from #1928. A `Field.formula` or record-scoped -predicate that uses a **text or boolean field with an arithmetic (`+ - * / %`) -or ordering (`< > <= >=`) operator against a number** faults the runtime -overload and silently evaluates to `null` (e.g. `record.title * 2`, -`record.is_active + 1`). The build now surfaces this as a **non-blocking -warning** with the offending field and a corrective message. - -Honours the ADR-0032 design law — the checker only flags what the runtime -would also fail: - -- Number / currency / percent / date / datetime fields are declared `dyn`, so - the cases the runtime rescues never warn — `record.amount / 100` (the #1930 - `registerOperator` fix), `record.due == today()` and numeric-string / ISO-date - values (the string-hydration retry), and numeric-coded `select` option values. -- Equality (`==` / `!=`) is excluded: a heterogeneous equality is runtime-safe - (evaluates to `false`), never a fault. - -New `firstTypeMismatch(source, fieldCelTypes, scope)` export in -`@objectstack/formula` (and an optional `fieldTypes` hint on -`validateExpression`); `@objectstack/lint`'s `validateStackExpressions` threads -each object's field types into every checked site: - -- **record-scoped** sites (`record.`) — formula fields, validation rules, - action / hook / sharing predicates; -- **flattened** flow / automation conditions (bare `field`) — where flow - variables stay `dyn` and are never flagged, and equality stays runtime-safe. - -Warnings are advisory in `objectstack build` / `validate` (fatal only under -`--strict`), matching the tier-3 channel. diff --git a/.changeset/time-relative-trigger.md b/.changeset/time-relative-trigger.md deleted file mode 100644 index 6bcfd74d2c..0000000000 --- a/.changeset/time-relative-trigger.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -'@objectstack/trigger-schedule': minor -'@objectstack/service-automation': minor -'@objectstack/spec': minor -'@objectstack/lint': minor -'@objectstack/cli': patch ---- - -feat(triggers): declarative time-relative trigger — daily sweep instead of fragile date-equality (#1874) - -Time-relative business rules ("alert 60 days before a contract's `end_date`") -could only be expressed as a `record_change` flow gated on a date-equality -condition like `end_date == daysFromNow(60)`. That predicate is only evaluated -when the record *happens to change*, so it fires only if a record is edited on -exactly the threshold day — i.e. almost never, unattended. The robust -alternative was a hand-written cron + range query that every author -re-implemented (contracts `renewal_alert`, hr `document_expiring_soon`, -procurement `po_overdue`, …). - -A flow's start node can now declare a `timeRelative` descriptor instead: - -```ts -config: { - timeRelative: { - object: 'contracts', - dateField: 'end_date', - offsetDays: [60, 30, 7], // T-minus reminders — fires on each threshold day - // — or — withinDays: 30 // "expiring soon" range; negative = overdue lookback - filter: { status: 'active' }, // optional, ANDed with the date window - }, - schedule: { type: 'cron', expression: '0 8 * * *' }, // optional; defaults to daily 08:00 UTC -} -``` - -The new `time_relative` trigger (shipped in `@objectstack/trigger-schedule` as -`TimeRelativeTriggerPlugin`) sweeps the object on that schedule and launches the -flow **once per matching record**, with the record on the automation context — -so the start-node `condition` gate and `{record.}` interpolation work -exactly as for a record-change flow. Because the window is evaluated every day, -a threshold is never missed regardless of when the record last changed. The -discovery query runs as a system operation (RLS-bypassing) and is capped -(`maxRecords`, default 1000) so a mis-scoped window can't fan out unboundedly; -per-record failures are isolated so one bad row never aborts the sweep. - -The automation engine routes a start node carrying `config.timeRelative` to the -`time_relative` trigger (ahead of the plain `schedule` trigger, whose behavior is -unchanged), and `os validate` gains readiness checks for the new descriptor -(unknown swept object, ambiguous draft status). New authorable spec key: -`TimeRelativeTriggerSchema` (`@objectstack/spec/automation`). diff --git a/.changeset/trim-validation-delete-event.md b/.changeset/trim-validation-delete-event.md deleted file mode 100644 index 67dca51bcb..0000000000 --- a/.changeset/trim-validation-delete-event.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@objectstack/spec": patch ---- - -Remove the dead `'delete'` member from the validation-rule `events` enum (#3184). The rule evaluator only runs on the insert/update write path — `engine.delete` never invokes it — so a rule declaring `events: ['delete']` was a silent no-op (flagged in #3106 and `docs/audits/2026-06-validationschema-property-liveness.md`). The enum now admits only `insert`/`update`; guard deletions with a `beforeDelete` lifecycle hook instead. No shipped metadata declares `events: ['delete']`; any off-spec metadata that did now fails loudly at `os validate` / registration rather than parsing and doing nothing. Also narrows the two hand-written mirrors (`rule-validator.ts` `BaseRule`, `metadata-protocol` JSON-schema form helper — whose stale `type` enum listing removed `unique`/`async`/`custom` variants is corrected in the same pass), updates the doc comments, the published data skill, and the hand-written validation doc. diff --git a/.changeset/trim-webhook-dead-triggers.md b/.changeset/trim-webhook-dead-triggers.md deleted file mode 100644 index 4c898be4c7..0000000000 --- a/.changeset/trim-webhook-dead-triggers.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -"@objectstack/spec": patch -"@objectstack/plugin-webhooks": patch ---- - -Trim the dead `undelete` and `api` webhook triggers (#3196). `WebhookTriggerType` declared five triggers but only three ever fired: - -- `undelete` had no event source — the engine has no soft-delete/restore capability (`delete` is a hard delete; no `deleted_at` convention, no restore operation, and `data.record.undeleted` is never emitted). The `undeleted` case in the auto-enqueuer's action mapper was dead code awaiting a producer that doesn't exist. -- `api` ("manually triggered") had no fire path — the only webhook HTTP surface re-queues already-failed deliveries; nothing originates a manual fire. - -Both are removed from the enum (contract-first, matching #3184/#3195): authoring a webhook on a removed trigger now fails loudly at `os validate` / registration instead of registering a webhook that silently never fires. No shipped webhook metadata used either. The auto-enqueuer now also warns when a persisted `sys_webhook` row carries a trigger it can't map to an emitted record event (a drift-guard, so a dead trigger can't silently no-op again). Reintroduce `undelete` only alongside a real restore subsystem, and `api` only alongside a real manual-fire endpoint. Updated the `sys_webhook` trigger options, field help (all locales), docs, and reference; added rejection tests. diff --git a/.changeset/unify-org-identifier-hook-session.md b/.changeset/unify-org-identifier-hook-session.md deleted file mode 100644 index 0ee6d0b7e0..0000000000 --- a/.changeset/unify-org-identifier-hook-session.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -"@objectstack/spec": patch -"@objectstack/objectql": patch ---- - -**Unify the developer-facing org identifier in JS hooks — `organizationId` is now the blessed name; `session.tenantId` becomes a deprecated alias (#3280).** The caller's active organization was surfaced to hook authors as `ctx.session.tenantId`, while everything else on the developer surface — the `organization_id` column, `current_user.organizationId` in RLS/sharing, and seed rows — already said `organization`. A hook author had to internalize the hidden equation `tenantId === organizationId` to move between surfaces. This is additive and non-breaking: - -- **`ctx.session.organizationId`** is added as the blessed name; **`ctx.session.tenantId`** still carries the identical value but is marked `@deprecated` in its TSDoc. Both come from the same resolved `ExecutionContext.tenantId` (which the kernel derives from `session.activeOrganizationId`). -- **`ctx.user.organizationId`** is added to the ergonomic `user` shortcut, so a hook that needs "the current org to filter by" writes `ctx.user.organizationId` with zero relearning — matching `current_user.organizationId` (RLS) and the `organization_id` column. The engine now populates `ctx.user` (`{ id, email?, organizationId? }`) at every hook event that already carries a `session`; it stays `undefined` for system / unauthenticated writes. - -**No behavior change and no breaking rename.** The generic driver-layer tenancy abstraction (`ExecutionContext.tenantId`, `DriverOptions.tenantId`, `SqlDriver.applyTenantScope`, `TenancyConfig.tenantField`) is deliberately untouched — that layer's isolation column is configurable and legitimately carries an *environment* id in per-environment (database-per-tenant) kernels. Hook-authoring docs now teach `organizationId` and distinguish the two isolation axes: **org row-scoping** (`organization_id`, shared DB) vs **environment / database-per-tenant** (`service-tenant`, `driver-turso`). Community edition never populates an org, so `organizationId` is `undefined` there. diff --git a/.changeset/view-metadata-type-schema-runtime-shapes.md b/.changeset/view-metadata-type-schema-runtime-shapes.md deleted file mode 100644 index ac51178814..0000000000 --- a/.changeset/view-metadata-type-schema-runtime-shapes.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -"@objectstack/spec": patch ---- - -**The `view` metadata type-schema now validates all three runtime `view` shapes instead of stripping two of them to `{}`.** `metadata-type-schemas.ts` mapped `view` to the aggregate container `ViewSchema` (`{ list, form, listViews, formViews }`, every slot optional). Zod strips unknown keys, so the two non-container shapes a `view` body actually carries at runtime — a standalone **ViewItem record** (`{ name, object, viewKind, config }`) and a **console personalization overlay** (raw view config + identity inherited by `normalizeViewMetadata`, #2555) — both strip-parsed to `{}`. That made the `422` check in `saveMetaItem` and read-time `computeMetadataDiagnostics` a **no-op** for those shapes: a broken `config` (e.g. a kanban missing `groupByField`) saved with a false `200` and badged valid, and the view create-seed test validated against nothing. - -`view` now maps to a new `ViewMetadataSchema` — a union over the three shapes, each validated genuinely: - -1. **defineView container** — non-empty (`ViewSchema` refined to require at least one of `list`/`form`/`listViews`/`formViews`; an empty container is rejected, mirroring `defineView`). -2. **ViewItem record** — `ViewItemSchema`; the nested `config` is validated against ListView/FormView. -3. **Flattened personalization overlay** — inline ListView/FormView config plus optional identity fields. Structural guards pin `config`/`list`/`form`/`listViews`/`formViews` to `undefined` so a malformed record or container can never be rescued through this lenient branch with its real payload silently stripped. - -All members strip-parse (no `.strict()`), so auxiliary Studio round-trip keys (`isPinned`, `sortOrder`, …) still ride along without a false `422`, and `saveMetaItem` keeps persisting the body verbatim. `z.toJSONSchema()` emits the schema as an `anyOf` of the four members, which `/api/v1/meta/types/view` serves to Studio's SchemaForm. - -Fixes #3095. diff --git a/.changeset/viewfilterrule-operator-enum-3373.md b/.changeset/viewfilterrule-operator-enum-3373.md deleted file mode 100644 index f80cad2bc2..0000000000 --- a/.changeset/viewfilterrule-operator-enum-3373.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -"@objectstack/spec": patch ---- - -fix(spec): enforce the `ViewFilterRule` operator enum with legacy-alias -normalization (#3373) - -`ViewFilterRule.operator` was previously an open string, so views could persist -operators the runtime cannot evaluate. The Zod schema now constrains it to the -supported operator enum and normalizes the known legacy aliases to their -canonical form on parse. This is a public spec/api-surface change -(`packages/spec/api-surface.json`) that landed on `main` in #3373 without a -changeset; this backfills it so the fix ships in the next release instead of -being silently stranded. diff --git a/examples/app-crm/CHANGELOG.md b/examples/app-crm/CHANGELOG.md index 38dd167d4f..44fd80fc59 100644 --- a/examples/app-crm/CHANGELOG.md +++ b/examples/app-crm/CHANGELOG.md @@ -1,5 +1,62 @@ # @objectstack/example-crm +## 4.0.90 + +### Patch Changes + +- Updated dependencies [b39c65d] +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [a3823b2] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [fdc244e] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [46e876c] +- Updated dependencies [2ea08ee] +- Updated dependencies [d1d1c40] +- Updated dependencies [ee0a499] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [2049b6a] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [92f5f19] +- Updated dependencies [a2d6555] +- Updated dependencies [3a6310c] +- Updated dependencies [32899e6] +- Updated dependencies [515f11a] +- Updated dependencies [4174a07] +- Updated dependencies [ce468c8] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/runtime@16.0.0 + - @objectstack/spec@16.0.0 + ## 4.0.90-rc.1 ### Patch Changes diff --git a/examples/app-crm/package.json b/examples/app-crm/package.json index 03a1261b1e..131609c9ef 100644 --- a/examples/app-crm/package.json +++ b/examples/app-crm/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/example-crm", - "version": "4.0.90-rc.1", + "version": "4.0.90", "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 20c3f0d2c4..146f8e69c0 100644 --- a/examples/app-showcase/CHANGELOG.md +++ b/examples/app-showcase/CHANGELOG.md @@ -1,5 +1,72 @@ # @objectstack/example-showcase +## 0.3.12 + +### Patch Changes + +- Updated dependencies [b39c65d] +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [41e703b] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [a3823b2] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [fdc244e] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [47d923c] +- Updated dependencies [46e876c] +- Updated dependencies [2ea08ee] +- Updated dependencies [d1d1c40] +- Updated dependencies [ee0a499] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [efbcfe1] +- Updated dependencies [2049b6a] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [92f5f19] +- Updated dependencies [a2d6555] +- Updated dependencies [3a6310c] +- Updated dependencies [32899e6] +- Updated dependencies [515f11a] +- Updated dependencies [4174a07] +- Updated dependencies [ce468c8] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/runtime@16.0.0 + - @objectstack/spec@16.0.0 + - @objectstack/connector-openapi@16.0.0 + - @objectstack/driver-sql@16.0.0 + - @objectstack/cloud-connection@16.0.0 + - @objectstack/connector-mcp@16.0.0 + - @objectstack/connector-rest@16.0.0 + - @objectstack/connector-slack@16.0.0 + - @objectstack/service-datasource@16.0.0 + ## 0.3.12-rc.1 ### Patch Changes diff --git a/examples/app-showcase/package.json b/examples/app-showcase/package.json index 5f61141048..4a99be56e2 100644 --- a/examples/app-showcase/package.json +++ b/examples/app-showcase/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/example-showcase", - "version": "0.3.12-rc.1", + "version": "0.3.12", "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 b802b98e52..822636e391 100644 --- a/examples/app-todo/CHANGELOG.md +++ b/examples/app-todo/CHANGELOG.md @@ -1,5 +1,75 @@ # @objectstack/example-todo +## 4.0.90 + +### Patch Changes + +- Updated dependencies [b39c65d] +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [e057f42] +- Updated dependencies [9ccd1e9] +- Updated dependencies [a3823b2] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [fdc244e] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [46e876c] +- Updated dependencies [2ea08ee] +- Updated dependencies [d1d1c40] +- Updated dependencies [ee0a499] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [15dbe18] +- Updated dependencies [83e8f7d] +- Updated dependencies [230358c] +- Updated dependencies [d2723e2] +- Updated dependencies [674457a] +- Updated dependencies [fefcd54] +- Updated dependencies [2049b6a] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [92f5f19] +- Updated dependencies [a2d6555] +- Updated dependencies [3a6310c] +- Updated dependencies [32899e6] +- Updated dependencies [515f11a] +- Updated dependencies [4174a07] +- Updated dependencies [ce468c8] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/runtime@16.0.0 + - @objectstack/spec@16.0.0 + - @objectstack/objectql@16.0.0 + - @objectstack/client@16.0.0 + - @objectstack/metadata@16.0.0 + - @objectstack/mcp@16.0.0 + - @objectstack/driver-sqlite-wasm@16.0.0 + - @objectstack/knowledge-memory@16.0.0 + - @objectstack/service-knowledge@16.0.0 + ## 4.0.90-rc.1 ### Patch Changes diff --git a/examples/app-todo/package.json b/examples/app-todo/package.json index 4ed00d939a..d72e6b9881 100644 --- a/examples/app-todo/package.json +++ b/examples/app-todo/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/example-todo", - "version": "4.0.90-rc.1", + "version": "4.0.90", "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 3945845c81..6e60071872 100644 --- a/examples/embed-objectql/CHANGELOG.md +++ b/examples/embed-objectql/CHANGELOG.md @@ -1,5 +1,54 @@ # @objectstack/example-embed-objectql +## 0.0.30 + +### Patch Changes + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [e057f42] +- Updated dependencies [a3823b2] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [fdc244e] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [46e876c] +- Updated dependencies [2ea08ee] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [d2723e2] +- Updated dependencies [674457a] +- Updated dependencies [fefcd54] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + - @objectstack/objectql@16.0.0 + - @objectstack/driver-memory@16.0.0 + ## 0.0.30-rc.1 ### Patch Changes diff --git a/examples/embed-objectql/package.json b/examples/embed-objectql/package.json index e680aeb067..c5bb388467 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.30-rc.1", + "version": "0.0.30", "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 e25eb13b77..7c3e08ebbc 100644 --- a/packages/adapters/hono/CHANGELOG.md +++ b/packages/adapters/hono/CHANGELOG.md @@ -1,5 +1,32 @@ # @objectstack/hono +## 16.0.0 + +### Patch Changes + +- Updated dependencies [b39c65d] +- Updated dependencies [22013aa] +- Updated dependencies [fdc244e] +- Updated dependencies [bfa3c3f] +- Updated dependencies [2ea08ee] +- Updated dependencies [d1d1c40] +- Updated dependencies [ee0a499] +- Updated dependencies [62a2117] +- Updated dependencies [83e8f7d] +- Updated dependencies [efbcfe1] +- Updated dependencies [2049b6a] +- Updated dependencies [6c270a6] +- Updated dependencies [92f5f19] +- Updated dependencies [a2d6555] +- Updated dependencies [3a6310c] +- Updated dependencies [32899e6] +- Updated dependencies [515f11a] +- Updated dependencies [4174a07] +- Updated dependencies [ce468c8] + - @objectstack/runtime@16.0.0 + - @objectstack/plugin-hono-server@16.0.0 + - @objectstack/types@16.0.0 + ## 16.0.0-rc.1 ### Patch Changes diff --git a/packages/adapters/hono/package.json b/packages/adapters/hono/package.json index b1ede15e62..28154e4fd6 100644 --- a/packages/adapters/hono/package.json +++ b/packages/adapters/hono/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/hono", - "version": "16.0.0-rc.1", + "version": "16.0.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 050bfa9390..84029ed647 100644 --- a/packages/apps/account/CHANGELOG.md +++ b/packages/apps/account/CHANGELOG.md @@ -1,5 +1,50 @@ # @objectstack/account +## 16.0.0 + +### Patch Changes + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [a3823b2] +- Updated dependencies [bc65105] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [46e876c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + - @objectstack/platform-objects@16.0.0 + ## 16.0.0-rc.1 ### Patch Changes diff --git a/packages/apps/account/package.json b/packages/apps/account/package.json index a2cd02034d..72108dc444 100644 --- a/packages/apps/account/package.json +++ b/packages/apps/account/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/account", - "version": "16.0.0-rc.1", + "version": "16.0.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 6704fc9ab0..aea2f8f01c 100644 --- a/packages/apps/setup/CHANGELOG.md +++ b/packages/apps/setup/CHANGELOG.md @@ -1,5 +1,50 @@ # @objectstack/setup +## 16.0.0 + +### Patch Changes + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [a3823b2] +- Updated dependencies [bc65105] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [46e876c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + - @objectstack/platform-objects@16.0.0 + ## 16.0.0-rc.1 ### Patch Changes diff --git a/packages/apps/setup/package.json b/packages/apps/setup/package.json index 6fbe625ae0..ca9a28adba 100644 --- a/packages/apps/setup/package.json +++ b/packages/apps/setup/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/setup", - "version": "16.0.0-rc.1", + "version": "16.0.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 8162b7d1dc..0062a78111 100644 --- a/packages/apps/studio/CHANGELOG.md +++ b/packages/apps/studio/CHANGELOG.md @@ -1,5 +1,50 @@ # @objectstack/studio +## 16.0.0 + +### Patch Changes + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [a3823b2] +- Updated dependencies [bc65105] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [46e876c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + - @objectstack/platform-objects@16.0.0 + ## 16.0.0-rc.1 ### Patch Changes diff --git a/packages/apps/studio/package.json b/packages/apps/studio/package.json index 57cf6778a1..c232282e1e 100644 --- a/packages/apps/studio/package.json +++ b/packages/apps/studio/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/studio", - "version": "16.0.0-rc.1", + "version": "16.0.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 d7d9c403e0..566ccde20f 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,409 @@ # @objectstack/cli +## 16.0.0 + +### Minor Changes + +- 2ea08ee: Flow trigger observability — kill the four-layer silence around record-change flows that never fire (2026-07-17 third-party eval). + + A misauthored auto-launched flow (wrong `objectName`, missing `requires: ['automation','triggers']`, failing start condition) produced ZERO output at every layer: the engine's own registration/binding logs land inside the CLI's boot-quiet stdout window (which swallows debug/info/warn — only error/fatal reach stderr), and each "didn't happen" path was itself silent. Fixes: + + - **Startup banner `Flows:` section** (`os serve`/`os dev`/`os start`): flow count, bound-to-trigger count, registered trigger types, draft count — plus loud `⚠` lines for flows declared with no automation engine enabled (`requires` missing), flows whose trigger type has no registered trigger, and bound record-change flows targeting an unknown object (dead binding). Printed after stdout is restored, so it is immune to the boot-quiet window. + - **Trigger-fired run failures now log at ERROR** (stderr — always visible): the automation engine no longer drops the AutomationResult of a trigger-fired execution; condition-evaluation faults and node failures surface with the flow name. Condition-not-met skips stay at debug (high-frequency, intentional). + - **`RecordChangeTrigger` probes object existence at bind time** and warns when a flow's `objectName` matches no registered object (exact-name matching), instead of silently arming a hook that can never fire. + - **`kernel:bootstrapped` binding audit** in the automation plugin: warns per enabled-but-unbound triggered flow with the reason, and reports registered/bound/draft counts (`AutomationEngine.getTriggerBindingAudit()`, extended `getFlowRuntimeStates()` with `status`/`triggerType`/`object`). + - **`os validate` flow-wiring advisories** (`@objectstack/lint` `validateFlowTriggerReadiness`): warns when a record-triggered flow targets an object the stack does not define, and when an auto-triggered flow's status is `draft` (authored or defaulted — draft flows still fire; declare `active` or `obsolete`). + - Removed leftover boot-debug writes (`registerApp`/`AppPlugin`/`StandaloneStack`/`AuditPlugin` stderr noise) that previous debugging of this same silence had left behind. + +- 83e8f7d: feat(mcp): decouple the stdio auto-start switch from the HTTP surface + surface the MCP endpoint on `os dev` boot (#3167) + + The MCP HTTP surface (`/api/v1/mcp`) and the long-lived stdio transport used to + share one env var: `OS_MCP_SERVER_ENABLED=true` turned the HTTP surface on **and** + silently auto-started the stdio transport — which bridges the raw metadata service + + - data engine with no per-request principal (unscoped). An operator setting it to + "make sure MCP is on" got an unscoped transport as a side effect. + + * **`@objectstack/types`** — new `resolveMcpStdioAutoStart()`. Stdio auto-start is + now its own switch, `OS_MCP_STDIO_ENABLED` (default off); `OS_MCP_SERVER_ENABLED` + governs only the HTTP surface. The legacy `OS_MCP_SERVER_ENABLED=true` trigger + still starts stdio for one release, flagged as deprecated. `=false` is unchanged + (it only ever gated HTTP). + * **`@objectstack/mcp`** — `MCPServerPlugin.start()` gates stdio on the new switch + and logs a one-time deprecation warning when started via the legacy alias. + * **`@objectstack/cli`** — `os dev` now prints the MCP endpoint, the agent-skill + URL, and a ready-to-paste `claude mcp add` command on boot (gated on the HTTP + surface being on), so the "an agent operates the app it's building" loop is + discoverable at dev time. + * **`create-objectstack`** — the blank scaffold README documents that the app is + itself an MCP server (the serve side), distinct from the consume-side connector. + +- 06ff734: feat(spec)!: remove deprecated `aiStudio`/`aiSeat` capability aliases (#3308) + + **BREAKING** (shipped as minor per the launch-window convention). The one-cycle + deprecation window from #3265 is over: the legacy camelCase `requires` spellings + `aiStudio`/`aiSeat` are no longer canonicalized to `ai-studio`/`ai-seat` — they + are now plain unknown tokens, rejected by `defineStack` like any other typo. + + - Removed exports `DEPRECATED_PLATFORM_CAPABILITY_ALIASES` and + `canonicalizePlatformCapability` from `@objectstack/spec`; `isKnownPlatformCapability` + no longer canonicalizes. + - `defineStack` no longer rewrites aliases (the `canonicalizeStackRequires` pass + is gone); the serve resolver no longer canonicalizes raw-artifact `requires`. + + Migration: use the canonical kebab-case tokens `ai-studio` / `ai-seat`. All + first-party configs were migrated in #862/#863; only stacks still carrying the + legacy spelling are affected. Cloud's `objectos-runtime` (pinned to an older + framework) follows on its next `.framework-sha` bump. + +### Patch Changes + +- 6289ec3: feat(i18n): translation slot for action `resultDialog` copy — the one-shot secret-reveal dialogs are now localizable + + The post-success `resultDialog` (temporary passwords, 2FA backup codes, OAuth + client secrets) had no slot in the translation protocol, so its title / + description / acknowledge button / field labels always rendered the hardcoded + English metadata literals even on fully-translated locales. + + - **spec.** `_actions.` (object + object-first node) and + `globalActions.` gain an optional `resultDialog` translation node + (`ActionResultDialogTranslationSchema`): `title`, `description`, + `acknowledge`, and `fields` keyed by the **literal** result-field path + (e.g. `"user.email"` — keys may contain dots; resolvers index the record + directly, never split on `.`). New `resolveActionResultDialog` overlay + resolver, wired into `translateAction` for API-boundary translation. + - **cli.** `os i18n extract` emits the new `resultDialog.*` keys (title / + description / acknowledge / `fields.` for labelled fields), so + coverage and skeleton generation see them. + - **platform-objects.** en / zh-CN / ja-JP / es-ES bundles ship the + resultDialog copy for all six shipped dialogs: `sys_user.create_user`, + `sys_user.set_user_password`, `sys_two_factor.enable_two_factor`, + `sys_two_factor.regenerate_backup_codes`, + `sys_oauth_application.create_oauth_application`, and + `sys_oauth_application.rotate_client_secret`. + + Client-side rendering lands in objectui (`actionResultDialog` resolver in + `@object-ui/i18n` + result-dialog handlers). Purely additive — untranslated + locales keep falling back to the metadata literals. + +- da58467: fix(cli): honor `OS_DATABASE_DRIVER=memory` (mingo InMemoryDriver) (#3276) + + `os dev` / `os start` / `os serve` advertised a `memory` database driver + (`--database-driver memory`, `OS_DATABASE_DRIVER=memory`, and a `memory://` + URL scheme), but `serve.ts`'s driver dispatch had no `memory` branch — so it + silently fell through to the dev SQLite `:memory:` default (SQLite-in-memory, + a _different_ engine) or, in production, registered no driver at all. + + The driver kind-resolution + construction is now extracted into + `utils/storage-driver.ts` (unit-testable in isolation) with the missing + `memory` branch: selecting it yields the mingo `InMemoryDriver` in dev AND + production. The `memory://` / `mingo://` URL scheme is now recognized too, + kept distinct from sqlite's `:memory:` pseudo-file. Telemetry-datasource + provisioning behavior is unchanged. + +- fb107b8: fix(cli): tolerate the `--` separator pnpm injects when forwarding script args (#3114) + + The AGENTS.md-documented backend-debug flow `pnpm dev -- --fresh -p ` failed at + the repo root with an opaque `Unexpected arguments: -p, 44637` (exit 2 + a help dump). + + pnpm appends forwarded args to a script **verbatim, including the `--`**, and each + nested `pnpm --filter` hop preserves it, so the showcase's `objectstack dev +--seed-admin` ran as `objectstack dev --seed-admin -- --fresh -p 44637`. oclif reads + `--` as POSIX end-of-flags, so everything after it became positional: `--fresh` was + silently swallowed as the `package` arg and `-p 44637` overflowed the arg list. Every + flag the user asked for was dropped — the failure was opaque precisely because the + `--` looks inert. + + A `preparse` hook now drops `--` separators before oclif parses argv, so the + npm-style `-- ` form and the bare form behave identically, for every command + and both bins (`run.js`, `run-dev.js`). No `os` command takes passthrough args (none + sets `strict = false`, none reads raw argv), so a `--` carries no meaning here and is + always a package-manager artifact. + + Note this is not fixable via oclif's `'--': false` parser option: that keeps + flag-parsing on past the separator but re-appends the `--` into argv, so strict + commands fail with `Unexpected argument: --` instead. + + Tradeoff: a `-`-prefixed token can no longer be forced to parse as a positional + value. Every `os` positional is a config path, a metadata / datasource / package + name, or an id — none start with `-`. + +- 216c2db: fix(cli): fail loudly when `turso`/libSQL is selected in the open-core CLI (#3276 follow-up) + + Same "declared ≠ enforced" class as the `memory` fix: the CLI advertised `turso` + (`--database-driver turso`, `OS_DATABASE_DRIVER=turso`, `libsql://` URLs) but the + driver dispatch had no `turso` branch, so it silently fell through to the SQLite + default and ignored the requested engine. + + `turso`/libSQL ships in the cloud / enterprise distribution + (`@objectstack/driver-turso`, composed by the cloud runtime's own kernel factory — + open-core's standalone stack deliberately does not consume it). Rather than pull an + EE driver into open-core, `createStorageDriver` now throws a typed + `UnsupportedDriverError` for `turso`/`libsql`, and `serve.ts` surfaces it as a + fatal, actionable boot error (naming the cloud/EE package and the open-core + alternatives) instead of silently degrading to SQLite. `libsql://` / `*.turso.*` + URLs stay classified as `turso` so they hit the same loud failure. + +- fdc244e: Dev-loop DX fixes from the 15.1 third-party evaluation (P2 batch): + + - **Hot-added objects are now queryable without a restart.** Adding a `*.object.ts` under `os dev` used to recompile "green" while every query answered `no such table` (or `not registered`) until a manual restart: the artifact reload never notified the ObjectQL registry, tables were only created at boot, and seeds only loaded from the boot-time bundle. The `metadata:reloaded` payload now carries the parsed artifact; ObjectQL ingests the object definitions and re-runs the idempotent schema sync (same `skipSchemaSync` opt-out as boot), and the runtime loads seeds for first-seen objects (dev, single-tenant). `os dev` also prints `✚ new object(s): …` on recompile. + - **Dev admin credentials stay visible.** The `os dev` startup banner only showed `admin@objectos.ai / admin123` on the boot that actually seeded it; with the persistent default DB every later boot hid it, and the Console login page never knew it existed. The hint now re-arms on every dev boot for as long as the account still verifies against the default password, and `GET /api/v1/auth/config` exposes a dev-gated `devSeedAdmin` field (never present outside `NODE_ENV=development`) so the login page can show it. + - **`os doctor` reference analysis understands current metadata shapes.** Objects bound through `defineView` containers (`list`/`listViews`/`form`/`formViews` → `data.object`, subform `childObject`, lookup form fields) and app navigation (`objectName`, nested `children`, `areas`) were reported as "defined but not referenced". The collector now walks the canonical shapes (plus flow node `config.object`/`objectName`) and the orphan-view check descends into containers. + +- 546a0d6: fix(cli): `os explain object` documented `ownership` with the wrong allowed values (#3244) + + The schema catalog described the object `ownership` field as the package + _contribution_ kind (`"own" | "extend"`, the `ObjectOwnershipEnum` set via + `registerObject`). But `ObjectSchema.ownership` is the **record-ownership + model** — `z.enum(['user', 'org', 'none'])` — a distinct concept the spec + explicitly warns not to conflate despite the shared word. + + `os explain object` now prints: + + ownership 'user' | 'org' | 'none' Record-ownership model: user (default, + injects a reassignable owner_id) | org | none (no per-record owner). + Distinct from the package own/extend contribution kind. + + A regression test (`packages/cli/test/commands.test.ts`) pins the documented + values to the record-ownership enum so the two concepts can't drift back + together. Found during the #1880 docs implementation-accuracy audit. + +- f58db35: fix(cli): treat an inline `label:` as the default-locale source in i18n coverage + + A fresh `npm create objectstack` scaffold reported 4 `i18n/missing-object` / + `i18n/missing-field` errors for its own `_note` object, even though the + template authors `label: 'Note'`, `pluralLabel: 'Notes'`, `label: 'Title'` and + `label: 'Body'` inline. The only way to silence them was to commit an `en` + bundle restating strings the metadata already carries. + + The inline `label:` _is_ the default-locale text: the runtime resolver falls + back to it when a bundle has no entry (`translateObject`), and `os i18n +extract` seeds bundles from it. Coverage now honours that contract — an inline + label satisfies the default locale, and a bundle is what _other_ locales need. + Keys with no source string anywhere are no longer reported as i18n gaps; a + missing label is already `required/label`'s finding. + + Non-default locales are unaffected: they still warn for every untranslated key + (`os lint` on `examples/app-todo` reports the same 79 warnings as before, with + its 39 default-locale errors gone). `os lint --include-platform` drops the + platform baseline's default-locale errors for the same reason — the platform + ships English labels inline — while keeping its non-default-locale warnings. + +- 878c1ed: `os lint` no longer buries the user's own signal under the platform i18n baseline. A fresh scaffold reported 800+ `i18n/missing-metadataForm` errors — translation keys for platform built-in metadata forms (email_template, …) that the platform packages already ship at runtime. Those are now hidden by default and folded into one summary line (`platform built-ins: N i18n issue(s) hidden`); pass `--include-platform` to audit them, and read `hiddenPlatform` in `--json` output. User-authored metadata coverage is reported unchanged. +- 9760844: feat(cli): surface the MCP endpoint in the server-ready banner (#3167) + + The MCP server (`/api/v1/mcp`) is a default-on core capability, but nothing in + the `os dev` / `os serve` boot output pointed to it — a developer had to already + know it was there to connect an AI client. The server-ready banner now prints + the MCP URL and the `SKILL.md` pointer whenever the surface is enabled + (`isMcpServerEnabled()`, the same switch that auto-loads the plugin and gates + the route), so an agent can operate the running app straight from the dev loop. + Hidden when `OS_MCP_SERVER_ENABLED=false`. + +- fefcd54: fix(spec): declare `ownership` as a first-class ObjectSchema field (#3175) + + The object-level record-ownership model — `ownership: 'user' | 'org' | 'none'`, + which drives the registry's `owner_id` auto-provisioning (`applySystemFields`) — + was read by the engine via `(schema as any).ownership` while `ObjectSchema.create()` + **rejected** it as an unknown top-level key (ADR-0032 / #1535). So a tested engine + opt-out (`ownership: 'org' | 'none'` on catalog / junction tables) could not be + set through the sanctioned authoring path, and the same `ownership` word was read + elsewhere as the unrelated package-contribution kind (`own` / `extend`). + + - **spec**: `ObjectSchema` now declares `ownership: z.enum(['user','org','none']).optional()`. + Authoring the record-ownership opt-out validates cleanly; the registry reads it + off the typed schema (no `as any`). A retired `ownership: 'own'` / `'extend'` + value fails with guidance pointing at the record-ownership model and noting that + `own`/`extend` is the contribution kind (`registerObject`), not an object-schema value. + - **cli**: the `object` scaffold no longer emits the now-invalid `ownership: 'own'` + (owner injection is the default), and `objectstack info` labels the record model + with the correct `user` default. + + No runtime behavior change: `applySystemFields` and its `owner_id` injection logic + are unchanged — this makes the property the engine already honors legally authorable + and consistently typed. + +- 8923843: Reject view containers that define no views. A flat list-view object (`{ name, label, type, columns, ... }`) parses to an empty `ViewSchema` container because Zod strips unknown keys — zero views register and the Console silently renders nothing. `defineView()` now throws on a zero-view container, and `os validate` gains a `view-container-shape` check (`validateViewContainers` in `@objectstack/lint`) that reports flat or empty `views: []` entries pre-parse with a wrap-it fix hint. +- a2795f6: feat(triggers): declarative time-relative trigger — daily sweep instead of fragile date-equality (#1874) + + Time-relative business rules ("alert 60 days before a contract's `end_date`") + could only be expressed as a `record_change` flow gated on a date-equality + condition like `end_date == daysFromNow(60)`. That predicate is only evaluated + when the record _happens to change_, so it fires only if a record is edited on + exactly the threshold day — i.e. almost never, unattended. The robust + alternative was a hand-written cron + range query that every author + re-implemented (contracts `renewal_alert`, hr `document_expiring_soon`, + procurement `po_overdue`, …). + + A flow's start node can now declare a `timeRelative` descriptor instead: + + ```ts + config: { + timeRelative: { + object: 'contracts', + dateField: 'end_date', + offsetDays: [60, 30, 7], // T-minus reminders — fires on each threshold day + // — or — withinDays: 30 // "expiring soon" range; negative = overdue lookback + filter: { status: 'active' }, // optional, ANDed with the date window + }, + schedule: { type: 'cron', expression: '0 8 * * *' }, // optional; defaults to daily 08:00 UTC + } + ``` + + The new `time_relative` trigger (shipped in `@objectstack/trigger-schedule` as + `TimeRelativeTriggerPlugin`) sweeps the object on that schedule and launches the + flow **once per matching record**, with the record on the automation context — + so the start-node `condition` gate and `{record.}` interpolation work + exactly as for a record-change flow. Because the window is evaluated every day, + a threshold is never missed regardless of when the record last changed. The + discovery query runs as a system operation (RLS-bypassing) and is capped + (`maxRecords`, default 1000) so a mis-scoped window can't fan out unboundedly; + per-record failures are isolated so one bad row never aborts the sweep. + + The automation engine routes a start node carrying `config.timeRelative` to the + `time_relative` trigger (ahead of the plain `schedule` trigger, whose behavior is + unchanged), and `os validate` gains readiness checks for the new descriptor + (unknown swept object, ambiguous draft status). New authorable spec key: + `TimeRelativeTriggerSchema` (`@objectstack/spec/automation`). + +- Updated dependencies [b39c65d] +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [2f3c641] +- Updated dependencies [e38da5b] +- Updated dependencies [f9b118d] +- Updated dependencies [22013aa] +- Updated dependencies [a9459e6] +- Updated dependencies [3ad3dd5] +- Updated dependencies [e412fb6] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [02eafa5] +- Updated dependencies [deb7e7e] +- Updated dependencies [a8aa34c] +- Updated dependencies [e057f42] +- Updated dependencies [9ccd1e9] +- Updated dependencies [a3823b2] +- Updated dependencies [a276969] +- Updated dependencies [47d923c] +- Updated dependencies [bfa3c3f] +- Updated dependencies [a791200] +- Updated dependencies [39b56d0] +- Updated dependencies [db34d54] +- Updated dependencies [1965549] +- Updated dependencies [447465a] +- Updated dependencies [a140ff0] +- Updated dependencies [bc65105] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [6b51346] +- Updated dependencies [80273c8] +- Updated dependencies [fdc244e] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [dd9f223] +- Updated dependencies [47d923c] +- Updated dependencies [46e876c] +- Updated dependencies [780b4b5] +- Updated dependencies [2ea08ee] +- Updated dependencies [7125007] +- Updated dependencies [d1d1c40] +- Updated dependencies [616e839] +- Updated dependencies [b320158] +- Updated dependencies [ee0a499] +- Updated dependencies [5f05de2] +- Updated dependencies [021ba4c] +- Updated dependencies [158aa14] +- Updated dependencies [9d897b3] +- Updated dependencies [62a2117] +- Updated dependencies [f8c1b69] +- Updated dependencies [15dbe18] +- Updated dependencies [83e8f7d] +- Updated dependencies [230358c] +- Updated dependencies [d2723e2] +- Updated dependencies [674457a] +- Updated dependencies [fefcd54] +- Updated dependencies [efbcfe1] +- Updated dependencies [2049b6a] +- Updated dependencies [beaf2de] +- Updated dependencies [06cb319] +- Updated dependencies [1e145eb] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [290e2f0] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [92f5f19] +- Updated dependencies [a2d6555] +- Updated dependencies [3a6310c] +- Updated dependencies [32899e6] +- Updated dependencies [515f11a] +- Updated dependencies [4174a07] +- Updated dependencies [ce468c8] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [ea32ec7] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/runtime@16.0.0 + - @objectstack/spec@16.0.0 + - @objectstack/platform-objects@16.0.0 + - @objectstack/plugin-security@16.0.0 + - @objectstack/objectql@16.0.0 + - @objectstack/plugin-hono-server@16.0.0 + - @objectstack/plugin-approvals@16.0.0 + - @objectstack/service-automation@16.0.0 + - @objectstack/service-messaging@16.0.0 + - @objectstack/plugin-sharing@16.0.0 + - @objectstack/rest@16.0.0 + - @objectstack/service-analytics@16.0.0 + - @objectstack/lint@16.0.0 + - @objectstack/plugin-auth@16.0.0 + - @objectstack/core@16.0.0 + - @objectstack/client@16.0.0 + - @objectstack/console@16.0.0 + - @objectstack/formula@16.0.0 + - @objectstack/metadata@16.0.0 + - @objectstack/driver-sql@16.0.0 + - @objectstack/trigger-record-change@16.0.0 + - @objectstack/plugin-audit@16.0.0 + - @objectstack/service-storage@16.0.0 + - @objectstack/mcp@16.0.0 + - @objectstack/types@16.0.0 + - @objectstack/observability@16.0.0 + - @objectstack/service-realtime@16.0.0 + - @objectstack/trigger-schedule@16.0.0 + - @objectstack/plugin-webhooks@16.0.0 + - @objectstack/cloud-connection@16.0.0 + - @objectstack/verify@16.0.0 + - @objectstack/account@16.0.0 + - @objectstack/setup@16.0.0 + - @objectstack/driver-memory@16.0.0 + - @objectstack/driver-mongodb@16.0.0 + - @objectstack/driver-sqlite-wasm@16.0.0 + - @objectstack/plugin-email@16.0.0 + - @objectstack/plugin-reports@16.0.0 + - @objectstack/service-cache@16.0.0 + - @objectstack/service-datasource@16.0.0 + - @objectstack/service-job@16.0.0 + - @objectstack/service-package@16.0.0 + - @objectstack/service-queue@16.0.0 + - @objectstack/service-settings@16.0.0 + - @objectstack/service-sms@16.0.0 + - @objectstack/trigger-api@16.0.0 + - @objectstack/plugin-pinyin-search@16.0.0 + ## 16.0.0-rc.1 ### Minor Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index 55c8305d8e..e1defd8e80 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/cli", - "version": "16.0.0-rc.1", + "version": "16.0.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 8f467bb7e9..b34cef723d 100644 --- a/packages/client-react/CHANGELOG.md +++ b/packages/client-react/CHANGELOG.md @@ -1,5 +1,56 @@ # @objectstack/client-react +## 16.0.0 + +### Patch Changes + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [e057f42] +- Updated dependencies [9ccd1e9] +- Updated dependencies [a3823b2] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [dd9f223] +- Updated dependencies [46e876c] +- Updated dependencies [5f05de2] +- Updated dependencies [021ba4c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [290e2f0] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + - @objectstack/core@16.0.0 + - @objectstack/client@16.0.0 + ## 16.0.0-rc.1 ### Patch Changes diff --git a/packages/client-react/package.json b/packages/client-react/package.json index 074eec54c5..4fab5f05f2 100644 --- a/packages/client-react/package.json +++ b/packages/client-react/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/client-react", - "version": "16.0.0-rc.1", + "version": "16.0.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 dd2dfcee1f..97a2679177 100644 --- a/packages/client/CHANGELOG.md +++ b/packages/client/CHANGELOG.md @@ -1,5 +1,152 @@ # @objectstack/client +## 16.0.0 + +### Minor Changes + +- 9ccd1e9: feat(client): typed `data.batchTransaction()` for the atomic cross-object batch (#1604 / ADR-0034 item 4) + + Adds `client.data.batchTransaction(operations)` (and the environment-scoped + `client.project(id).data.batchTransaction`) — a typed SDK surface for + `POST {basePath}/batch`, the all-or-nothing cross-object transactional batch + that master-detail saves go through. Reuses `CrossObjectBatchOperation` / + `CrossObjectBatchRequest` / `CrossObjectBatchResponse` from + `@objectstack/spec/api` (also re-exported from the client for convenience); + supports `{ $ref: }` intra-batch parent references. + + The method is always atomic and deliberately exposes no `atomic` flag — the + endpoint rejects `atomic: false` with `400 BATCH_NOT_ATOMIC`. Non-atomic + per-object bulk writes stay on `data.batch()` / `createMany` / `updateMany`, + so any best-effort fallback is isolated in the caller's adapter (the ObjectUI + `masterDetailTx` adapter), not in the SDK. + +- bfa3c3f: **Broadcast a `transactionalBatch` capability bit in discovery so clients negotiate the atomic cross-object batch declaratively, instead of runtime-probing 404/405/501 (#3298).** + + The atomic cross-object batch endpoint (`POST {basePath}/batch`, #1604 / ADR-0034 item 4) and its typed SDK surface (`client.data.batchTransaction`, #3271) already shipped, but discovery never told a client whether a backend actually supports it. Consumers (notably ObjectUI's `ObjectStackAdapter`) had to _probe_: fire a `/batch`, read `404`/`405` (no route) or `501` (no runtime transaction), and only then fall back to non-atomic client-side simulation. That is "find out by calling", not capability negotiation — it cannot be decided at connect time and cannot serve as the "minimum backend supports `/batch`" gate that blocks hard-deleting the non-atomic fallback downstream. + + `WellKnownCapabilitiesSchema` gains a required `transactionalBatch: boolean`, and **every** discovery producer fills it honestly (`declared === enforced`), so it never becomes a declared-but-unpopulated bit: + + - **`@objectstack/metadata-protocol`** (`getDiscovery`) — reports whether the runtime engine can honour a transaction (`typeof engine.transaction === 'function'`). The `/batch` handler runs its ops inside `engine.transaction()`, which degrades to a non-atomic passthrough (or 501) without one. + - **`@objectstack/rest`** (`/discovery`) — ANDs the engine signal with whether it actually mounts the route (`api.enableBatch`), so a server with batch disabled reports `false` even on a transaction-capable engine (never advertise an endpoint that would 404). + - **`@objectstack/plugin-hono-server`** (standalone discovery) — reports `false`: this minimal surface registers CRUD only and does not mount `/batch` (that ships with `@objectstack/rest`). Under-reporting is the safe direction — a client keeps its correct-but-slower fallback rather than losing atomicity. + - **`@objectstack/client`** — already normalizes hierarchical `capabilities` to flat booleans, so `client.capabilities.transactionalBatch` is exposed (and now typed) for declarative consumers. + + The bit follows the existing capability semantics: `true` ⟺ the `/batch` route is mounted **and** the runtime can honour a transaction — the exact condition under which the endpoint returns `200` rather than `404`/`405`/`501`. Additive and behavior-preserving; only the discovery payload gains a field. + +- 668dd17: **Breaking (npm type surface): retire the vestigial feed contracts + protocol surface (ADR-0052 §5 follow-up, #1959).** + + The `service-feed` runtime was deleted in #1955; `sys_comment` / `sys_activity` + are the canonical record-collaboration/timeline backend. This removes the dead + type surface that still pointed at the deleted runtime — every removed method was + already unreachable (the feed REST route was never mounted → 404; the protocol + implementation was never wired with a feed service, so `requireFeedService()` + could only throw). No behavior changes. + + No authorable metadata key is removed (the `feeds:` object capability flag and + the `RecordActivity` UI component config are unchanged), so `PROTOCOL_MAJOR` + stays 15 and this ships as `minor` rather than a protocol major. + + FROM → TO migration for every removed export: + + - `@objectstack/spec/contracts` — `IFeedService`, `CreateFeedItemInput`, + `UpdateFeedItemInput`, `ListFeedOptions`, `FeedListResult` → **removed, no + replacement**. Comments/activity are plain records: write `sys_comment` / read + `sys_activity` via the data engine or the REST data API. + - `@objectstack/spec/api` — `FeedApiContracts`, `FeedApiErrorCode`, + `FeedProtocol`, and all feed request/response schemas + types (`GetFeed*`, + `CreateFeedItem*`, `UpdateFeedItem*`, `DeleteFeedItem*`, `AddReaction*`, + `RemoveReaction*`, `PinFeedItem*`, `UnpinFeedItem*`, `StarFeedItem*`, + `UnstarFeedItem*`, `SearchFeed*`, `GetChangelog*`, `ChangelogEntry`, + `SubscribeRequest/Response`, `FeedUnsubscribeRequest`, `UnsubscribeResponse`, + `FeedPathParams`, `FeedItemPathParams`, `FeedListFilterType`) → **removed**. Use + the data API against `sys_comment` / `sys_activity` (`/api/v1/data/sys_comment/…`); + reactions and threaded replies are fields on `sys_comment`. + - `@objectstack/spec/data` — `FeedItemSchema`/`FeedItem`, `FeedActorSchema`/`FeedActor`, + `MentionSchema`/`Mention`, `ReactionSchema`/`Reaction`, + `FieldChangeEntrySchema`/`FieldChangeEntry`, `FeedVisibility`, + `RecordSubscriptionSchema`/`RecordSubscription`, `SubscriptionEventType`, and the + `data`-namespace `NotificationChannel` → **removed**. `FeedItemType` and + `FeedFilterMode` are **kept** (live UI activity-timeline config). For notification + channels use `NotificationChannelSchema` from `@objectstack/spec/system`. + - `@objectstack/client` — `client.feed.*` (`list` / `create` / `update` / `delete` / + `addReaction` / `removeReaction` / `pin` / `unpin` / `star` / `unstar` / `search` / + `getChangelog` / `subscribe` / `unsubscribe`) and the re-exported feed response + types → **removed**. One-line fix: use `client.data.*` on `sys_comment` / + `sys_activity`, e.g. `client.data.create('sys_comment', { object, record_id, body })` + and `client.data.find('sys_activity', { filters: [['record_id', '=', id]] })`. + - `@objectstack/metadata-protocol` — `ObjectStackProtocolImplementation` no longer + implements the 14 feed methods; its constructor + `(engine, getServicesRegistry?, getFeedService?, environmentId?)` becomes + `(engine, getServicesRegistry?, environmentId?)`. One-line fix: delete the third + argument. + +### Patch Changes + +- 8abf133: **Breaking (discovery response shape): retire the residual feed capability surface (#3180, follow-up to #1959 / ADR-0052 §5).** + + The feed backend was retired long ago; #1959 removed the feed contracts + SDK. This + removes the last discovery/dispatcher references to it, and fixes a real bug where the + `comments` capability was permanently `false`. + + - `@objectstack/spec` — `WellKnownCapabilitiesSchema.feed` and `ApiRoutesSchema.feed` + (`routes.feed`) are **removed**, and the `/api/v1/feed` entry is dropped from + `DEFAULT_DISPATCHER_ROUTES`. FROM → TO: clients reading `discovery.capabilities.feed` + or `discovery.routes.feed` → use `discovery.capabilities.comments`; comments/activity + are served by the generic data API on `sys_comment` / `sys_activity` + (`/api/v1/data/sys_comment/…`). + - `@objectstack/metadata-protocol` — `getDiscovery()` no longer emits the always-`false` + `feed` service/capability. **Bug fix:** the `comments` capability previously keyed off + the deleted `'feed'` service (so it was permanently `false` after #1955); it now tracks + the presence of the `sys_comment` object (provided by the always-on audit slate), so + `declared === enforced`. + - `@objectstack/client` — the internal `feed: '/api/v1/feed'` route constant is removed + (it only existed to satisfy the now-removed `ApiRoutes.feed` type; no client code used it). + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [e057f42] +- Updated dependencies [a3823b2] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [dd9f223] +- Updated dependencies [46e876c] +- Updated dependencies [5f05de2] +- Updated dependencies [021ba4c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [290e2f0] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + - @objectstack/core@16.0.0 + ## 16.0.0-rc.1 ### Minor Changes diff --git a/packages/client/package.json b/packages/client/package.json index 5dfd9026d7..576e53ee2e 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/client", - "version": "16.0.0-rc.1", + "version": "16.0.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 b03bb3848e..90c61a5935 100644 --- a/packages/cloud-connection/CHANGELOG.md +++ b/packages/cloud-connection/CHANGELOG.md @@ -1,5 +1,70 @@ # @objectstack/cloud-connection +## 16.0.0 + +### Patch Changes + +- Updated dependencies [b39c65d] +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [e057f42] +- Updated dependencies [a3823b2] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [fdc244e] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [dd9f223] +- Updated dependencies [46e876c] +- Updated dependencies [2ea08ee] +- Updated dependencies [d1d1c40] +- Updated dependencies [ee0a499] +- Updated dependencies [5f05de2] +- Updated dependencies [021ba4c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [83e8f7d] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [2049b6a] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [290e2f0] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [92f5f19] +- Updated dependencies [a2d6555] +- Updated dependencies [3a6310c] +- Updated dependencies [32899e6] +- Updated dependencies [515f11a] +- Updated dependencies [4174a07] +- Updated dependencies [ce468c8] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/runtime@16.0.0 + - @objectstack/spec@16.0.0 + - @objectstack/core@16.0.0 + - @objectstack/types@16.0.0 + ## 16.0.0-rc.1 ### Patch Changes diff --git a/packages/cloud-connection/package.json b/packages/cloud-connection/package.json index 5e0c79f105..6542c5ddaf 100644 --- a/packages/cloud-connection/package.json +++ b/packages/cloud-connection/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/cloud-connection", - "version": "16.0.0-rc.1", + "version": "16.0.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 cb2fa28cf3..4953d5949e 100644 --- a/packages/connectors/connector-mcp/CHANGELOG.md +++ b/packages/connectors/connector-mcp/CHANGELOG.md @@ -1,5 +1,54 @@ # @objectstack/connector-mcp +## 16.0.0 + +### Patch Changes + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [e057f42] +- Updated dependencies [a3823b2] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [dd9f223] +- Updated dependencies [46e876c] +- Updated dependencies [5f05de2] +- Updated dependencies [021ba4c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [290e2f0] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + - @objectstack/core@16.0.0 + ## 16.0.0-rc.1 ### Patch Changes diff --git a/packages/connectors/connector-mcp/package.json b/packages/connectors/connector-mcp/package.json index 3d302ea7a4..6cbe161e0b 100644 --- a/packages/connectors/connector-mcp/package.json +++ b/packages/connectors/connector-mcp/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/connector-mcp", - "version": "16.0.0-rc.1", + "version": "16.0.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 478ab4e11f..003d4efcc1 100644 --- a/packages/connectors/connector-openapi/CHANGELOG.md +++ b/packages/connectors/connector-openapi/CHANGELOG.md @@ -1,5 +1,73 @@ # @objectstack/connector-openapi +## 16.0.0 + +### Minor Changes + +- 41e703b: feat(connector-openapi): degrade + retry on an unreachable remote spec URL (#3049 follow-up) + + The `openapi` provider fetches `providerConfig.spec` when it is an http(s) URL. + That fetch previously threw plain on any failure, so a momentarily-unreachable + spec endpoint aborted the whole app boot. It now classifies the fault the same + way `connector-mcp` classifies its connect path (ADR-0097): + + - **Network error** (DNS / connection refused / timeout) or a **transient HTTP + status** (`408` / `429` / `5xx`, mirroring the `retryableStatusCodes` + convention) throws `ConnectorUpstreamUnavailableError` — the materializer + degrades the instance (`state: 'degraded'` on `GET /connectors`, dispatch + fails clearly) and retries with backoff plus on every `metadata:reloaded`. + - A **wrong URL** (non-retryable `4xx`) or an **unparseable document** stays a + plain, fatal configuration fault. + + Inline and file-path (`#3016`) specs do no boot I/O and are unaffected. + +### Patch Changes + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [e057f42] +- Updated dependencies [a3823b2] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [dd9f223] +- Updated dependencies [46e876c] +- Updated dependencies [5f05de2] +- Updated dependencies [021ba4c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [290e2f0] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + - @objectstack/core@16.0.0 + ## 16.0.0-rc.1 ### Patch Changes diff --git a/packages/connectors/connector-openapi/package.json b/packages/connectors/connector-openapi/package.json index a32b4b5fe7..554622921f 100644 --- a/packages/connectors/connector-openapi/package.json +++ b/packages/connectors/connector-openapi/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/connector-openapi", - "version": "16.0.0-rc.1", + "version": "16.0.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 bae0a2df51..b6d3e281d2 100644 --- a/packages/connectors/connector-rest/CHANGELOG.md +++ b/packages/connectors/connector-rest/CHANGELOG.md @@ -1,5 +1,54 @@ # @objectstack/connector-rest +## 16.0.0 + +### Patch Changes + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [e057f42] +- Updated dependencies [a3823b2] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [dd9f223] +- Updated dependencies [46e876c] +- Updated dependencies [5f05de2] +- Updated dependencies [021ba4c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [290e2f0] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + - @objectstack/core@16.0.0 + ## 16.0.0-rc.1 ### Patch Changes diff --git a/packages/connectors/connector-rest/package.json b/packages/connectors/connector-rest/package.json index 08f20330ab..9ed398d0be 100644 --- a/packages/connectors/connector-rest/package.json +++ b/packages/connectors/connector-rest/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/connector-rest", - "version": "16.0.0-rc.1", + "version": "16.0.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 2e489208e1..dc9abb429c 100644 --- a/packages/connectors/connector-slack/CHANGELOG.md +++ b/packages/connectors/connector-slack/CHANGELOG.md @@ -1,5 +1,54 @@ # @objectstack/connector-slack +## 16.0.0 + +### Patch Changes + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [e057f42] +- Updated dependencies [a3823b2] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [dd9f223] +- Updated dependencies [46e876c] +- Updated dependencies [5f05de2] +- Updated dependencies [021ba4c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [290e2f0] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + - @objectstack/core@16.0.0 + ## 16.0.0-rc.1 ### Patch Changes diff --git a/packages/connectors/connector-slack/package.json b/packages/connectors/connector-slack/package.json index cc0759b268..2f80062b48 100644 --- a/packages/connectors/connector-slack/package.json +++ b/packages/connectors/connector-slack/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/connector-slack", - "version": "16.0.0-rc.1", + "version": "16.0.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 6613dc713c..b1238be106 100644 --- a/packages/console/CHANGELOG.md +++ b/packages/console/CHANGELOG.md @@ -1,5 +1,128 @@ # @objectstack/console +## 16.0.0 + +### Minor Changes + +- bfa3c3f: Console (objectui) refreshed to `3b2e4d98d904`. Frontend changes in this range: + + - fix(list): route remaining system-field groupings through shared classifier (#2706) + - feat(console): user-import wizard defaults to the `auto` password policy (tracks framework#3236) (#2701) + - feat(flow-designer): schema-driven keyValue + numberList mapping (#3304) (#2708) + + objectui range: `0318118e02fd...3b2e4d98d904` + +- 39b56d0: Console (objectui) refreshed to `94d4876df090`. Frontend changes in this range: + + - feat(dashboard): Studio authors the ADR-0021 dataset shape only (framework#3251) (#2703) + - feat(app-shell): render ActionParamDialog params through the shared form field-widget renderer (#2700, ADR-0059) (#2704) + - feat(app-shell): distinguish writable system objects from engine-owned in badge + empty-state (ADR-0103 / #3220) (#2705) + - fix(list): keep injected owner_id out of leading auto-derived list columns (#2702) + - feat(flow-designer): #2670 Phase 3 — nested container node selection + schema-driven editing (#2699) + - feat(approvals-inbox): retire hardcoded secondary buttons for server-declared actions (#2697) + + objectui range: `fd45313b4d00...94d4876df090` + +- 447465a: Console (objectui) refreshed to `e164196801bd`. Frontend changes in this range: + + - fix(app-shell,plugin-detail): record History tab renders display values, not raw audit payloads (#2691) + - fix(plugin-gantt): mirror the row 「→」 slot in the task-list header (#2690) + - fix(plugin-detail): #2688 header Record-#id floor + raw audit user id in meta footer (#2689) + - feat(plugin-gantt)!: remove the mobile QR share (移动端二维码) context-menu feature (#2687) + - feat(plugin-gantt): dependencyTypes switch — hide the type switcher for id-only dependency stores (#2686) + - feat(approvals): decision attachments + progress display + deep link + designer sync (#2681) + - feat(studio): inline push-down expansion of loop/parallel/try_catch regions on the flow canvas (#2680) + - feat(plugin-gantt): ownership-aware reschedule + confirm-first auto-schedule, export fixes, business time zone (#2683) + - fix(app-shell): skip resultDialog fields whose path does not resolve (#2674) + - feat(studio): visualize loop/parallel/try_catch nested regions on the flow canvas (#2670) (#2675) + - feat(plugin-gantt): manual-scheduling summary bars, interaction switches, beforeTaskUpdate veto + tooltip/scrollbar/cursor fixes (#2677) + - fix(flow-designer): author the canonical config.schedule the runtime reads (#2671) + - feat(report): drill a date-bucket cell into its time range, not a superset (#1752) (#2672) + - feat(studio): filter editor for roll-up summary fields (framework#1868) (#2669) + - feat(flow-designer): first-class panel for the time-relative trigger (#1874) (#2668) + - feat(studio): nest per-iteration / per-region step logs in the flow Runs panel (#2667) + - fix(metadata-admin): dashboard label fallback + skill activation editors (#1878) (#2666) + + objectui range: `2e7d7f0f7ee7...e164196801bd` + +- a140ff0: Console (objectui) refreshed to `fd45313b4d00`. Frontend changes in this range: + + - feat(app-shell): DeclaredActionsBar — render server-declared object actions on bespoke pages (#2678 P2-4) (#2692) + - feat(data): unify master-detail saves behind DataSource.batchTransaction; isolate non-atomic fallback in the adapter (#2679) (#2684) + + objectui range: `e164196801bd...fd45313b4d00` + +### Patch Changes + +- a276969: Console (objectui) refreshed to `0318118e02fd`. Frontend changes in this range: + + - fix(app-shell): guard ActionParamDialog submit during file upload + map spec `autonumber` (ADR-0059 follow-ups) (#2707) + + objectui range: `94d4876df090...0318118e02fd` + +- 47d923c: Console (objectui) refreshed to `2e7d7f0f7ee7`. Frontend changes in this range: + + - feat(evaluator): route CEL-dialect component/action predicates to the canonical engine (#2664) + - fix(grid): explain the import wizard's disabled Next and silent downgrade (#2640, #2639) (#2646) + - fix(form+detail): single-file children stay inline grids; drop non-spec `attachment` (#2654, #2655) (#2656) + - feat(access): localize curated capability labels client-side (#2600 B5 follow-up) (#2657) + - feat(access): localize capability picker group headers (#2600 B5, objectui side) (#2653) + - fix(access): Studio permission matrix — stop clipping the Bulk column at narrow widths (#2600 B3) (#2652) + - feat(access): Studio permission matrix — field-level bulk + filter for wide objects (#2600 B4) (#2651) + - feat(access): Studio Explain panel — package-scoped object dropdown instead of free-text api-name (#2600 B2) (#2650) + - feat(access): Studio permission matrix — collapse identity + zero-grant capabilities so the matrix hits the first screen (#2600 B1) (#2649) + - feat(plugin-list): 列表工具栏增加手动刷新按钮 (#2634) (#2645) + - fix(studio): approver Type dropdown drops deprecated `role`, membership-tier picker (#2643) + - fix(components): route internal html-page links through the SPA navigation handler (#2642) + - feat(discovery): trust only handlerReady/available services (ADR-0076 D12) (#2637) + - feat(types)!: adopt @objectstack/spec 15.1.1; drop value-erased spec/ui `…Schema` re-exports (#2589) + - feat(console): dev-seeded admin credentials hint on the login page (#2635) + - fix(auth): 注册页去掉重复的「or」分隔线(与 #2629 登录页修复对齐) (#2633) + - feat(app-shell/react): adapt to framework 15.1 — atomic publish rendering + honest discovery (#2630) + - fix(chatbot): plan approval flips the card to a Building… badge immediately (#2632) + - fix(app-shell,components): welcome CTA deep-links into the environment create dialog (#2631) + - fix(auth): login-page config race + sign-in watchdog — never strand SSO-only users on a password wall (#2629) + - feat(types): derive ListViewSchema from @objectstack/spec/ui (#2231) (#2622) + + objectui range: `077e45b4bc55...2e7d7f0f7ee7` + +- a791200: Console (objectui) refreshed to `69fa5d163a97`. Frontend changes in this range: + + - fix(app-shell): mark notifications read via the REST surface, not direct receipt writes (#2743) + + objectui range: `af1b0db96e44...69fa5d163a97` + +- db34d54: Console (objectui) refreshed to `9a5f016f7d5c`. Frontend changes in this range: + + - feat(flow-designer): nested-array columns in the node property form (#2678 P2-5) (#2761) + - fix: redo record-list "Add View" flow — empty-name 405, invisible drafts, canonical naming (#2768) + - feat(SchemaForm): field-type-aware operators + values for view filter (#2766) + - fix(plugin-charts): draw dashboard chart bars on first paint via isAnimationActive=false (#2756) (#2759) + - feat(data-objectstack): gate non-atomic batch fallback on discovery transactionalBatch capability (#2693) (#2755) + + objectui range: `69fa5d163a97...9a5f016f7d5c` + +- 1965549: Console (objectui) refreshed to `af1b0db96e44`. Frontend changes in this range: + + - feat(i18n): localize action result dialogs via \_actions..resultDialog (#2736) + - feat(data): thread the host's authenticated fetch into provider:'api' data sources (#2725) (#2732) + - feat(managedBy): add explicit `engine-owned` lifecycle bucket (tracks framework ADR-0103 addendum, #3343) (#2739) + - feat(fields): CheckboxesField visibleWhen cascading + dependsOn gating (completes option-widget parity) (#2735) + - feat(fields): RadioField visibleWhen cascading + dependsOn gating; single-source the option resolver (#2728) + - fix(kanban,calendar): surface write failures instead of silently swallowing them (#2716) + - fix(plugin-charts): draw dashboard bars on first paint via one settle re-mount (#2727) + - feat(dashboard): retire pre-ADR-0021 inline-analytics renderer branches (framework#3320) (#2723) + - fix(data-objectstack): type the exportDownload test fetch mock so its type-check passes (#2726) + - feat(detail): related lists paginate by default with server-side $top/$skip windows (#2711) (#2722) + - fix(approvals-inbox): align participant gating with the server-computed viewer block (#2719) + - fix(plugin-view): coerce i18n tab-label helpers to string (TS2322) (#2721) + - feat(fields): MultiSelectField per-option visibleWhen cascading + dependsOn gating (#2715) (#2717) + - fix(site): make docs build resilient to remote badge fetch failures (#2695) (#2718) + - feat(approvals-inbox): retire the approve/reject composer for declared actions with file attachments (#2698) (#2710) + - feat(fields): select+multiple → multi-value chip picker; restore fields/core lint gates (#2709) + + objectui range: `3b2e4d98d904...af1b0db96e44` + ## 16.0.0-rc.1 ### Minor Changes diff --git a/packages/console/package.json b/packages/console/package.json index 5cec0d5f6e..eaab584693 100644 --- a/packages/console/package.json +++ b/packages/console/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/console", - "version": "16.0.0-rc.1", + "version": "16.0.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 2fc1fc240c..b887ace4df 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -1,5 +1,134 @@ # @objectstack/core +## 16.0.0 + +### Minor Changes + +- dd9f223: feat(analytics): scope a datetime date-bucket drill to the reference-tz midnight instants (#1752 follow-up) + + Closes the one gap left by the initial #1752 change: a `datetime` date dimension + bucketed under a **non-UTC reference timezone** previously fell back to a superset + drill (its bucket boundary is that tz's midnight _instant_, which `YYYY-MM-DD` + calendar bounds can't express). + + - **`@objectstack/core`** adds `zonedDateStartToUtcMs(ymd, tz)` — the UTC instant + at which a calendar day begins in a reference timezone (the inverse of + `calendarPartsInTz`). DST-safe: the offset is read from the platform tz + database via `Intl`, with a two-pass resolution for the rare offset-boundary + case; an unset/`'UTC'`/invalid zone returns plain UTC midnight. + - **`@objectstack/service-analytics`** now emits `drillRanges` bounds per the + field's temporal type (ADR-0053): a `datetime` field → ISO **instant** bounds + at the reference tz's midnight (works under any tz, incl. DST); a `date` field + → `YYYY-MM-DD` calendar bounds (tz-naive, exact under any tz). An unknown field + type is still emitted only under UTC and omitted (superset) under a non-UTC tz. + + No objectui change is needed — the client already forwards whatever bound values + the server sends into the drill filter and the `filter[field][gte|lt]` URL. + +- 290e2f0: feat(analytics): emit a half-open date-range drill scope for granularity-bucketed date dimensions (#1752) + + A report/dashboard cell grouped by a `dateGranularity` date dimension ("2026-Q2") + covers a SPAN of records, so drilling it needs a range (`>= start AND < nextStart`), + which the equality drill contract (`drillRawRows`) can't express — date dims were + therefore excluded from drill metadata and a drill landed on an unscoped superset. + + - **`@objectstack/core`** adds `bucketKeyToCalendarRange(key, granularity)`, the + inverse of `bucketDateValue`: it turns a canonical bucket key into its half-open + `[start, end)` calendar span (`YYYY-MM-DD`, `end` exclusive). Pure, timezone-naive + calendar arithmetic; returns `null` for unbucketable / out-of-range keys so the + caller falls back to an unscoped (superset) drill rather than emit a wrong bound. + - **`@objectstack/service-analytics`** emits a `drillRanges` sidecar (aligned to + `rows` by index — the range companion to `drillRawRows`) for `date` + + `dateGranularity` dimensions, computed from the canonical bucket key in the + pre-label-resolution snapshot pass. A `datetime` field under a non-UTC reference + timezone is omitted (host drills a superset) until instant-boundary support + lands; a tz-naive `date` field is exact under any timezone (ADR-0053). + + Consumed by objectui's report drill-through to scope the drilled record list to the + clicked time bucket. + +### Patch Changes + +- e057f42: fix: harden the bulk-write path — retries, idempotency, contracts, and summary visibility (#3147–#3152) + + Six reliability fixes to the batched seed/import + `engine.insert(array)` path + introduced by the #2678 bulk-write rework: + + - **#3151** `bulkWrite` validates that `writeBatch` returns one record per input + row (a short/long/non-array return is degraded per-row, not backfilled as + phantom success); `engine.insert(array)` likewise rejects a short driver + `bulkCreate` return instead of padding afterInsert with `undefined`. + - **#3150** wraps the two remaining un-retried write points (seed + `writeRecord`/`resolveDeferredUpdates`, import's no-`createManyData` + fallback) in `withTransientRetry`; `defaultIsTransientError` short-circuits + definitive logical errors to non-transient. + - **#3148** import `resolveRef` flushes pending creates on a same-object miss so + a later row can reference an earlier same-file CREATE, and no longer + negatively caches a miss. + - **#3149** threads an `attempt` counter through `bulkWrite`; seed rechecks by + `externalId` and import by `matchFields` before re-writing, so a + commit-then-lost-response retry cannot duplicate a batch. + - **#3147** `recomputeSummaries` retries transient failures and, on exhaustion, + surfaces `SummaryRecomputeError` (`ERR_SUMMARY_RECOMPUTE`) instead of a + silent warn; seed/import recover it to a warning without re-writing. + - **#3152** autonumbers are assigned after validation, so a batch that dies in + validation consumes no sequence value (no number-range gaps). + +- 5f05de2: **`createLogger({ file })` now actually writes the file under ESM.** `openFileStream` loaded `fs` with a lazy `require()` to keep the browser-safe logger entry out of the `fs` bundle graph; esbuild rewrites that to its `__require` shim in the ESM output, which throws `Dynamic require of "fs" is not supported`, and a bare `catch {}` swallowed it. Since the workspace is `type: module`, every Node ESM consumer — `os serve`, `os dev` — silently got no file logging at all, while the CJS build kept working. The builtin now loads via `process.getBuiltinModule` (opaque to bundlers, works in both module systems, with a `require` fallback for Node < 20.16), and a `file` destination that cannot be opened reports itself on stderr instead of disappearing. + + Turning the destination back on also fixed three faults that were unreachable while it never opened: `child()` opened a second stream per child and orphaned it, destroying a child logger closed the stream its parent and siblings were still writing to, and an async open failure (e.g. an unwritable path) hit an `'error'` event with no listener and took the process down. + +- 021ba4c: fix(core): ObjectLogger honors NO_COLOR and TTY detection before emitting ANSI colors + + The kernel/plugin logger (`ctx.logger`, wired by `os serve` / `os dev`) colorized its + `pretty`-format level tags unconditionally, so `NO_COLOR=1` runs and piped/CI output + still carried ANSI escapes (e.g. `\x1b[31m…ERROR\x1b[0m`), breaking plain-text log + scanners (see scripts/publish-smoke.sh, which had to strip ANSI before grepping). + + Per the no-color.org convention, color is now emitted only when the destination stream + (stdout, or stderr for error/fatal) is an interactive TTY **and** `NO_COLOR` is unset or + empty — any non-empty `NO_COLOR` value disables color. Interactive terminals keep the + existing colorized output. The optional file destination now always receives plain text. + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [a3823b2] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [46e876c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + ## 16.0.0-rc.1 ### Patch Changes diff --git a/packages/core/package.json b/packages/core/package.json index 22887e1cf0..effc634e48 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/core", - "version": "16.0.0-rc.1", + "version": "16.0.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 2648e4e86e..a4b971dfd5 100644 --- a/packages/create-objectstack/CHANGELOG.md +++ b/packages/create-objectstack/CHANGELOG.md @@ -1,5 +1,103 @@ # create-objectstack +## 16.0.0 + +### Minor Changes + +- 3f218e4: feat(create-objectstack): the blank scaffold ships the three generic connector executors by default + + `npm create objectstack` now generates an `objectstack.config.ts` that wires the + `rest`, `openapi`, and `mcp` connector executor plugins (ADR-0022/0023/0024 + + ADR-0097) into `plugins:`, alongside `requires: ['automation']`. This closes the + last authoring gap in the ADR-0097 promise that integrations are expressible + **and executable** as pure metadata: an author (human or AI) can now add a + declarative `connectors:` entry naming `provider: 'rest' | 'openapi' | 'mcp'` + and have it materialize into a live, dispatchable connector at boot — with no + host-code edit. + + - `plugins:` — `new ConnectorRestPlugin()`, `new ConnectorOpenApiPlugin()`, + `new ConnectorMcpPlugin()` (zero-arg = contribute the provider factory only). + - `requires: ['automation']` — the automation service performs the + materialization and owns the registry the executors register into. It is also + a hard dependency of the connector plugins, so a scaffold that lists them in + `plugins:` without it fails boot; automation ships transitively via + `@objectstack/cli`. + - deps — `@objectstack/connector-rest`, `@objectstack/connector-openapi`, + `@objectstack/connector-mcp`. + - Security (#3055): declarative `mcp` stdio transports stay denied by default — + opt in per host with `new ConnectorMcpPlugin({ declarativeStdio: ['node'] })`. + + Brand connectors (Slack, …) remain marketplace/opt-in. + +### Patch Changes + +- 83e8f7d: feat(mcp): decouple the stdio auto-start switch from the HTTP surface + surface the MCP endpoint on `os dev` boot (#3167) + + The MCP HTTP surface (`/api/v1/mcp`) and the long-lived stdio transport used to + share one env var: `OS_MCP_SERVER_ENABLED=true` turned the HTTP surface on **and** + silently auto-started the stdio transport — which bridges the raw metadata service + + - data engine with no per-request principal (unscoped). An operator setting it to + "make sure MCP is on" got an unscoped transport as a side effect. + + * **`@objectstack/types`** — new `resolveMcpStdioAutoStart()`. Stdio auto-start is + now its own switch, `OS_MCP_STDIO_ENABLED` (default off); `OS_MCP_SERVER_ENABLED` + governs only the HTTP surface. The legacy `OS_MCP_SERVER_ENABLED=true` trigger + still starts stdio for one release, flagged as deprecated. `=false` is unchanged + (it only ever gated HTTP). + * **`@objectstack/mcp`** — `MCPServerPlugin.start()` gates stdio on the new switch + and logs a one-time deprecation warning when started via the legacy alias. + * **`@objectstack/cli`** — `os dev` now prints the MCP endpoint, the agent-skill + URL, and a ready-to-paste `claude mcp add` command on boot (gated on the HTTP + surface being on), so the "an agent operates the app it's building" loop is + discoverable at dev time. + * **`create-objectstack`** — the blank scaffold README documents that the app is + itself an MCP server (the serve side), distinct from the consume-side connector. + +- 3b6ef8a: Scaffolded projects ship with a `.gitignore` again — `npx create-objectstack` produced none, leaving `node_modules/` and `.env` un-ignored for every new user. + + `npm pack` / `pnpm pack` strip `.gitignore` from a tarball unconditionally, at every depth. The blank template committed one at `src/templates/blank/.gitignore` and the build faithfully copied it to `dist/templates/blank/.gitignore`, but `files: ["dist"]` publishing dropped it on the way to the registry — so the file was present in the repo, present in every local build, and absent from all 11 files of a real scaffold. Verified against the published 15.1.1 tarball, which ships `dist/templates/blank/.dockerignore` and no `.gitignore`. + + The template is now committed as `_gitignore` (a name npm does not strip) and restored to `.gitignore` when the template is copied, via a `TEMPLATE_FILE_ALIASES` map in the new `template-copy.ts`. Only `.gitignore` is aliased: the strip list is `.gitignore` and `.npmrc`, not "every dotfile" — `.dockerignore` packs fine and stays literal. + + The restored ignore rules also cover `.env` / `.env.*`, which they never did. The template README has users write `OS_AUTH_SECRET` and `OS_SECRET_KEY` into a `.env`, and `docker-compose.yml` calls that file "never committed" — but only the prose said so, and `.dockerignore` was the only file that listed it. + + A packing ratchet in `template-consistency.test.ts` guards both halves: it packs the real package, scaffolds from the extracted tarball with the real copy logic, and asserts every template file lands under its intended name. Source-level assertions cannot see this class of bug — the file only vanishes at publish. + +- 3a8ce9d: fix(create-objectstack): the blank scaffold declares pnpm build approvals, so a fresh `pnpm install` no longer exits 1 on pnpm 11 + + pnpm 11 turned an unapproved dependency build script from a warning into a hard + error. The blank template declared no build approvals, so the very first command + a new user runs failed on any current pnpm: + + ``` + npx create-objectstack myapp && cd myapp && pnpm install + # [ERR_PNPM_IGNORED_BUILDS] Ignored build scripts: better-sqlite3@12.11.1, esbuild@0.28.1 + # exit 1 + ``` + + The scaffold now ships a `pnpm-workspace.yaml` approving the two packages it + actually depends on building — `better-sqlite3` (the native sqlite driver behind + `@objectstack/driver-sql`) and `esbuild` (compiles `objectstack.config.ts`). + + Both approval keys are present because pnpm reads them by version, and neither + alone covers the supported range: + + - `allowBuilds` (a package → boolean map) — the only key pnpm 11 honors, and + understood back to pnpm 10.31. `onlyBuiltDependencies` alone still errors. + - `onlyBuiltDependencies` (a list) — pnpm 10.0–10.30, which ignore `allowBuilds`. + + npm and yarn ignore the file, so the npm install path is unaffected. Both + packages ship prebuilt binaries, so this was an install-time hard stop rather + than a runtime defect — the project ran fine once installed. + + This is the #3091 failure class (in-repo settings masking what users resolve) + and was caught by the publish smoke gate added in #3100, which installs the + release candidate the way a user does — on whatever pnpm corepack hands a fresh + machine. + +- 809214f: Stop leaking repo-internal skills into scaffolded projects. The scaffolder (and the docs) advertised `npx skills add objectstack-ai/framework --all`, and the skills CLI's `--all` implies `--skill '*'` — which includes even `metadata.internal` skills — so repo-internal tooling like `.claude/skills/dogfood-verification` landed in every new project's `.agents/skills/`. All install commands are now scoped to the published catalog via the `/skills` subpath (`npx skills add objectstack-ai/framework/skills --all`), the internal skill is additionally marked `metadata.internal: true` to hide it from interactive discovery, and a template-consistency ratchet plus a scaffold-e2e assertion keep the boundary from regressing. + ## 16.0.0-rc.1 ## 16.0.0-rc.0 diff --git a/packages/create-objectstack/package.json b/packages/create-objectstack/package.json index 2859fcc267..92d398ef7c 100644 --- a/packages/create-objectstack/package.json +++ b/packages/create-objectstack/package.json @@ -1,6 +1,6 @@ { "name": "create-objectstack", - "version": "16.0.0-rc.1", + "version": "16.0.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 0bd1794106..b3209133aa 100644 --- a/packages/formula/CHANGELOG.md +++ b/packages/formula/CHANGELOG.md @@ -1,5 +1,192 @@ # @objectstack/formula +## 16.0.0 + +### Minor Changes + +- 6b51346: feat(formula): `dateField == today()` now matches — AST temporal-comparison rewrite (#3183) + + **Behavior change (the fix):** a `Field.date` compared with `==`/`!=` against a + temporal function now matches on the calendar day. Previously it **silently + returned the wrong answer** — `record.due_date == today()` was always `false` + (and `!= today()` always `true`) even for a same-day record, because a + `Field.date` reads back as a `YYYY-MM-DD` **string** (ADR-0053 Phase 1) and + cel-js's equality (`overloads.js` `isEqual`) treats a string and a timestamp as + unequal without consulting any overload. + + `celEngine.evaluate` now rewrites the parsed AST: for each `==`/`!=` whose one + operand is `today()`/`daysFromNow()`/`daysAgo()`/`now()`, the **field operand** + is wrapped in `date(...)` (the stdlib coercion), then the expression is + serialized and evaluated. So `record.due_date == today()` runs as + `date(record.due_date) == today()`. + + - **Per-occurrence**, not per-field: `record.d == "2026-06-20" || record.d == today()` + keeps the string-literal comparison intact while fixing the temporal one. + - **Type-blind-safe**: `date()` degrades gracefully — an already-`Date` + (`Field.datetime`) operand passes through; a non-date string or null → + `Invalid Date` → the comparison stays `false`, exactly as before. No + field-type information is needed, and no currently-correct result is worsened. + - **Cheap**: the rewrite only reserializes when such a comparison is present + (a plain-`includes` gate skips the rest), and is memoized per source string. + + Applies to every interpreter site — read-time `Field.formula`, default values, + validation rules, hook conditions, and flow conditions — since all route through + `celEngine.evaluate`. RLS/sharing conditions are unaffected: they compile via + `cel-to-filter`, which already rejects function calls as a loud authoring error. + + **Supersedes the #3192 advisory lint.** That build-time warning + (`checkTemporalDateEquality`) flagged `dateField == today()` as a silent-miss; + with the runtime fixed it would be a false alarm, so it (and the + `temporalEqualityFields` helper it used) is removed. Authors can now write the + natural `record.due_date == today()` directly; the `date(...)` / + `daysBetween(...) == 0` / range idioms all keep working. + +- 80273c8: feat(formula): warn when a `date` field is compared to a temporal function with `==`/`!=` (#3183) + + A `Field.date` deserializes as a `YYYY-MM-DD` **string** (ADR-0053 Phase 1), and + cel-js's equality hard-codes `string == ` to `false` — it returns + `false` for a string left operand without ever consulting a registered overload, + and refuses cross-type object equality (`@marcbachmann/cel-js` `overloads.js` + `isEqual`). So the most natural "is it due today" predicate — + + ```cel + record.due_date == today() // silently false, even when due_date IS today + record.due_date != today() // silently true for a same-day record + ``` + + — compiles clean, throws nothing, and silently never matches. Same silent-miss + family as #1928; **timezone-independent** (fails identically at UTC) and + cross-cutting (formulas, validation, RLS, flow/action/sharing/hook predicates). + + cel-js gives no operator-layer hook to fix the comparison, so this adds a + **build-time advisory warning** (the established ADR-0032 guardrail strategy) + rather than a runtime behavior change. `validateExpression` reuses the shared + `ExprSchemaHint.fieldTypes` (the same per-field type map the #1928 tier-4 + soundness check already threads through `@objectstack/lint`) to flag a `==`/`!=` + between a `date` field (`record.`/`previous.`/bare) and + `today()`/`daysFromNow()`/`daysAgo()`/`now()`, with a self-correcting message + pointing at the working idioms: `date(record.d) == today()`, a range + (`>= … && <= …`), or `daysBetween(today(), record.d) == 0`. + + Warning severity — never fails the build (the write/validation path may carry a + real `Date`). Restricted to `type: 'date'` (unambiguously a string); `datetime` + is excluded to avoid false positives. Ordering operators (`>=`/`<=`/`<`/`>`) + already work — cel-js _throws_ for them, tripping the engine's existing + string-hydration retry — so they are not flagged. + + A runtime fix (normalizing the peer of a temporal operand in the data layer) + remains tracked in #3183; a naive "hydrate date fields to `Date`" version would + trade this silent-miss for another (breaking `dateField == "2026-06-20"`), so it + needs its own design. + +- 7125007: **Stored `Field.formula` fields that compute dates/durations no longer silently evaluate to `null` (#3306).** Three independent CEL gaps made shipped template formulas (e.g. `hr_employee.tenure_years`, `hr_time_off_request.days`) return `null` with no parse/build/runtime error: + + 1. **The null-guard idiom `cond ? : null` now compiles and evaluates.** cel-js's ternary type-unifier rejects a concrete `int`/`double`/`string` branch against `null` — so even `true ? 5 : null` faulted _"Ternary branches must have the same type"_ and the whole formula nulled. A `Field.formula` is inherently nullable and the catalog blesses both ternary and `== null`, so this is the canonical "compute value, else blank" shape. An AST pre-pass (mirroring the #3183 temporal-equality rewrite) wraps the non-null branch in `dyn(...)` — value-preserving, null-branch-only, idempotent — so it type-checks and runs. Applied in `compile()`, `evaluate()`, and the build soundness check alike. + + 2. **`floor(x)` / `ceil(x)` are now registered** (parallel to `round`/`abs`) and advertised in the catalog. They round toward −∞ / +∞, so `floor(-1.2) == -2` — NOT interchangeable with integer division's round-toward-zero. Previously `floor(...)` faulted `found no matching overload` and the formula nulled. + + 3. **Date arithmetic is now a build-time ERROR instead of a silent runtime `null`.** `record.end_date - record.start_date + 1`, `today() + 30`, `record.date + n` type-check clean (operands are `dyn`) but always fault at runtime and never recover (a date string is not numeric, so hydration can't rescue it). The build soundness check now types `date`/`datetime` fields as `google.protobuf.Timestamp` and flags date/duration **arithmetic against a number** with a corrective message pointing at `daysBetween(a, b)` / `daysFromNow(n)` / `addDays(d, n)` / `addMonths(d, n)`. Sound by construction — ordering (`date < today()`, `date < "2026-01-01"` string-lex), equality (#3183), and string concatenation (`"Due: " + date`) are all runtime-tolerated and never flagged; only arithmetic against a number is. A `!= null` guard on a date field no longer masks the inner fault (`== null` no-op overloads registered in the check-only env). + + > **Heads-up for downstream:** (3) adds a NEW build-time error. A stored formula or predicate doing arithmetic on a `date`/`datetime` field (`end - start + 1`, `today() + 30`) that previously built (and nulled at runtime) will now fail `objectstack build` / `validateStackExpressions` with a message telling you to use `daysBetween` / `daysFromNow` / `addDays`. This only fires for genuinely-broken expressions that already returned `null`. + + Fixes #3306. + +- ea32ec7: feat(formula,lint): advisory type-soundness warnings for formula/predicate expressions (#1928 tier 4) + + Closes the last open guardrail from #1928. A `Field.formula` or record-scoped + predicate that uses a **text or boolean field with an arithmetic (`+ - * / %`) + or ordering (`< > <= >=`) operator against a number** faults the runtime + overload and silently evaluates to `null` (e.g. `record.title * 2`, + `record.is_active + 1`). The build now surfaces this as a **non-blocking + warning** with the offending field and a corrective message. + + Honours the ADR-0032 design law — the checker only flags what the runtime + would also fail: + + - Number / currency / percent / date / datetime fields are declared `dyn`, so + the cases the runtime rescues never warn — `record.amount / 100` (the #1930 + `registerOperator` fix), `record.due == today()` and numeric-string / ISO-date + values (the string-hydration retry), and numeric-coded `select` option values. + - Equality (`==` / `!=`) is excluded: a heterogeneous equality is runtime-safe + (evaluates to `false`), never a fault. + + New `firstTypeMismatch(source, fieldCelTypes, scope)` export in + `@objectstack/formula` (and an optional `fieldTypes` hint on + `validateExpression`); `@objectstack/lint`'s `validateStackExpressions` threads + each object's field types into every checked site: + + - **record-scoped** sites (`record.`) — formula fields, validation rules, + action / hook / sharing predicates; + - **flattened** flow / automation conditions (bare `field`) — where flow + variables stay `dyn` and are never flagged, and equality stays runtime-safe. + + Warnings are advisory in `objectstack build` / `validate` (fatal only under + `--strict`), matching the tier-3 channel. + +### Patch Changes + +- e0859b1: fix(formula): retire the `js` expression dialect and fix the `hasDialect` false-positive (#3278) + + The `js` **expression** dialect was declared in `ExpressionDialect` but never + shipped — it existed only as a registry stub with no engine and no author helper + (`cel`/`F`/`P` → CEL, `tmpl` → template, `cron` → cron; nothing ever emitted + `js`). Per ADR-0049 (enforce-or-remove) it is removed from the enum; the set is + now `{cel, cron, template}`. + + Procedural JavaScript is unaffected: it remains the **L2** authoring surface — + the sandboxed, capability-gated `ScriptBody { language: 'js' }` in hook/action + bodies — which is a separate enum (`hook-body.zod.ts`), not an expression + dialect. + + Also fixes a latent bug in `hasDialect`: it detected stubs via + `dialect.startsWith('stub:')`, but stubs were registered under their real name, + so the check was dead code and `hasDialect('js')` returned a false-positive + `true`. With the stub removed, `hasDialect` reports only registered real + engines, and the registry test now asserts the negative case (`hasDialect('js') +=== false`) so the gate can actually go red. + + No runtime behavior changes for any valid persisted artifact — no producer ever + emitted `dialect: 'js'`. See the ADR-0058 addendum. + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [a3823b2] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [46e876c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + ## 16.0.0-rc.1 ### Minor Changes diff --git a/packages/formula/package.json b/packages/formula/package.json index 06386223cd..ea57d339e9 100644 --- a/packages/formula/package.json +++ b/packages/formula/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/formula", - "version": "16.0.0-rc.1", + "version": "16.0.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 64c01b0962..860a945036 100644 --- a/packages/lint/CHANGELOG.md +++ b/packages/lint/CHANGELOG.md @@ -1,5 +1,213 @@ # @objectstack/lint +## 16.0.0 + +### Minor Changes + +- 3a18b60: feat(approvals): rename the `role` approver type to `org_membership_level` (#3133) + + `ApproverType.role` was the last platform surface projecting the reserved word + "role" (ADR-0090 D3). It is not covered by D3's better-auth exception: that + exception protects better-auth's own `sys_member.role` **column**, which we do + not own — `ApproverType` is our own enum, an authoring surface, and D3 mandates + that the projection of that concept is spelled `org_membership_level` and + labelled "organization membership", **never "role"**. + + The sentence licensing the leak was also false: ADR-0090 D3 claims + `sys_member.role` is "already relabelled `org_membership_level` in the platform + projection", but `org_membership_level` existed nowhere in the codebase and + ADR-0057 D7 lists that relabel under "Deferred (evidence-gated, P4)". The + projection never landed, so the word reached authors. + + The name manufactured a real, silent failure — "hotcrm class": every other + surface renamed to `position` (`sys_role`, `ShareRecipientType.role`, + `ctx.roles[]`), so `{ type: 'role', value: 'sales_manager' }` reads as the + legacy spelling of a position. It resolves against the membership tier, finds + no member row, falls back to an inert `role:sales_manager` literal, and the + request waits forever on an approver that cannot exist. + + - **spec**: `ApproverType` gains `org_membership_level`; `role` stays as a + deprecated alias for one window (a published 15.x flow keeps loading) with + `DEPRECATED_APPROVER_TYPES` + `canonicalApproverType()` as the single source + for the mapping. Removed in the next major. + - **plugin-approvals**: resolves on the canonical type and warns on the + deprecated spelling. The `type:value` fallback literal keeps the **authored** + spelling — stored `sys_approval_approver` rows and `pending_approvers` slots + from 15.x carry `role:`, and rewriting it would orphan them. + - **lint**: `approval-role-not-membership-tier` → `approval-approver-not-membership-tier` + (the rule id carried the reserved word too), plus a new + `approval-approver-type-deprecated`. The two are mutually exclusive: a bad + _value_ wins, because prescribing `org_membership_level` for a position name + would be wrong advice — the fix there is `position`. + + Authoring `type: 'role'` keeps working and now says so out loud. Rewrite it as + `org_membership_level`; if the value is an org position, the fix is `position`. + +- 2ea08ee: Flow trigger observability — kill the four-layer silence around record-change flows that never fire (2026-07-17 third-party eval). + + A misauthored auto-launched flow (wrong `objectName`, missing `requires: ['automation','triggers']`, failing start condition) produced ZERO output at every layer: the engine's own registration/binding logs land inside the CLI's boot-quiet stdout window (which swallows debug/info/warn — only error/fatal reach stderr), and each "didn't happen" path was itself silent. Fixes: + + - **Startup banner `Flows:` section** (`os serve`/`os dev`/`os start`): flow count, bound-to-trigger count, registered trigger types, draft count — plus loud `⚠` lines for flows declared with no automation engine enabled (`requires` missing), flows whose trigger type has no registered trigger, and bound record-change flows targeting an unknown object (dead binding). Printed after stdout is restored, so it is immune to the boot-quiet window. + - **Trigger-fired run failures now log at ERROR** (stderr — always visible): the automation engine no longer drops the AutomationResult of a trigger-fired execution; condition-evaluation faults and node failures surface with the flow name. Condition-not-met skips stay at debug (high-frequency, intentional). + - **`RecordChangeTrigger` probes object existence at bind time** and warns when a flow's `objectName` matches no registered object (exact-name matching), instead of silently arming a hook that can never fire. + - **`kernel:bootstrapped` binding audit** in the automation plugin: warns per enabled-but-unbound triggered flow with the reason, and reports registered/bound/draft counts (`AutomationEngine.getTriggerBindingAudit()`, extended `getFlowRuntimeStates()` with `status`/`triggerType`/`object`). + - **`os validate` flow-wiring advisories** (`@objectstack/lint` `validateFlowTriggerReadiness`): warns when a record-triggered flow targets an object the stack does not define, and when an auto-triggered flow's status is `draft` (authored or defaulted — draft flows still fire; declare `active` or `obsolete`). + - Removed leftover boot-debug writes (`registerApp`/`AppPlugin`/`StandaloneStack`/`AuditPlugin` stderr noise) that previous debugging of this same silence had left behind. + +- ea32ec7: feat(formula,lint): advisory type-soundness warnings for formula/predicate expressions (#1928 tier 4) + + Closes the last open guardrail from #1928. A `Field.formula` or record-scoped + predicate that uses a **text or boolean field with an arithmetic (`+ - * / %`) + or ordering (`< > <= >=`) operator against a number** faults the runtime + overload and silently evaluates to `null` (e.g. `record.title * 2`, + `record.is_active + 1`). The build now surfaces this as a **non-blocking + warning** with the offending field and a corrective message. + + Honours the ADR-0032 design law — the checker only flags what the runtime + would also fail: + + - Number / currency / percent / date / datetime fields are declared `dyn`, so + the cases the runtime rescues never warn — `record.amount / 100` (the #1930 + `registerOperator` fix), `record.due == today()` and numeric-string / ISO-date + values (the string-hydration retry), and numeric-coded `select` option values. + - Equality (`==` / `!=`) is excluded: a heterogeneous equality is runtime-safe + (evaluates to `false`), never a fault. + + New `firstTypeMismatch(source, fieldCelTypes, scope)` export in + `@objectstack/formula` (and an optional `fieldTypes` hint on + `validateExpression`); `@objectstack/lint`'s `validateStackExpressions` threads + each object's field types into every checked site: + + - **record-scoped** sites (`record.`) — formula fields, validation rules, + action / hook / sharing predicates; + - **flattened** flow / automation conditions (bare `field`) — where flow + variables stay `dyn` and are never flagged, and equality stays runtime-safe. + + Warnings are advisory in `objectstack build` / `validate` (fatal only under + `--strict`), matching the tier-3 channel. + +- a2795f6: feat(triggers): declarative time-relative trigger — daily sweep instead of fragile date-equality (#1874) + + Time-relative business rules ("alert 60 days before a contract's `end_date`") + could only be expressed as a `record_change` flow gated on a date-equality + condition like `end_date == daysFromNow(60)`. That predicate is only evaluated + when the record _happens to change_, so it fires only if a record is edited on + exactly the threshold day — i.e. almost never, unattended. The robust + alternative was a hand-written cron + range query that every author + re-implemented (contracts `renewal_alert`, hr `document_expiring_soon`, + procurement `po_overdue`, …). + + A flow's start node can now declare a `timeRelative` descriptor instead: + + ```ts + config: { + timeRelative: { + object: 'contracts', + dateField: 'end_date', + offsetDays: [60, 30, 7], // T-minus reminders — fires on each threshold day + // — or — withinDays: 30 // "expiring soon" range; negative = overdue lookback + filter: { status: 'active' }, // optional, ANDed with the date window + }, + schedule: { type: 'cron', expression: '0 8 * * *' }, // optional; defaults to daily 08:00 UTC + } + ``` + + The new `time_relative` trigger (shipped in `@objectstack/trigger-schedule` as + `TimeRelativeTriggerPlugin`) sweeps the object on that schedule and launches the + flow **once per matching record**, with the record on the automation context — + so the start-node `condition` gate and `{record.}` interpolation work + exactly as for a record-change flow. Because the window is evaluated every day, + a threshold is never missed regardless of when the record last changed. The + discovery query runs as a system operation (RLS-bypassing) and is capped + (`maxRecords`, default 1000) so a mis-scoped window can't fan out unboundedly; + per-record failures are isolated so one bad row never aborts the sweep. + + The automation engine routes a start node carrying `config.timeRelative` to the + `time_relative` trigger (ahead of the plain `schedule` trigger, whose behavior is + unchanged), and `os validate` gains readiness checks for the new descriptor + (unknown swept object, ambiguous draft status). New authorable spec key: + `TimeRelativeTriggerSchema` (`@objectstack/spec/automation`). + +### Patch Changes + +- 524696a: feat(spec)!: `DashboardWidgetSchema.strict()` — reject undeclared widget keys (framework#3251) + + The ADR-0021 analytics endpoint. `DashboardWidgetSchema` now rejects any + undeclared top-level key instead of silently stripping it, moving a whole class + of author error (a hallucinated or legacy key that renders as a silent no-op) + from fallible human review to deterministic CI. `options: z.unknown()` remains + the escape hatch for renderer-specific extras. + + A custom error map names the offending key(s) and, when a key is a removed + pre-ADR-0021 inline-analytics key (`object` / `categoryField` / `valueField` / + `aggregate`, pivot `rowField` / `columnField`) or an objectui-internal prop + (`component`, inline `data`), points the author at the dataset shape + (`dataset` + `dimensions` + `values`). + + Recorded as protocol-16 migration `step16` + (`dashboard-widget-strict-unknown-keys`), mirroring protocol-15's `step15` + strict flip on the form/page schemas (ADR-0089 D3a). The inline-analytics shape + itself was already removed at protocol 9 (single-form cutover), so there is no + mechanical rewrite — the residue is the strictness, delegated to the author. + + **Breaking:** shipped as `minor` per the launch-window policy (a breaking change + does not burn a major while the stack is in lockstep), riding the already-pending + 16.0.0 train. The release train's Version-Packages PR must set + `PROTOCOL_VERSION = '16.0.0'`; until then `step16` is inert + (`composeMigrationChain` caps at `PROTOCOL_MAJOR`). + + `@objectstack/lint` — the `widget-legacy-analytics-shape` / + `widget-legacy-analytics-unrenderable` rules are retained as the friendly, + suppressible bridge on the raw-config lint/doctor paths (strict preempts them on + the schema-parsed compile/validate paths); doc comment updated to explain the + interplay. + +- 8923843: Reject view containers that define no views. A flat list-view object (`{ name, label, type, columns, ... }`) parses to an empty `ViewSchema` container because Zod strips unknown keys — zero views register and the Console silently renders nothing. `defineView()` now throws on a zero-view container, and `os validate` gains a `view-container-shape` check (`validateViewContainers` in `@objectstack/lint`) that reports flat or empty `views: []` entries pre-parse with a wrap-it fix hint. +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [a3823b2] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [6b51346] +- Updated dependencies [80273c8] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [46e876c] +- Updated dependencies [7125007] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [ea32ec7] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + - @objectstack/formula@16.0.0 + - @objectstack/sdui-parser@16.0.0 + ## 16.0.0-rc.1 ### Patch Changes diff --git a/packages/lint/package.json b/packages/lint/package.json index 78a56dfc84..6953b24426 100644 --- a/packages/lint/package.json +++ b/packages/lint/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/lint", - "version": "16.0.0-rc.1", + "version": "16.0.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 a8a27ece08..ab6456535c 100644 --- a/packages/mcp/CHANGELOG.md +++ b/packages/mcp/CHANGELOG.md @@ -1,5 +1,136 @@ # @objectstack/plugin-mcp-server +## 16.0.0 + +### Minor Changes + +- 15dbe18: feat(mcp)!: stdio transport requires an API-key principal — fail-closed, no unscoped bridge (ADR-0101, #3246) + + The long-lived MCP **stdio** transport no longer reads data unscoped. It now runs + under an env-supplied identity, closing the platform's last identity-less + execution surface (the `mcp-stdio-authority` conformance row graduates + `experimental` → `enforced`). + + - `OS_MCP_STDIO_API_KEY=osk_...` supplies the stdio identity, resolved through + the SAME `@objectstack/core` verify + authorization chain as the HTTP/REST + surfaces; the `record_by_id` resource reads via `ql.find(obj, { where:{id}, +context })`, so RLS/FLS/tenant apply exactly as on REST `/data`. Re-resolved + per read, so a revoked/expired key stops working on a live session. + - **Fail-closed** — enabling stdio auto-start (`OS_MCP_STDIO_ENABLED=true` / + `autoStart`) without a resolvable key throws and refuses to start. There is no + unscoped fallback and deliberately no `system` bypass; full authority is a key + minted on a platform-admin or dedicated service identity. + + **BREAKING (stdio auto-start only):** previously `OS_MCP_STDIO_ENABLED=true` + (or the plugin `autoStart` option) started stdio with full, unscoped authority + and no credential. It now requires `OS_MCP_STDIO_API_KEY`; without it, boot + fails closed. The default-on HTTP surface and any deployment that never enables + stdio auto-start are unaffected. + +- 83e8f7d: feat(mcp): decouple the stdio auto-start switch from the HTTP surface + surface the MCP endpoint on `os dev` boot (#3167) + + The MCP HTTP surface (`/api/v1/mcp`) and the long-lived stdio transport used to + share one env var: `OS_MCP_SERVER_ENABLED=true` turned the HTTP surface on **and** + silently auto-started the stdio transport — which bridges the raw metadata service + + - data engine with no per-request principal (unscoped). An operator setting it to + "make sure MCP is on" got an unscoped transport as a side effect. + + * **`@objectstack/types`** — new `resolveMcpStdioAutoStart()`. Stdio auto-start is + now its own switch, `OS_MCP_STDIO_ENABLED` (default off); `OS_MCP_SERVER_ENABLED` + governs only the HTTP surface. The legacy `OS_MCP_SERVER_ENABLED=true` trigger + still starts stdio for one release, flagged as deprecated. `=false` is unchanged + (it only ever gated HTTP). + * **`@objectstack/mcp`** — `MCPServerPlugin.start()` gates stdio on the new switch + and logs a one-time deprecation warning when started via the legacy alias. + * **`@objectstack/cli`** — `os dev` now prints the MCP endpoint, the agent-skill + URL, and a ready-to-paste `claude mcp add` command on boot (gated on the HTTP + surface being on), so the "an agent operates the app it's building" loop is + discoverable at dev time. + * **`create-objectstack`** — the blank scaffold README documents that the app is + itself an MCP server (the serve side), distinct from the consume-side connector. + +- 230358c: feat(mcp): `validate_expression` tool — validate a CEL expression against a schema before authoring (#1928) + + Adds an agent-callable MCP tool that runs the same build-time expression checks + as `objectstack build`, so an AI can validate a formula / predicate / flow + condition **while authoring** instead of shipping one that silently evaluates to + `null`. Given `{ objectName, expression, site? }` it resolves the object's real + schema (field names + types, via the principal-bound `describeObject` bridge) + and returns: + + - **errors** — bare field refs (`amount` → `record.amount`), unknown fields + (with a did-you-mean), unknown functions; + - **warnings** — text/boolean fields misused in arithmetic, date-equality + pitfalls; + - **inScope** — the fields, stdlib functions, and namespace roots available, so + the model can self-correct; + - **inferredType** for a `formula` site. + + `site` (`formula` | `validation` | `flow_condition` | `template`, default + `formula`) maps to the validator's role + scope — `flow_condition` binds fields + bare, the rest bind `record.`. Read-only, gated by the `data:read` OAuth + scope, and fail-closed on `sys_*` objects like the other schema tools. This is + the authoring-time surface the guardrail series (#1928) always pointed at; + `@objectstack/mcp` gains a `@objectstack/formula` dependency (acyclic; formula is + a leaf). + +### Patch Changes + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [e057f42] +- Updated dependencies [a3823b2] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [6b51346] +- Updated dependencies [80273c8] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [dd9f223] +- Updated dependencies [46e876c] +- Updated dependencies [7125007] +- Updated dependencies [5f05de2] +- Updated dependencies [021ba4c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [83e8f7d] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [290e2f0] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [92f5f19] +- Updated dependencies [32899e6] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [ea32ec7] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + - @objectstack/core@16.0.0 + - @objectstack/formula@16.0.0 + - @objectstack/types@16.0.0 + ## 16.0.0-rc.1 ### Patch Changes diff --git a/packages/mcp/package.json b/packages/mcp/package.json index 4499e98879..6141a09508 100644 --- a/packages/mcp/package.json +++ b/packages/mcp/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/mcp", - "version": "16.0.0-rc.1", + "version": "16.0.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 fdccb95d46..59c466d5ad 100644 --- a/packages/metadata-core/CHANGELOG.md +++ b/packages/metadata-core/CHANGELOG.md @@ -1,5 +1,85 @@ # @objectstack/metadata-core +## 16.0.0 + +### Patch Changes + +- 62a2117: **Split the overloaded `managedBy: 'system'` bucket with an explicit `engine-owned` value (ADR-0103 addendum, #3343).** ADR-0103 deferred the enum split ("revisitable later as a rename") because a new `managedBy` value would fall through to the fully-editable `platform` default on deployed Console clients. Both reasons against it are now retired — the server-side write guard / `apiMethods` reconciliation / `/me/permissions` clamp make that fallthrough cosmetic (the write is rejected regardless of what the client renders), and objectui#2712 closed the UI union — so v16 lands it, **additively**. + + - **New enum value `engine-owned`** with the same all-locked default affordance row as `system` (`create/import/edit/delete: false`, `exportCsv: true`). It joins `ENGINE_OWNED_BUCKETS` (the engine write guard) and `GUARDED_WRITE_BUCKETS` (the `/me/permissions` clamp); the guard, `reconcileManagedApiMethods`, and the clamp mechanisms are unchanged — `engine-owned` is an explicit member of the set they already covered by resolved affordance. + - **20 objects relabelled `system → engine-owned`** — the ones the engine owns end to end and that declared no write-opening `userActions` (the metadata store, jobs, approval runtime rows, sharing rows, `sys_automation_run`, the messaging delivery/receipt pipeline, `sys_secret`, settings). One-line, behaviour-identical per object. + - **8 admin/user-writable objects keep `managedBy: 'system'`** (the RBAC link tables, `sys_user_preference`, `sys_approval_delegation`, the messaging config grids) — `system` now reads as "engine-managed schema, writable via `userActions`". + + Behaviour-, enforcement- and wire-identical: resolved affordances, the guard verdict, the 405 `apiMethods` reconciliation, and the permissions clamp are the same before and after — this is a self-documenting relabel, not a policy change. No data migration (`managedBy` is schema metadata) and no code branches on the `'system'` literal. Retiring the overloaded `system` entirely (moving the 8 writable objects to a dedicated bucket) is a breaking rename deferred to v17. + +- 06cb319: fix(identity): close the generic-write apiMethods hole on sys_presence and sys_metadata (#3220) + + Follow-through on #1591/#3213 (better-auth apiMethods reconciliation) for two + non-better-auth managed objects that shipped the same contradiction: their + `enable.apiMethods` advertised generic `create`/`update`/`delete` while their + `managedBy` bucket forbids user-context writes, leaving the generic `/data` + route open to a write the bucket does not permit. + + - `sys_presence` (`managedBy: 'append-only'`) advertised `create`/`update`/`delete` + (update/delete on an append-only object at that) but is written only over the + realtime websocket/in-memory path, never through ObjectQL. Narrowed to + `['get', 'list']`. + - `sys_metadata` (`managedBy: 'system'`) advertised full CRUD but customization + overlays are authored only through the metadata-protocol RPC (engine writes + carry a transaction context, not a user session); neither the framework nor + the Console (objectui) POSTs `/data/sys_metadata`. Narrowed to `['get', 'list']`. + + Reads stay open. The metadata-protocol / realtime write paths are engine-level + and bypass the HTTP exposure gate, so they are unaffected — verified by the + metadata-authoring dogfood and the objectql overlay tests. + + A blast-radius audit confirmed the broader `system`/`append-only` buckets are NOT + safe to guard wholesale: several `system` objects (`sys_user_position`, + `sys_user_permission_set`, `sys_position_permission_set`, `sys_user_preference`, + `sys_import_job`) are legitimately user-writable by design (delegated + administration, user preferences, imports). Generalizing the engine write guard + to those buckets is intentionally NOT done here — see #3220 for the bucket-taxonomy + root cause. + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [a3823b2] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [46e876c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + ## 16.0.0-rc.1 ### Patch Changes diff --git a/packages/metadata-core/package.json b/packages/metadata-core/package.json index c28103cef8..c0dc2ad75a 100644 --- a/packages/metadata-core/package.json +++ b/packages/metadata-core/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/metadata-core", - "version": "16.0.0-rc.1", + "version": "16.0.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 1b8334110a..43d05a3a0b 100644 --- a/packages/metadata-fs/CHANGELOG.md +++ b/packages/metadata-fs/CHANGELOG.md @@ -1,5 +1,13 @@ # @objectstack/metadata-fs +## 16.0.0 + +### Patch Changes + +- Updated dependencies [62a2117] +- Updated dependencies [06cb319] + - @objectstack/metadata-core@16.0.0 + ## 16.0.0-rc.1 ### Patch Changes diff --git a/packages/metadata-fs/package.json b/packages/metadata-fs/package.json index e7b8e5b362..f3ad4d1eb6 100644 --- a/packages/metadata-fs/package.json +++ b/packages/metadata-fs/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/metadata-fs", - "version": "16.0.0-rc.1", + "version": "16.0.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 d58e96e2d8..637f8be570 100644 --- a/packages/metadata-protocol/CHANGELOG.md +++ b/packages/metadata-protocol/CHANGELOG.md @@ -1,5 +1,259 @@ # @objectstack/metadata-protocol +## 16.0.0 + +### Minor Changes + +- bfa3c3f: **Broadcast a `transactionalBatch` capability bit in discovery so clients negotiate the atomic cross-object batch declaratively, instead of runtime-probing 404/405/501 (#3298).** + + The atomic cross-object batch endpoint (`POST {basePath}/batch`, #1604 / ADR-0034 item 4) and its typed SDK surface (`client.data.batchTransaction`, #3271) already shipped, but discovery never told a client whether a backend actually supports it. Consumers (notably ObjectUI's `ObjectStackAdapter`) had to _probe_: fire a `/batch`, read `404`/`405` (no route) or `501` (no runtime transaction), and only then fall back to non-atomic client-side simulation. That is "find out by calling", not capability negotiation — it cannot be decided at connect time and cannot serve as the "minimum backend supports `/batch`" gate that blocks hard-deleting the non-atomic fallback downstream. + + `WellKnownCapabilitiesSchema` gains a required `transactionalBatch: boolean`, and **every** discovery producer fills it honestly (`declared === enforced`), so it never becomes a declared-but-unpopulated bit: + + - **`@objectstack/metadata-protocol`** (`getDiscovery`) — reports whether the runtime engine can honour a transaction (`typeof engine.transaction === 'function'`). The `/batch` handler runs its ops inside `engine.transaction()`, which degrades to a non-atomic passthrough (or 501) without one. + - **`@objectstack/rest`** (`/discovery`) — ANDs the engine signal with whether it actually mounts the route (`api.enableBatch`), so a server with batch disabled reports `false` even on a transaction-capable engine (never advertise an endpoint that would 404). + - **`@objectstack/plugin-hono-server`** (standalone discovery) — reports `false`: this minimal surface registers CRUD only and does not mount `/batch` (that ships with `@objectstack/rest`). Under-reporting is the safe direction — a client keeps its correct-but-slower fallback rather than losing atomicity. + - **`@objectstack/client`** — already normalizes hierarchical `capabilities` to flat booleans, so `client.capabilities.transactionalBatch` is exposed (and now typed) for declarative consumers. + + The bit follows the existing capability semantics: `true` ⟺ the `/batch` route is mounted **and** the runtime can honour a transaction — the exact condition under which the endpoint returns `200` rather than `404`/`405`/`501`. Additive and behavior-preserving; only the discovery payload gains a field. + +- 668dd17: **Breaking (npm type surface): retire the vestigial feed contracts + protocol surface (ADR-0052 §5 follow-up, #1959).** + + The `service-feed` runtime was deleted in #1955; `sys_comment` / `sys_activity` + are the canonical record-collaboration/timeline backend. This removes the dead + type surface that still pointed at the deleted runtime — every removed method was + already unreachable (the feed REST route was never mounted → 404; the protocol + implementation was never wired with a feed service, so `requireFeedService()` + could only throw). No behavior changes. + + No authorable metadata key is removed (the `feeds:` object capability flag and + the `RecordActivity` UI component config are unchanged), so `PROTOCOL_MAJOR` + stays 15 and this ships as `minor` rather than a protocol major. + + FROM → TO migration for every removed export: + + - `@objectstack/spec/contracts` — `IFeedService`, `CreateFeedItemInput`, + `UpdateFeedItemInput`, `ListFeedOptions`, `FeedListResult` → **removed, no + replacement**. Comments/activity are plain records: write `sys_comment` / read + `sys_activity` via the data engine or the REST data API. + - `@objectstack/spec/api` — `FeedApiContracts`, `FeedApiErrorCode`, + `FeedProtocol`, and all feed request/response schemas + types (`GetFeed*`, + `CreateFeedItem*`, `UpdateFeedItem*`, `DeleteFeedItem*`, `AddReaction*`, + `RemoveReaction*`, `PinFeedItem*`, `UnpinFeedItem*`, `StarFeedItem*`, + `UnstarFeedItem*`, `SearchFeed*`, `GetChangelog*`, `ChangelogEntry`, + `SubscribeRequest/Response`, `FeedUnsubscribeRequest`, `UnsubscribeResponse`, + `FeedPathParams`, `FeedItemPathParams`, `FeedListFilterType`) → **removed**. Use + the data API against `sys_comment` / `sys_activity` (`/api/v1/data/sys_comment/…`); + reactions and threaded replies are fields on `sys_comment`. + - `@objectstack/spec/data` — `FeedItemSchema`/`FeedItem`, `FeedActorSchema`/`FeedActor`, + `MentionSchema`/`Mention`, `ReactionSchema`/`Reaction`, + `FieldChangeEntrySchema`/`FieldChangeEntry`, `FeedVisibility`, + `RecordSubscriptionSchema`/`RecordSubscription`, `SubscriptionEventType`, and the + `data`-namespace `NotificationChannel` → **removed**. `FeedItemType` and + `FeedFilterMode` are **kept** (live UI activity-timeline config). For notification + channels use `NotificationChannelSchema` from `@objectstack/spec/system`. + - `@objectstack/client` — `client.feed.*` (`list` / `create` / `update` / `delete` / + `addReaction` / `removeReaction` / `pin` / `unpin` / `star` / `unstar` / `search` / + `getChangelog` / `subscribe` / `unsubscribe`) and the re-exported feed response + types → **removed**. One-line fix: use `client.data.*` on `sys_comment` / + `sys_activity`, e.g. `client.data.create('sys_comment', { object, record_id, body })` + and `client.data.find('sys_activity', { filters: [['record_id', '=', id]] })`. + - `@objectstack/metadata-protocol` — `ObjectStackProtocolImplementation` no longer + implements the 14 feed methods; its constructor + `(engine, getServicesRegistry?, getFeedService?, environmentId?)` becomes + `(engine, getServicesRegistry?, environmentId?)`. One-line fix: delete the third + argument. + +### Patch Changes + +- e057f42: fix: harden the bulk-write path — retries, idempotency, contracts, and summary visibility (#3147–#3152) + + Six reliability fixes to the batched seed/import + `engine.insert(array)` path + introduced by the #2678 bulk-write rework: + + - **#3151** `bulkWrite` validates that `writeBatch` returns one record per input + row (a short/long/non-array return is degraded per-row, not backfilled as + phantom success); `engine.insert(array)` likewise rejects a short driver + `bulkCreate` return instead of padding afterInsert with `undefined`. + - **#3150** wraps the two remaining un-retried write points (seed + `writeRecord`/`resolveDeferredUpdates`, import's no-`createManyData` + fallback) in `withTransientRetry`; `defaultIsTransientError` short-circuits + definitive logical errors to non-transient. + - **#3148** import `resolveRef` flushes pending creates on a same-object miss so + a later row can reference an earlier same-file CREATE, and no longer + negatively caches a miss. + - **#3149** threads an `attempt` counter through `bulkWrite`; seed rechecks by + `externalId` and import by `matchFields` before re-writing, so a + commit-then-lost-response retry cannot duplicate a batch. + - **#3147** `recomputeSummaries` retries transient failures and, on exhaustion, + surfaces `SummaryRecomputeError` (`ERR_SUMMARY_RECOMPUTE`) instead of a + silent warn; seed/import recover it to a warning without re-writing. + - **#3152** autonumbers are assigned after validation, so a batch that dies in + validation consumes no sequence value (no number-range gaps). + +- 0e41302: fix(metadata-protocol): unscoped metadata list dedupes package-aware, not by bare name (ADR-0048 #1828) + + `getMetaItems` merged registry items, `sys_metadata` overlay rows, draft-preview + rows, and MetadataService items into `Map`s keyed by bare `name`, so two installed + packages shipping the same `type/name` (e.g. `page/home`) collapsed to one row + (last-write-wins) on an unscoped `GET /meta/:type` whenever either package had an + overlay — and the frontend prefer-local resolution, which reads that list, could + no longer tell the two packages' rows apart. + + The three merge sites (plus the env/org pre-merge) now key by `(package, name)`, + mirroring `getMetaItem`'s scoped-then-global-fallback resolution: colliding rows + stay distinct each with its own `_packageId`, a package-less (env-wide) overlay + still wins over the single artifact it customizes (ADR-0005 precedence and + single-package behaviour unchanged), and the registry-hydration artifact graft is + scoped to each row's own `package_id` so a collision no longer mislabels provenance. + +- b8a21ad: Publish/discard package drafts in the draft's own org scope, fixing `no_draft` after saving a draft via Studio. + + Studio "Save Draft" (`PUT /meta/:type/:name?mode=draft`) never threads the session's `activeOrganizationId`, so the draft row is written env-wide (`organization_id = NULL`). "Publish" (`POST /packages/:id/publish-drafts`) resolves the active org and passed it to `promoteDraft`, which looked the draft up with a strict `organization_id = ` equality — so it 404'd (`[no_draft] No pending draft exists …`) on the env-wide row it could never match, even though `listDrafts` had already surfaced that draft to the publish CTA (PR #1852's `$or`). `discardPackageDrafts` had the same latent gap. + + `listDrafts` now projects each draft's own `organizationId`, and `publishPackageDrafts` / `discardPackageDrafts` promote / delete each draft in that scope (env-wide stays env-wide, per-org stays per-org). Seed-body capture and the ADR-0067 revert-plan pre-state read are scoped the same way. + + Fixes #3115. + +- beaf2de: fix(metadata-protocol): strip static `readonly` on INSERT at the data-write ingress (#3043) + + #2948/#3003 made static `readonly: true` fields server-enforced on UPDATE (a + non-system PATCH forging `approval_status: 'approved'` is silently stripped in + the engine), but INSERT was exempt. For approval/status/verdict columns that + exemption was the _shorter_ attack: instead of the #3003 draft-then-PATCH move, a + non-system caller could `POST` a record already `approval_status: 'approved'` in + one step — and the UPDATE-only strip never reached it. + + The strip now also runs on INSERT, but at the **external data-write ingress** + (`DataProtocol.createData` / `createManyData` / `batchData` / `cloneData`) rather + than in the engine. That seam is the single point every external programmatic + create funnels through — the REST CRUD route, the GraphQL/MCP dispatcher + (`bridge.create` → `callData` → `createData`), and bulk import — while **trusted + internal writers** (better-auth's adapter, the metadata repository, the seed + loader) call `engine.insert` directly and bypass it. Enforcing at the ingress + protects every caller/agent path at once without stripping the internal writers + that legitimately seed read-only columns on create (identity provisioning, + provenance stamps, event-log cursors) — the blast radius an engine-level insert + strip would have. + + - **Caller-forged only, at the ingress.** The payload here is raw caller input + (the security middleware stamps `owner_id` / `organization_id` later, inside + `engine.insert`), so only keys the caller actually sent are dropped; server + stamps are added afterwards and are unaffected. + - **Re-derives the default.** A stripped field falls back to its declared + `defaultValue` in the engine (a forged `approval_status` becomes `draft`, not + NULL). + - **System-context exempt.** `isSystem` writes still seed read-only columns. + - **Silent** (HTTP 2xx), per-row on batch/import. `readonlyWhen` stays + INSERT-exempt (a conditional lock needs a prior record). + - **Author-defined business objects only.** Platform objects (`managedBy` set, + or the `sys_` namespace) carry their own field-write governance that a silent + strip must not pre-empt — e.g. ADR-0086 REJECTS (403) a forged + `managed_by:'package'` on `sys_permission_set`, and #3004 rejects a forged + `owner_id`; several of those columns are `readonly`, so stripping them here + would swallow the payload the guard is meant to reject. The #3043 threat is app + approval/status fields, never `sys_` — the same boundary `applySystemFields` + uses for ownership. + + Behavior change: a non-system create through the data API (REST / GraphQL / MCP / + import) can no longer seed a `readonly` column from the payload. Flows that + legitimately write read-only columns at creation must run with a system context + (`isSystem`), the same requirement the UPDATE strip already imposes. + +- 8abf133: **Breaking (discovery response shape): retire the residual feed capability surface (#3180, follow-up to #1959 / ADR-0052 §5).** + + The feed backend was retired long ago; #1959 removed the feed contracts + SDK. This + removes the last discovery/dispatcher references to it, and fixes a real bug where the + `comments` capability was permanently `false`. + + - `@objectstack/spec` — `WellKnownCapabilitiesSchema.feed` and `ApiRoutesSchema.feed` + (`routes.feed`) are **removed**, and the `/api/v1/feed` entry is dropped from + `DEFAULT_DISPATCHER_ROUTES`. FROM → TO: clients reading `discovery.capabilities.feed` + or `discovery.routes.feed` → use `discovery.capabilities.comments`; comments/activity + are served by the generic data API on `sys_comment` / `sys_activity` + (`/api/v1/data/sys_comment/…`). + - `@objectstack/metadata-protocol` — `getDiscovery()` no longer emits the always-`false` + `feed` service/capability. **Bug fix:** the `comments` capability previously keyed off + the deleted `'feed'` service (so it was permanently `false` after #1955); it now tracks + the presence of the `sys_comment` object (provided by the always-on audit slate), so + `declared === enforced`. + - `@objectstack/client` — the internal `feed: '/api/v1/feed'` route constant is removed + (it only existed to satisfy the now-removed `ApiRoutes.feed` type; no client code used it). + +- 515f11a: fix(seed): replaying seeds no longer corrupts lookup natural keys on the upsert update path + + Every dev-server restart replayed package seeds in upsert mode, and any record whose + lookup/master_detail was authored as a natural key could have that reference overwritten + with NULL on the update path (`NOT NULL constraint failed` on required columns; silent + link loss on nullable ones). Four fixes: + + - An unresolved reference now leaves the column untouched (deferred to pass 2) or drops + the record loudly — it is never written as NULL over an existing row. + - DB-side reference resolution probes the target dataset's declared `externalId` (e.g. + `email`) before falling back to `name` and `id`, matching how in-memory resolution + already keyed records. + - A rejected update (e.g. a `state_machine` rule vetoing the replay) no longer severs + natural-key resolution for downstream child datasets. + - Replays are idempotent: an upsert/update whose declared fields already match the + existing row is skipped instead of rewritten (no more `updated_at` churn or lifecycle + re-validation on every boot). + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [e057f42] +- Updated dependencies [a3823b2] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [6b51346] +- Updated dependencies [80273c8] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [dd9f223] +- Updated dependencies [46e876c] +- Updated dependencies [7125007] +- Updated dependencies [5f05de2] +- Updated dependencies [021ba4c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [83e8f7d] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [beaf2de] +- Updated dependencies [06cb319] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [290e2f0] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [92f5f19] +- Updated dependencies [32899e6] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [ea32ec7] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + - @objectstack/core@16.0.0 + - @objectstack/formula@16.0.0 + - @objectstack/metadata-core@16.0.0 + - @objectstack/types@16.0.0 + ## 16.0.0-rc.1 ### Minor Changes diff --git a/packages/metadata-protocol/package.json b/packages/metadata-protocol/package.json index 21dfd27d1c..89d4643441 100644 --- a/packages/metadata-protocol/package.json +++ b/packages/metadata-protocol/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/metadata-protocol", - "version": "16.0.0-rc.1", + "version": "16.0.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 0e67c3798c..c05ba9d4c7 100644 --- a/packages/metadata/CHANGELOG.md +++ b/packages/metadata/CHANGELOG.md @@ -1,5 +1,79 @@ # @objectstack/metadata +## 16.0.0 + +### Patch Changes + +- fdc244e: Dev-loop DX fixes from the 15.1 third-party evaluation (P2 batch): + + - **Hot-added objects are now queryable without a restart.** Adding a `*.object.ts` under `os dev` used to recompile "green" while every query answered `no such table` (or `not registered`) until a manual restart: the artifact reload never notified the ObjectQL registry, tables were only created at boot, and seeds only loaded from the boot-time bundle. The `metadata:reloaded` payload now carries the parsed artifact; ObjectQL ingests the object definitions and re-runs the idempotent schema sync (same `skipSchemaSync` opt-out as boot), and the runtime loads seeds for first-seen objects (dev, single-tenant). `os dev` also prints `✚ new object(s): …` on recompile. + - **Dev admin credentials stay visible.** The `os dev` startup banner only showed `admin@objectos.ai / admin123` on the boot that actually seeded it; with the persistent default DB every later boot hid it, and the Console login page never knew it existed. The hint now re-arms on every dev boot for as long as the account still verifies against the default password, and `GET /api/v1/auth/config` exposes a dev-gated `devSeedAdmin` field (never present outside `NODE_ENV=development`) so the login page can show it. + - **`os doctor` reference analysis understands current metadata shapes.** Objects bound through `defineView` containers (`list`/`listViews`/`form`/`formViews` → `data.object`, subform `childObject`, lookup form fields) and app navigation (`objectName`, nested `children`, `areas`) were reported as "defined but not referenced". The collector now walks the canonical shapes (plus flow node `config.object`/`objectName`) and the orphan-view check descends into containers. + +- d2723e2: **`MetadataManager.register()` / `unregister()` now announce to `subscribe()` watchers.** Both updated the registry, persisted to writable loaders and published to realtime, but never fired the watch callbacks — so `subscribe()` looked like it covered every write while silently missing all of them. Only the `saveMetaItem` path (via the repository watch stream) and the filesystem watcher ever reached a subscriber. Runtime consumers that cache metadata — notably ObjectQL's SchemaRegistry bridge, the component that decides what is queryable — went stale on every other write until the process restarted. + + Announcing is now the **default**, so a new call site is correct without knowing this contract exists. This is a contract fix rather than a bug fix: the one live behavior change is that runtime datasource writes (`datasource-admin`) now reach the HMR SSE stream, which subscribes to every registered type. `unregisterPackage()` / `bulkUnregister()` also announce their deletes now — correct, but latent, since neither has a production caller today. + + Bulk ingest opts out explicitly with the new `MetadataWriteOptions` (`{ notify: false }`) — boot-time filesystem priming, artifact ingest, and ObjectQL's registry bridge, each of which either runs before consumers cache anything or announces the whole batch once (as the artifact reload path does via `metadata:reloaded`). The bridge in particular MUST stay silent: it copies objects out of the SchemaRegistry, and announcing would feed them back through a handler that re-registers under `_packageId ?? 'metadata-service'`, overwriting the true package provenance of every object whose body carries no `_packageId`. + + Additive only — `register(type, name, data)` and `unregister(type, name)` keep working unchanged. + + Fixes #3112. + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [e057f42] +- Updated dependencies [a3823b2] +- Updated dependencies [bc65105] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [dd9f223] +- Updated dependencies [46e876c] +- Updated dependencies [5f05de2] +- Updated dependencies [021ba4c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [83e8f7d] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [beaf2de] +- Updated dependencies [06cb319] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [290e2f0] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [92f5f19] +- Updated dependencies [32899e6] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + - @objectstack/platform-objects@16.0.0 + - @objectstack/core@16.0.0 + - @objectstack/metadata-core@16.0.0 + - @objectstack/types@16.0.0 + - @objectstack/metadata-fs@16.0.0 + ## 16.0.0-rc.1 ### Patch Changes diff --git a/packages/metadata/package.json b/packages/metadata/package.json index 140f310fe3..6bcf023e03 100644 --- a/packages/metadata/package.json +++ b/packages/metadata/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/metadata", - "version": "16.0.0-rc.1", + "version": "16.0.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 143f5405d6..6ba97fc3b6 100644 --- a/packages/objectql/CHANGELOG.md +++ b/packages/objectql/CHANGELOG.md @@ -1,5 +1,300 @@ # @objectstack/objectql +## 16.0.0 + +### Major Changes + +- 6c270a6: **BREAKING: remove the deprecated `ctx.session.tenantId` / `ctx.user.tenantId` alias from the hook & action authoring surface — converge on `organizationId` (#3290).** + + #3280 made `organizationId` the blessed developer-facing name for the caller's active org across the JS authoring surface and kept `tenantId` as a `@deprecated` alias carrying the identical value. That alias is now **removed** from the hook `ctx.session`, the action-body `ctx.session`, and the action-body `ctx.user`. Read the caller's active org under the single blessed name: + + ```diff + - const org = ctx.session.tenantId; // hook or action body + + const org = ctx.user?.organizationId ?? ctx.session?.organizationId; + ``` + + **FROM → TO migration** (in any `*.hook.ts` / `*.action.ts` body): + + - `ctx.session.tenantId` → `ctx.session.organizationId` + - `ctx.user.tenantId` (action body) → `ctx.user.organizationId` + + The value is unchanged — `organizationId` is the same active-org id, matching the `organization_id` column and `current_user.organizationId` in RLS/sharing. `ctx.user` is `undefined` for system / unauthenticated writes, so read `ctx.session?.organizationId` when a hook or action must work regardless of a resolved user. + + What changed internally: + + - **`@objectstack/spec`** — `HookContextSchema.session` drops the `tenantId` field (only `organizationId` remains). A stray `tenantId` on a constructed session is now stripped by the schema. + - **`@objectstack/objectql`** — the engine's `buildSession()` no longer emits `session.tenantId`; the audit-stamp plugin sources the `tenant_id` column from `session.organizationId`. + - **`@objectstack/runtime`** — `buildActionSession()` and the REST action `ctx.user` no longer emit `tenantId`. + - **`@objectstack/trigger-record-change`** — reads `session.organizationId` (was `session.tenantId`) when forwarding the writer's org to a `runAs:'user'` flow; behavior is identical. + + **Explicit non-goal (unchanged):** the generic **driver-layer** tenancy abstraction is _not_ touched — `ExecutionContext.tenantId`, `DriverOptions.tenantId`, `SqlDriver.applyTenantScope` / `TenancyConfig.tenantField`, and `ExecutionLog.tenantId`. That isolation column is configurable and legitimately carries an _environment_ id in database-per-tenant kernels; it is a distinct axis from the developer-facing org. The build-time `check:org-identifier` guard now also covers `packages/**` to keep reference bodies off the removed name. + +### Minor Changes + +- 22013aa: **Split the overloaded `managedBy: 'system'` bucket into engine-owned vs. admin-writable, and enforce engine-owned writes (ADR-0103, #3220).** The `system` bucket conflated two incompatible write policies: rows a platform service owns end to end (never user-written), and platform-defined schema whose rows are legitimately admin/user-writable. It carried the same all-false affordance row as `better-auth`/`append-only` but, unlike `better-auth`, had no engine enforcement — a wildcard admin could raw-write these rows through the generic data API (ADR-0049 gap). + + Rather than add a new `managedBy` enum value (which would fall through to fully-editable `platform` defaults on already-deployed Console clients), the write policy is now the **resolved affordance** (`resolveCrudAffordances` = bucket default + `userActions`), and _engine-owned_ is defined as a `system`/`append-only` object that grants no write: + + - **Writable set declares `userActions`** — the RBAC link tables (`sys_user_position`, `sys_user_permission_set`, `sys_position_permission_set`), `sys_user_preference`, `sys_approval_delegation`, and the messaging config grids (`sys_notification_preference` / `…_subscription` / `…_template`) now declare `userActions: { create, edit, delete: true }`. The affordance is a declaration only — the `DelegatedAdminGate` / RLS / permission sets remain the authz. + - **Engine-owned objects locked to reads** — `apiMethods: ['get','list']` added where absent (jobs, notifications, approval request/approver/token/action, `sys_record_share`, `sys_automation_run`, mail/settings/secret audit, the messaging delivery pipeline). `sys_secret` is explicitly read-locked (an empty `apiMethods` array fails open). + - **`sys_import_job`** stays engine-owned: the REST import route now writes its job rows `isSystem`-elevated (attribution preserved via the explicit `created_by` stamp) and the object is locked to `['get','list']`. + - **New engine write guard** (`assertEngineOwnedWriteAllowed`, plugin-security) fail-closed rejects user-context generic writes to engine-owned `system`/`append-only` objects, keyed off the resolved affordance; `isSystem` and context-less engine/service writes bypass by construction. Wired into the security middleware alongside the other data-layer gates. + - **`reconcileManagedApiMethods`** (objectql registry) now runs for **every** managed bucket, not just `better-auth`: any advertised write verb an object's resolved affordances forbid is stripped at registration with a warning (the drift backstop, ADR-0049). + - **`/me/permissions` clamp** (plugin-hono-server) now clamps `system`/`append-only` as well as `better-auth`, so the client hint reflects `permission ∩ guard`. + + **Potentially breaking:** a downstream/third-party `system` object that advertised generic write verbs relying on today's fail-open behaviour will have those verbs stripped (with a warning) and user-context generic writes to it rejected. Declare `userActions` opening the verbs the object legitimately takes from a user context. `better-auth` keeps plugin-auth's identity write guard unchanged; the row-level `managed_by` provenance vocabulary (ADR-0066) is a different axis and is untouched. + +- 04ecd4e: feat(validation): `state_machine.initialStates` enforces the FSM entry point on INSERT (#3165) + + A `state_machine` rule's `transitions` only governs UPDATE — on INSERT the rule + was a no-op, and a `select` field permits ANY declared option as the initial + value. So a record could be born mid-flow (created already `approved`), skipping + the whole state machine. This was the gap #3043's mitigation idea assumed didn't + exist (declared ≠ enforced, ADR-0049). + + `state_machine` rules gain an optional `initialStates: string[]` — the states a + record may be CREATED in. When set, an insert whose (defaulted) state-field value + is outside the list is rejected server-side with `code: 'invalid_initial_state'`. + Omit it to keep the legacy behavior (no initial-state check on insert). A missing + / empty value is left to required-validation; `transitions` (UPDATE) is + unaffected. Enforced at the same `evaluateValidationRules(..., 'insert')` seam the + engine already runs after field defaults. + +- 4d5a892: feat(objectql): roll-up `summary` fields can filter which child rows they aggregate (#1868) + + `summaryOperations` gains an optional `filter` — a query `where` FilterCondition + evaluated against each child row, so a summary aggregates only the matching + children instead of the whole collection. This is what lets a single child object + feed several distinct parent totals, which the cross-object rollup templates need: + + ```typescript + // One `engagement` child → distinct filtered totals. + total_signups: { + type: 'summary', + summaryOperations: { object: 'engagement', field: 'id', function: 'count', filter: { type: 'signup' } }, + } + // Sum only received receipt lines (3-way match). + received_amount: { + type: 'summary', + summaryOperations: { object: 'procurement_receipt', field: 'amount', function: 'sum', filter: { status: 'received' } }, + } + ``` + + The engine ANDs the predicate with the parent-FK match when it recomputes, and + because the whole filtered aggregate is re-run on every child write, a child that + moves in or out of the predicate (e.g. a status change) keeps the parent current + with no extra wiring. Operator and compound forms work too + (`filter: { type: { $in: ['signup', 'trial'] }, amount: { $gte: 100 } }`). + + Purely additive: omitting `filter` aggregates every child exactly as before. + +### Patch Changes + +- a8aa34c: Enforce validation rules, `requiredWhen`, and per-option `visibleWhen` on multi-row updates (#3106). The bulk branch of `engine.update` (`options.multi` → `driver.updateMany`) previously never called `evaluateValidationRules`, so every object-level rule (`script`, `state_machine`, `format`, `cross_field`, `json_schema`, `conditional`), field-level `requiredWhen`, and per-option `visibleWhen` check was a silent no-op there. The engine now reads the row-scoped match set (the same AST the write binds, one query shared with the `readonlyWhen` bulk strip) and evaluates the payload against each matched row's prior state; any error-severity violation rejects the whole batch with `ValidationError` (annotated with the failing record id) before anything is written. Schemas needing no prior state (`format`/`json_schema`-only) are evaluated once against the payload with no fetch, and rule-free schemas are unaffected. Behavior change: bulk writes that previously slipped past declared rules now throw. Doc comments in `rule-validator.ts` and `validation.zod.ts` no longer overstate coverage and name the remaining `events: ['delete']` gap (tracked separately). +- e057f42: fix: harden the bulk-write path — retries, idempotency, contracts, and summary visibility (#3147–#3152) + + Six reliability fixes to the batched seed/import + `engine.insert(array)` path + introduced by the #2678 bulk-write rework: + + - **#3151** `bulkWrite` validates that `writeBatch` returns one record per input + row (a short/long/non-array return is degraded per-row, not backfilled as + phantom success); `engine.insert(array)` likewise rejects a short driver + `bulkCreate` return instead of padding afterInsert with `undefined`. + - **#3150** wraps the two remaining un-retried write points (seed + `writeRecord`/`resolveDeferredUpdates`, import's no-`createManyData` + fallback) in `withTransientRetry`; `defaultIsTransientError` short-circuits + definitive logical errors to non-transient. + - **#3148** import `resolveRef` flushes pending creates on a same-object miss so + a later row can reference an earlier same-file CREATE, and no longer + negatively caches a miss. + - **#3149** threads an `attempt` counter through `bulkWrite`; seed rechecks by + `externalId` and import by `matchFields` before re-writing, so a + commit-then-lost-response retry cannot duplicate a batch. + - **#3147** `recomputeSummaries` retries transient failures and, on exhaustion, + surfaces `SummaryRecomputeError` (`ERR_SUMMARY_RECOMPUTE`) instead of a + silent warn; seed/import recover it to a warning without re-writing. + - **#3152** autonumbers are assigned after validation, so a batch that dies in + validation consumes no sequence value (no number-range gaps). + +- a3823b2: Collapse the hook event taxonomy from 18 declared events to the 8 the engine actually dispatches (#3195). The removed 10 (`beforeFindOne`/`afterFindOne`, `beforeCount`/`afterCount`, `beforeAggregate`/`afterAggregate`, `beforeUpdateMany`/`afterUpdateMany`, `beforeDeleteMany`/`afterDeleteMany`) were declared in `HookEvent` but never fired — the enum mirrored the engine method table instead of domain events, so a hook subscribing to them registered fine and then silently no-op'd. + + - `findOne` now fires the same `beforeFind`/`afterFind` hooks as `find` — the read event attaches to record materialization, not the engine method, so one subscription covers every read shape (no separate `beforeFindOne`/`afterFindOne`). + - Bulk (`multi: true`) updates/deletes already fire the singular `beforeUpdate`/`beforeDelete`/`afterUpdate`/`afterDelete` events with the row-scoping predicate in `ctx.input.ast`; this is now documented, and there is no `*Many` event. + - Read authorization / row filtering is the RLS/permission-rule layer's job and field masking is field-level metadata — neither is a hook every author must re-attach. + - `engine.registerHook` now warns when a hook subscribes to an event the engine never dispatches, so enum-vs-dispatch drift can't recur silently. + + No shipped hook or authored metadata used any of the removed events; authoring one now fails loudly at parse/validate time instead of registering a dead hook. Skills and docs updated to teach the 8 events and the declarative alternatives. + +- fdc244e: Dev-loop DX fixes from the 15.1 third-party evaluation (P2 batch): + + - **Hot-added objects are now queryable without a restart.** Adding a `*.object.ts` under `os dev` used to recompile "green" while every query answered `no such table` (or `not registered`) until a manual restart: the artifact reload never notified the ObjectQL registry, tables were only created at boot, and seeds only loaded from the boot-time bundle. The `metadata:reloaded` payload now carries the parsed artifact; ObjectQL ingests the object definitions and re-runs the idempotent schema sync (same `skipSchemaSync` opt-out as boot), and the runtime loads seeds for first-seen objects (dev, single-tenant). `os dev` also prints `✚ new object(s): …` on recompile. + - **Dev admin credentials stay visible.** The `os dev` startup banner only showed `admin@objectos.ai / admin123` on the boot that actually seeded it; with the persistent default DB every later boot hid it, and the Console login page never knew it existed. The hint now re-arms on every dev boot for as long as the account still verifies against the default password, and `GET /api/v1/auth/config` exposes a dev-gated `devSeedAdmin` field (never present outside `NODE_ENV=development`) so the login page can show it. + - **`os doctor` reference analysis understands current metadata shapes.** Objects bound through `defineView` containers (`list`/`listViews`/`form`/`formViews` → `data.object`, subform `childObject`, lookup form fields) and app navigation (`objectName`, nested `children`, `areas`) were reported as "defined but not referenced". The collector now walks the canonical shapes (plus flow node `config.object`/`objectName`) and the orphan-view check descends into containers. + +- 2ea08ee: Flow trigger observability — kill the four-layer silence around record-change flows that never fire (2026-07-17 third-party eval). + + A misauthored auto-launched flow (wrong `objectName`, missing `requires: ['automation','triggers']`, failing start condition) produced ZERO output at every layer: the engine's own registration/binding logs land inside the CLI's boot-quiet stdout window (which swallows debug/info/warn — only error/fatal reach stderr), and each "didn't happen" path was itself silent. Fixes: + + - **Startup banner `Flows:` section** (`os serve`/`os dev`/`os start`): flow count, bound-to-trigger count, registered trigger types, draft count — plus loud `⚠` lines for flows declared with no automation engine enabled (`requires` missing), flows whose trigger type has no registered trigger, and bound record-change flows targeting an unknown object (dead binding). Printed after stdout is restored, so it is immune to the boot-quiet window. + - **Trigger-fired run failures now log at ERROR** (stderr — always visible): the automation engine no longer drops the AutomationResult of a trigger-fired execution; condition-evaluation faults and node failures surface with the flow name. Condition-not-met skips stay at debug (high-frequency, intentional). + - **`RecordChangeTrigger` probes object existence at bind time** and warns when a flow's `objectName` matches no registered object (exact-name matching), instead of silently arming a hook that can never fire. + - **`kernel:bootstrapped` binding audit** in the automation plugin: warns per enabled-but-unbound triggered flow with the reason, and reports registered/bound/draft counts (`AutomationEngine.getTriggerBindingAudit()`, extended `getFlowRuntimeStates()` with `status`/`triggerType`/`object`). + - **`os validate` flow-wiring advisories** (`@objectstack/lint` `validateFlowTriggerReadiness`): warns when a record-triggered flow targets an object the stack does not define, and when an auto-triggered flow's status is `draft` (authored or defaulted — draft flows still fire; declare `active` or `obsolete`). + - Removed leftover boot-debug writes (`registerApp`/`AppPlugin`/`StandaloneStack`/`AuditPlugin` stderr noise) that previous debugging of this same silence had left behind. + +- d2723e2: **`MetadataManager.register()` / `unregister()` now announce to `subscribe()` watchers.** Both updated the registry, persisted to writable loaders and published to realtime, but never fired the watch callbacks — so `subscribe()` looked like it covered every write while silently missing all of them. Only the `saveMetaItem` path (via the repository watch stream) and the filesystem watcher ever reached a subscriber. Runtime consumers that cache metadata — notably ObjectQL's SchemaRegistry bridge, the component that decides what is queryable — went stale on every other write until the process restarted. + + Announcing is now the **default**, so a new call site is correct without knowing this contract exists. This is a contract fix rather than a bug fix: the one live behavior change is that runtime datasource writes (`datasource-admin`) now reach the HMR SSE stream, which subscribes to every registered type. `unregisterPackage()` / `bulkUnregister()` also announce their deletes now — correct, but latent, since neither has a production caller today. + + Bulk ingest opts out explicitly with the new `MetadataWriteOptions` (`{ notify: false }`) — boot-time filesystem priming, artifact ingest, and ObjectQL's registry bridge, each of which either runs before consumers cache anything or announces the whole batch once (as the artifact reload path does via `metadata:reloaded`). The bridge in particular MUST stay silent: it copies objects out of the SchemaRegistry, and announcing would feed them back through a handler that re-registers under `_packageId ?? 'metadata-service'`, overwriting the true package provenance of every object whose body carries no `_packageId`. + + Additive only — `register(type, name, data)` and `unregister(type, name)` keep working unchanged. + + Fixes #3112. + +- 674457a: **Enforce per-option `visibleWhen` on `checkboxes` fields, and match option values by string form (objectui#2729).** Server-side per-option gating already covered `select` / `multiselect` / `radio`, but two holes let gated values through on write: + + - **`checkboxes` was not enforced.** `CHOICE_FIELD_TYPES` omitted `checkboxes`, so a gated `checkboxes` option (whose client widget cascades identically to `multiselect` since objectui#2715) was hidden in the UI but accepted from a crafted write. Added `checkboxes` to the enforced set — its picked values are now re-evaluated against each option's `visibleWhen` (record + `current_user`) on insert/update/bulk-update, element-wise, like `multiselect`. + - **Numeric option values could slip the gate.** Option matching used strict `===`, but the enum-membership validator compares by `String(...)`. A numeric option value submitted as a string (a normal REST/JSON round-trip) passed the enum check yet missed its `visibleWhen` gate (fail-open). Matching now coerces both sides with `String(...)`, so the two validators agree on which option a written value denotes. + + Behavior for `select` / `multiselect` / `radio` is unchanged. Fail-open on unbound `current_user` / unevaluable predicates is preserved. + +- 668dd17: **Breaking (npm type surface): retire the vestigial feed contracts + protocol surface (ADR-0052 §5 follow-up, #1959).** + + The `service-feed` runtime was deleted in #1955; `sys_comment` / `sys_activity` + are the canonical record-collaboration/timeline backend. This removes the dead + type surface that still pointed at the deleted runtime — every removed method was + already unreachable (the feed REST route was never mounted → 404; the protocol + implementation was never wired with a feed service, so `requireFeedService()` + could only throw). No behavior changes. + + No authorable metadata key is removed (the `feeds:` object capability flag and + the `RecordActivity` UI component config are unchanged), so `PROTOCOL_MAJOR` + stays 15 and this ships as `minor` rather than a protocol major. + + FROM → TO migration for every removed export: + + - `@objectstack/spec/contracts` — `IFeedService`, `CreateFeedItemInput`, + `UpdateFeedItemInput`, `ListFeedOptions`, `FeedListResult` → **removed, no + replacement**. Comments/activity are plain records: write `sys_comment` / read + `sys_activity` via the data engine or the REST data API. + - `@objectstack/spec/api` — `FeedApiContracts`, `FeedApiErrorCode`, + `FeedProtocol`, and all feed request/response schemas + types (`GetFeed*`, + `CreateFeedItem*`, `UpdateFeedItem*`, `DeleteFeedItem*`, `AddReaction*`, + `RemoveReaction*`, `PinFeedItem*`, `UnpinFeedItem*`, `StarFeedItem*`, + `UnstarFeedItem*`, `SearchFeed*`, `GetChangelog*`, `ChangelogEntry`, + `SubscribeRequest/Response`, `FeedUnsubscribeRequest`, `UnsubscribeResponse`, + `FeedPathParams`, `FeedItemPathParams`, `FeedListFilterType`) → **removed**. Use + the data API against `sys_comment` / `sys_activity` (`/api/v1/data/sys_comment/…`); + reactions and threaded replies are fields on `sys_comment`. + - `@objectstack/spec/data` — `FeedItemSchema`/`FeedItem`, `FeedActorSchema`/`FeedActor`, + `MentionSchema`/`Mention`, `ReactionSchema`/`Reaction`, + `FieldChangeEntrySchema`/`FieldChangeEntry`, `FeedVisibility`, + `RecordSubscriptionSchema`/`RecordSubscription`, `SubscriptionEventType`, and the + `data`-namespace `NotificationChannel` → **removed**. `FeedItemType` and + `FeedFilterMode` are **kept** (live UI activity-timeline config). For notification + channels use `NotificationChannelSchema` from `@objectstack/spec/system`. + - `@objectstack/client` — `client.feed.*` (`list` / `create` / `update` / `delete` / + `addReaction` / `removeReaction` / `pin` / `unpin` / `star` / `unstar` / `search` / + `getChangelog` / `subscribe` / `unsubscribe`) and the re-exported feed response + types → **removed**. One-line fix: use `client.data.*` on `sys_comment` / + `sys_activity`, e.g. `client.data.create('sys_comment', { object, record_id, body })` + and `client.data.find('sys_activity', { filters: [['record_id', '=', id]] })`. + - `@objectstack/metadata-protocol` — `ObjectStackProtocolImplementation` no longer + implements the 14 feed methods; its constructor + `(engine, getServicesRegistry?, getFeedService?, environmentId?)` becomes + `(engine, getServicesRegistry?, environmentId?)`. One-line fix: delete the third + argument. + +- 86d30af: fix(tenancy): platform-global (`tenancy.enabled:false`) objects are never driver-org-scoped (#3249) + + An org-context read of a platform-global object (e.g. `sys_license`, ADR-0066) + could return 0 rows for an authenticated caller while an anonymous read saw the + data: the engine stamped `execCtx.tenantId` into driver options unconditionally, + and the SQL driver's tenant-field cache could be re-corrupted to + `organization_id` by a partial re-registration (lifecycle archive `syncSchema`, + schema-drift re-sync) whose schema omitted the `tenancy` block. + + - New `isTenancyDisabled(schema)` export from `@objectstack/spec/data` — the + single source of truth for the ADR-0066 platform-global posture, now shared by + the registry (tenant-column injection), the ObjectQL engine, and the SQL + driver. + - `ObjectQL.buildDriverOptions` no longer stamps `tenantId` for objects whose + registered schema declares `tenancy.enabled: false` (an explicitly-passed + options `tenantId` still wins — deliberate caller intent). + - `SqlDriver` (and `SqliteWasmDriver`) now keep a sticky record of an explicit + `tenancy.enabled:false` declaration: a later registration without a `tenancy` + block preserves the opt-out instead of re-scoping via the implicit + `organization_id` heuristic; a registration that carries a `tenancy` + declaration stays authoritative. + +- 2018df9: **Unify the developer-facing org identifier in JS hooks — `organizationId` is now the blessed name; `session.tenantId` becomes a deprecated alias (#3280).** The caller's active organization was surfaced to hook authors as `ctx.session.tenantId`, while everything else on the developer surface — the `organization_id` column, `current_user.organizationId` in RLS/sharing, and seed rows — already said `organization`. A hook author had to internalize the hidden equation `tenantId === organizationId` to move between surfaces. This is additive and non-breaking: + + - **`ctx.session.organizationId`** is added as the blessed name; **`ctx.session.tenantId`** still carries the identical value but is marked `@deprecated` in its TSDoc. Both come from the same resolved `ExecutionContext.tenantId` (which the kernel derives from `session.activeOrganizationId`). + - **`ctx.user.organizationId`** is added to the ergonomic `user` shortcut, so a hook that needs "the current org to filter by" writes `ctx.user.organizationId` with zero relearning — matching `current_user.organizationId` (RLS) and the `organization_id` column. The engine now populates `ctx.user` (`{ id, email?, organizationId? }`) at every hook event that already carries a `session`; it stays `undefined` for system / unauthenticated writes. + + **No behavior change and no breaking rename.** The generic driver-layer tenancy abstraction (`ExecutionContext.tenantId`, `DriverOptions.tenantId`, `SqlDriver.applyTenantScope`, `TenancyConfig.tenantField`) is deliberately untouched — that layer's isolation column is configurable and legitimately carries an _environment_ id in per-environment (database-per-tenant) kernels. Hook-authoring docs now teach `organizationId` and distinguish the two isolation axes: **org row-scoping** (`organization_id`, shared DB) vs **environment / database-per-tenant** (`service-tenant`, `driver-turso`). Community edition never populates an org, so `organizationId` is `undefined` there. + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [e057f42] +- Updated dependencies [a3823b2] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [6b51346] +- Updated dependencies [80273c8] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [dd9f223] +- Updated dependencies [46e876c] +- Updated dependencies [7125007] +- Updated dependencies [5f05de2] +- Updated dependencies [021ba4c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [83e8f7d] +- Updated dependencies [0e41302] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [b8a21ad] +- Updated dependencies [beaf2de] +- Updated dependencies [06cb319] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [290e2f0] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [92f5f19] +- Updated dependencies [32899e6] +- Updated dependencies [515f11a] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [ea32ec7] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + - @objectstack/core@16.0.0 + - @objectstack/metadata-protocol@16.0.0 + - @objectstack/formula@16.0.0 + - @objectstack/metadata-core@16.0.0 + - @objectstack/types@16.0.0 + ## 16.0.0-rc.1 ### Patch Changes diff --git a/packages/objectql/package.json b/packages/objectql/package.json index b20f87a1e7..0880ce5d1d 100644 --- a/packages/objectql/package.json +++ b/packages/objectql/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/objectql", - "version": "16.0.0-rc.1", + "version": "16.0.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 d874c5e62b..5c48251f1f 100644 --- a/packages/observability/CHANGELOG.md +++ b/packages/observability/CHANGELOG.md @@ -1,5 +1,111 @@ # @objectstack/observability +## 16.0.0 + +### Minor Changes + +- efbcfe1: feat(observability): admin-only richer per-request timing detail via `X-OS-Debug-Timing: json` (#2408) + + Completes the optional "richer JSON" diagnostic from #2408. In addition to the + basic `Server-Timing` header, an admin/service caller can now request a + per-query breakdown — the slowest SQL statements and a query count — by sending + `X-OS-Debug-Timing: json`. The detail is returned in a separate + `X-OS-Debug-Timing-Detail` response header (compact JSON) and is **admin-only, + even under global mode**: an ordinary caller never sees SQL shapes. + + - **observability**: `PerfTiming` gains opt-in per-event detail capture + (`enableDetail` / `recordDetail` / `details`) plus the ambient + `recordServerTimingDetail`. The disclosure gate gains a `privileged` level + (set by `allowPerfDisclosure`, read via `isPerfDisclosurePrivileged`) so the + richer detail can be gated independently of the basic header. + - **driver-sql**: when detail capture is on, the query listener additionally + records each query's **parametrized** statement (knex's `q.sql`, `?` + placeholders) — never the bindings, so no literal row value ever enters the + collector. Zero overhead when detail is off. + - **plugin-hono-server**: `X-OS-Debug-Timing: json` enables detail capture; the + middleware emits `X-OS-Debug-Timing-Detail` (slowest queries, capped and + sanitized to header-safe ASCII) only when the principal is a proven admin. + + Basic and global behavior are unchanged; `json` is purely additive. + +- 2049b6a: feat(observability): admin-gated per-request `Server-Timing` via `X-OS-Debug-Timing` (#2408) + + Perf-tuning mode was previously global-only (`serverTiming` option / + `OS_SERVER_TIMING`), which discloses internal phase durations — a mild + backend-fingerprinting surface — to every caller. This adds the per-request + gating path from the design so an operator can pull a single request's + `Server-Timing` breakdown on a live environment without turning the header on + for everyone. + + - **observability**: a request-scoped disclosure gate (`runWithPerfDisclosure`, + `allowPerfDisclosure`, `isPerfDisclosureAllowed`, `PerfDisclosureGate`) kept + separate from the pure `PerfTiming` collector and pinned to its own + `Symbol.for` store so the middleware and dispatcher share it across module + copies. + - **plugin-hono-server**: the Server-Timing middleware is registered by default + (unless `serverTiming: false`). It runs the collector when timing is global + **or** the request sends `X-OS-Debug-Timing: 1`, and emits the header only + when the gate is open. `OS_PERF_TIMING=1` now also enables global mode. + - **runtime**: after resolving the execution context, the dispatcher opens the + gate for admin/service/system principals, so ordinary callers never receive + the header even if they send the debug header. + + Existing global-mode behavior is unchanged. + +### Patch Changes + +- ce468c8: feat(observability): decompose `Server-Timing` into auth / db / hooks / serialize spans (perf-tuning mode) + + The opt-in `Server-Timing` header now breaks a request's server time into the phases that actually explain it, so an operator can open DevTools → Network → Timing and see where the time went without standing up an external tracing backend: + + - **`db`** — total SQL time with a **query count**. The SQL driver wires knex's `query` / `query-response` events (keyed by `__knexQueryUid`) and folds each query into one aggregate member (`db;dur=210;desc="6 queries"`) — the query count is the number most useful for spotting N sequential round-trips. Timing is attributed to the originating request via `AsyncLocalStorage`, so it is correct under concurrency and never cross-attributes. SQL text is never emitted, only durations and a count. + - **`auth`** — identity / session resolution in the dispatcher, the prime suspect for unexplained data-API overhead. + - **`hooks`** — total business-hook execution time with a hook count, fed through the engine's existing `HookMetricsRecorder` seam (wired from the runtime, so `@objectstack/objectql`'s lean `core` tier stays observability-free). + - **`serialize`** — response JSON encoding in the HTTP adapter. + + Adds `countServerTiming(name, dur, unit)` (and `PerfTiming.count`) to fold high-frequency phases into a single aggregate member instead of flooding the header. Every phase is a no-op when perf-tuning is off (`serverTiming: true` / `OS_SERVER_TIMING=true`), so there is zero measurable overhead on the normal path. + + Closes #2408. + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [a3823b2] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [46e876c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + ## 16.0.0-rc.1 ### Patch Changes diff --git a/packages/observability/package.json b/packages/observability/package.json index 6577dd31bd..1ab21a1146 100644 --- a/packages/observability/package.json +++ b/packages/observability/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/observability", - "version": "16.0.0-rc.1", + "version": "16.0.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 9757f585f0..e25a7d7ee2 100644 --- a/packages/platform-objects/CHANGELOG.md +++ b/packages/platform-objects/CHANGELOG.md @@ -1,5 +1,110 @@ # @objectstack/platform-objects +## 16.0.0 + +### Minor Changes + +- bc65105: feat(platform-objects): surface phone number in the create_user result dialog + + `sys_user`'s `create_user` action now declares `user.phoneNumber` in its + `resultDialog.fields`, so admins creating phone-based accounts see the + sign-in phone number alongside the email and temporary password. The + create-user response carries `phoneNumber` only for phone-based users; + objectui's ActionResultDialog skips declared fields whose path is absent + from the payload, so email-only users see no extra row. + +### Patch Changes + +- 6289ec3: feat(i18n): translation slot for action `resultDialog` copy — the one-shot secret-reveal dialogs are now localizable + + The post-success `resultDialog` (temporary passwords, 2FA backup codes, OAuth + client secrets) had no slot in the translation protocol, so its title / + description / acknowledge button / field labels always rendered the hardcoded + English metadata literals even on fully-translated locales. + + - **spec.** `_actions.` (object + object-first node) and + `globalActions.` gain an optional `resultDialog` translation node + (`ActionResultDialogTranslationSchema`): `title`, `description`, + `acknowledge`, and `fields` keyed by the **literal** result-field path + (e.g. `"user.email"` — keys may contain dots; resolvers index the record + directly, never split on `.`). New `resolveActionResultDialog` overlay + resolver, wired into `translateAction` for API-boundary translation. + - **cli.** `os i18n extract` emits the new `resultDialog.*` keys (title / + description / acknowledge / `fields.` for labelled fields), so + coverage and skeleton generation see them. + - **platform-objects.** en / zh-CN / ja-JP / es-ES bundles ship the + resultDialog copy for all six shipped dialogs: `sys_user.create_user`, + `sys_user.set_user_password`, `sys_two_factor.enable_two_factor`, + `sys_two_factor.regenerate_backup_codes`, + `sys_oauth_application.create_oauth_application`, and + `sys_oauth_application.rotate_client_secret`. + + Client-side rendering lands in objectui (`actionResultDialog` resolver in + `@object-ui/i18n` + result-dialog handlers). Purely additive — untranslated + locales keep falling back to the metadata literals. + +- 22013aa: **Split the overloaded `managedBy: 'system'` bucket into engine-owned vs. admin-writable, and enforce engine-owned writes (ADR-0103, #3220).** The `system` bucket conflated two incompatible write policies: rows a platform service owns end to end (never user-written), and platform-defined schema whose rows are legitimately admin/user-writable. It carried the same all-false affordance row as `better-auth`/`append-only` but, unlike `better-auth`, had no engine enforcement — a wildcard admin could raw-write these rows through the generic data API (ADR-0049 gap). + + Rather than add a new `managedBy` enum value (which would fall through to fully-editable `platform` defaults on already-deployed Console clients), the write policy is now the **resolved affordance** (`resolveCrudAffordances` = bucket default + `userActions`), and _engine-owned_ is defined as a `system`/`append-only` object that grants no write: + + - **Writable set declares `userActions`** — the RBAC link tables (`sys_user_position`, `sys_user_permission_set`, `sys_position_permission_set`), `sys_user_preference`, `sys_approval_delegation`, and the messaging config grids (`sys_notification_preference` / `…_subscription` / `…_template`) now declare `userActions: { create, edit, delete: true }`. The affordance is a declaration only — the `DelegatedAdminGate` / RLS / permission sets remain the authz. + - **Engine-owned objects locked to reads** — `apiMethods: ['get','list']` added where absent (jobs, notifications, approval request/approver/token/action, `sys_record_share`, `sys_automation_run`, mail/settings/secret audit, the messaging delivery pipeline). `sys_secret` is explicitly read-locked (an empty `apiMethods` array fails open). + - **`sys_import_job`** stays engine-owned: the REST import route now writes its job rows `isSystem`-elevated (attribution preserved via the explicit `created_by` stamp) and the object is locked to `['get','list']`. + - **New engine write guard** (`assertEngineOwnedWriteAllowed`, plugin-security) fail-closed rejects user-context generic writes to engine-owned `system`/`append-only` objects, keyed off the resolved affordance; `isSystem` and context-less engine/service writes bypass by construction. Wired into the security middleware alongside the other data-layer gates. + - **`reconcileManagedApiMethods`** (objectql registry) now runs for **every** managed bucket, not just `better-auth`: any advertised write verb an object's resolved affordances forbid is stripped at registration with a warning (the drift backstop, ADR-0049). + - **`/me/permissions` clamp** (plugin-hono-server) now clamps `system`/`append-only` as well as `better-auth`, so the client hint reflects `permission ∩ guard`. + + **Potentially breaking:** a downstream/third-party `system` object that advertised generic write verbs relying on today's fail-open behaviour will have those verbs stripped (with a warning) and user-context generic writes to it rejected. Declare `userActions` opening the verbs the object legitimately takes from a user context. `better-auth` keeps plugin-auth's identity write guard unchanged; the row-level `managed_by` provenance vocabulary (ADR-0066) is a different axis and is untouched. + +- 62a2117: **Split the overloaded `managedBy: 'system'` bucket with an explicit `engine-owned` value (ADR-0103 addendum, #3343).** ADR-0103 deferred the enum split ("revisitable later as a rename") because a new `managedBy` value would fall through to the fully-editable `platform` default on deployed Console clients. Both reasons against it are now retired — the server-side write guard / `apiMethods` reconciliation / `/me/permissions` clamp make that fallthrough cosmetic (the write is rejected regardless of what the client renders), and objectui#2712 closed the UI union — so v16 lands it, **additively**. + + - **New enum value `engine-owned`** with the same all-locked default affordance row as `system` (`create/import/edit/delete: false`, `exportCsv: true`). It joins `ENGINE_OWNED_BUCKETS` (the engine write guard) and `GUARDED_WRITE_BUCKETS` (the `/me/permissions` clamp); the guard, `reconcileManagedApiMethods`, and the clamp mechanisms are unchanged — `engine-owned` is an explicit member of the set they already covered by resolved affordance. + - **20 objects relabelled `system → engine-owned`** — the ones the engine owns end to end and that declared no write-opening `userActions` (the metadata store, jobs, approval runtime rows, sharing rows, `sys_automation_run`, the messaging delivery/receipt pipeline, `sys_secret`, settings). One-line, behaviour-identical per object. + - **8 admin/user-writable objects keep `managedBy: 'system'`** (the RBAC link tables, `sys_user_preference`, `sys_approval_delegation`, the messaging config grids) — `system` now reads as "engine-managed schema, writable via `userActions`". + + Behaviour-, enforcement- and wire-identical: resolved affordances, the guard verdict, the 405 `apiMethods` reconciliation, and the permissions clamp are the same before and after — this is a self-documenting relabel, not a policy change. No data migration (`managedBy` is schema metadata) and no code branches on the `'system'` literal. Retiring the overloaded `system` entirely (moving the 8 writable objects to a dedicated bucket) is a breaking rename deferred to v17. + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [a3823b2] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [46e876c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [beaf2de] +- Updated dependencies [06cb319] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + - @objectstack/metadata-core@16.0.0 + ## 16.0.0-rc.1 ### Patch Changes diff --git a/packages/platform-objects/package.json b/packages/platform-objects/package.json index 5c162e6e95..9d15ca20e2 100644 --- a/packages/platform-objects/package.json +++ b/packages/platform-objects/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/platform-objects", - "version": "16.0.0-rc.1", + "version": "16.0.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 37b241c5ff..f3b3dab296 100644 --- a/packages/plugins/driver-memory/CHANGELOG.md +++ b/packages/plugins/driver-memory/CHANGELOG.md @@ -1,5 +1,54 @@ # @objectstack/driver-memory +## 16.0.0 + +### Patch Changes + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [e057f42] +- Updated dependencies [a3823b2] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [dd9f223] +- Updated dependencies [46e876c] +- Updated dependencies [5f05de2] +- Updated dependencies [021ba4c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [290e2f0] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + - @objectstack/core@16.0.0 + ## 16.0.0-rc.1 ### Patch Changes diff --git a/packages/plugins/driver-memory/package.json b/packages/plugins/driver-memory/package.json index b6a4da6075..ac53e504f7 100644 --- a/packages/plugins/driver-memory/package.json +++ b/packages/plugins/driver-memory/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/driver-memory", - "version": "16.0.0-rc.1", + "version": "16.0.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 339edab2e5..24a4105f60 100644 --- a/packages/plugins/driver-mongodb/CHANGELOG.md +++ b/packages/plugins/driver-mongodb/CHANGELOG.md @@ -1,5 +1,54 @@ # @objectstack/driver-mongodb +## 16.0.0 + +### Patch Changes + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [e057f42] +- Updated dependencies [a3823b2] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [dd9f223] +- Updated dependencies [46e876c] +- Updated dependencies [5f05de2] +- Updated dependencies [021ba4c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [290e2f0] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + - @objectstack/core@16.0.0 + ## 16.0.0-rc.1 ### Patch Changes diff --git a/packages/plugins/driver-mongodb/package.json b/packages/plugins/driver-mongodb/package.json index 8a5320d401..6da57b5e3b 100644 --- a/packages/plugins/driver-mongodb/package.json +++ b/packages/plugins/driver-mongodb/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/driver-mongodb", - "version": "16.0.0-rc.1", + "version": "16.0.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 f3cc0d3c24..b558c70a76 100644 --- a/packages/plugins/driver-sql/CHANGELOG.md +++ b/packages/plugins/driver-sql/CHANGELOG.md @@ -1,5 +1,141 @@ # @objectstack/driver-sql +## 16.0.0 + +### Minor Changes + +- efbcfe1: feat(observability): admin-only richer per-request timing detail via `X-OS-Debug-Timing: json` (#2408) + + Completes the optional "richer JSON" diagnostic from #2408. In addition to the + basic `Server-Timing` header, an admin/service caller can now request a + per-query breakdown — the slowest SQL statements and a query count — by sending + `X-OS-Debug-Timing: json`. The detail is returned in a separate + `X-OS-Debug-Timing-Detail` response header (compact JSON) and is **admin-only, + even under global mode**: an ordinary caller never sees SQL shapes. + + - **observability**: `PerfTiming` gains opt-in per-event detail capture + (`enableDetail` / `recordDetail` / `details`) plus the ambient + `recordServerTimingDetail`. The disclosure gate gains a `privileged` level + (set by `allowPerfDisclosure`, read via `isPerfDisclosurePrivileged`) so the + richer detail can be gated independently of the basic header. + - **driver-sql**: when detail capture is on, the query listener additionally + records each query's **parametrized** statement (knex's `q.sql`, `?` + placeholders) — never the bindings, so no literal row value ever enters the + collector. Zero overhead when detail is off. + - **plugin-hono-server**: `X-OS-Debug-Timing: json` enables detail capture; the + middleware emits `X-OS-Debug-Timing-Detail` (slowest queries, capped and + sanitized to header-safe ASCII) only when the principal is a proven admin. + + Basic and global behavior are unchanged; `json` is purely additive. + +### Patch Changes + +- 47d923c: fix(driver-sql): drop the vestigial `sqlite3` peerDependency — the SQLite path uses `better-sqlite3` (#3277) + + `package.json` advertised `peerDependencies.sqlite3: "^5.0.0"`, but the driver never + loads `sqlite3` at runtime. Every first-party SQLite construction site builds a + `client: 'better-sqlite3'` Knex driver (`resolveSqliteDriver` in + `@objectstack/service-datasource`, the datasource driver factory, and the whole + driver test suite), and the README already tells consumers to `pnpm add better-sqlite3`. + `better-sqlite3` is auto-provided as an `optionalDependency` (with the native → wasm → + memory step-down of #2229 covering a failed native build), so the SQLite requirement is + already satisfied without the consumer installing anything. + + The stale `sqlite3` peer only misled: a consumer resolving peer deps could `pnpm add +sqlite3` (never used) while believing they'd satisfied the SQLite requirement. Removing + it aligns the declared contract with the code and the docs. The `sqlite3` string alias + still maps to `better-sqlite3` in the driver factory and dialect detection, so + `driver: 'sqlite3'` config keeps working — it just resolves to `better-sqlite3` like + everything else. + +- ce468c8: feat(observability): decompose `Server-Timing` into auth / db / hooks / serialize spans (perf-tuning mode) + + The opt-in `Server-Timing` header now breaks a request's server time into the phases that actually explain it, so an operator can open DevTools → Network → Timing and see where the time went without standing up an external tracing backend: + + - **`db`** — total SQL time with a **query count**. The SQL driver wires knex's `query` / `query-response` events (keyed by `__knexQueryUid`) and folds each query into one aggregate member (`db;dur=210;desc="6 queries"`) — the query count is the number most useful for spotting N sequential round-trips. Timing is attributed to the originating request via `AsyncLocalStorage`, so it is correct under concurrency and never cross-attributes. SQL text is never emitted, only durations and a count. + - **`auth`** — identity / session resolution in the dispatcher, the prime suspect for unexplained data-API overhead. + - **`hooks`** — total business-hook execution time with a hook count, fed through the engine's existing `HookMetricsRecorder` seam (wired from the runtime, so `@objectstack/objectql`'s lean `core` tier stays observability-free). + - **`serialize`** — response JSON encoding in the HTTP adapter. + + Adds `countServerTiming(name, dur, unit)` (and `PerfTiming.count`) to fold high-frequency phases into a single aggregate member instead of flooding the header. Every phase is a no-op when perf-tuning is off (`serverTiming: true` / `OS_SERVER_TIMING=true`), so there is zero measurable overhead on the normal path. + + Closes #2408. + +- 86d30af: fix(tenancy): platform-global (`tenancy.enabled:false`) objects are never driver-org-scoped (#3249) + + An org-context read of a platform-global object (e.g. `sys_license`, ADR-0066) + could return 0 rows for an authenticated caller while an anonymous read saw the + data: the engine stamped `execCtx.tenantId` into driver options unconditionally, + and the SQL driver's tenant-field cache could be re-corrupted to + `organization_id` by a partial re-registration (lifecycle archive `syncSchema`, + schema-drift re-sync) whose schema omitted the `tenancy` block. + + - New `isTenancyDisabled(schema)` export from `@objectstack/spec/data` — the + single source of truth for the ADR-0066 platform-global posture, now shared by + the registry (tenant-column injection), the ObjectQL engine, and the SQL + driver. + - `ObjectQL.buildDriverOptions` no longer stamps `tenantId` for objects whose + registered schema declares `tenancy.enabled: false` (an explicitly-passed + options `tenantId` still wins — deliberate caller intent). + - `SqlDriver` (and `SqliteWasmDriver`) now keep a sticky record of an explicit + `tenancy.enabled:false` declaration: a later registration without a `tenancy` + block preserves the opt-out instead of re-scoping via the implicit + `organization_id` heuristic; a registration that carries a `tenancy` + declaration stays authoritative. + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [e057f42] +- Updated dependencies [a3823b2] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [dd9f223] +- Updated dependencies [46e876c] +- Updated dependencies [5f05de2] +- Updated dependencies [021ba4c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [83e8f7d] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [efbcfe1] +- Updated dependencies [2049b6a] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [290e2f0] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [92f5f19] +- Updated dependencies [32899e6] +- Updated dependencies [ce468c8] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + - @objectstack/core@16.0.0 + - @objectstack/types@16.0.0 + - @objectstack/observability@16.0.0 + ## 16.0.0-rc.1 ### Patch Changes diff --git a/packages/plugins/driver-sql/package.json b/packages/plugins/driver-sql/package.json index 10e6ae48e7..a899d81eed 100644 --- a/packages/plugins/driver-sql/package.json +++ b/packages/plugins/driver-sql/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/driver-sql", - "version": "16.0.0-rc.1", + "version": "16.0.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 46943aedbc..ea89111fce 100644 --- a/packages/plugins/driver-sqlite-wasm/CHANGELOG.md +++ b/packages/plugins/driver-sqlite-wasm/CHANGELOG.md @@ -1,5 +1,58 @@ # @objectstack/driver-sqlite-wasm +## 16.0.0 + +### Patch Changes + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [e057f42] +- Updated dependencies [a3823b2] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [dd9f223] +- Updated dependencies [47d923c] +- Updated dependencies [46e876c] +- Updated dependencies [5f05de2] +- Updated dependencies [021ba4c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [efbcfe1] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [290e2f0] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [ce468c8] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + - @objectstack/core@16.0.0 + - @objectstack/driver-sql@16.0.0 + ## 16.0.0-rc.1 ### Patch Changes diff --git a/packages/plugins/driver-sqlite-wasm/package.json b/packages/plugins/driver-sqlite-wasm/package.json index 562034dc1b..50fadd453b 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": "16.0.0-rc.1", + "version": "16.0.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 1ee7528cd9..511117e484 100644 --- a/packages/plugins/embedder-openai/CHANGELOG.md +++ b/packages/plugins/embedder-openai/CHANGELOG.md @@ -1,5 +1,48 @@ # @objectstack/embedder-openai +## 16.0.0 + +### Patch Changes + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [a3823b2] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [46e876c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + ## 16.0.0-rc.1 ### Patch Changes diff --git a/packages/plugins/embedder-openai/package.json b/packages/plugins/embedder-openai/package.json index d3b601229c..afd514c81c 100644 --- a/packages/plugins/embedder-openai/package.json +++ b/packages/plugins/embedder-openai/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/embedder-openai", - "version": "16.0.0-rc.1", + "version": "16.0.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 fbacab7314..a4c5da6a0c 100644 --- a/packages/plugins/knowledge-memory/CHANGELOG.md +++ b/packages/plugins/knowledge-memory/CHANGELOG.md @@ -1,5 +1,55 @@ # @objectstack/knowledge-memory +## 16.0.0 + +### Patch Changes + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [e057f42] +- Updated dependencies [a3823b2] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [dd9f223] +- Updated dependencies [46e876c] +- Updated dependencies [5f05de2] +- Updated dependencies [021ba4c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [290e2f0] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + - @objectstack/core@16.0.0 + - @objectstack/service-knowledge@16.0.0 + ## 16.0.0-rc.1 ### Patch Changes diff --git a/packages/plugins/knowledge-memory/package.json b/packages/plugins/knowledge-memory/package.json index 6f3222d78a..a8d85c8c10 100644 --- a/packages/plugins/knowledge-memory/package.json +++ b/packages/plugins/knowledge-memory/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/knowledge-memory", - "version": "16.0.0-rc.1", + "version": "16.0.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 6938fba42e..b737739c9e 100644 --- a/packages/plugins/knowledge-ragflow/CHANGELOG.md +++ b/packages/plugins/knowledge-ragflow/CHANGELOG.md @@ -1,5 +1,55 @@ # @objectstack/knowledge-ragflow +## 16.0.0 + +### Patch Changes + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [e057f42] +- Updated dependencies [a3823b2] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [dd9f223] +- Updated dependencies [46e876c] +- Updated dependencies [5f05de2] +- Updated dependencies [021ba4c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [290e2f0] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + - @objectstack/core@16.0.0 + - @objectstack/service-knowledge@16.0.0 + ## 16.0.0-rc.1 ### Patch Changes diff --git a/packages/plugins/knowledge-ragflow/package.json b/packages/plugins/knowledge-ragflow/package.json index 0a574cc304..61d33f772f 100644 --- a/packages/plugins/knowledge-ragflow/package.json +++ b/packages/plugins/knowledge-ragflow/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/knowledge-ragflow", - "version": "16.0.0-rc.1", + "version": "16.0.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 81666084cc..d202cb270b 100644 --- a/packages/plugins/plugin-approvals/CHANGELOG.md +++ b/packages/plugins/plugin-approvals/CHANGELOG.md @@ -1,5 +1,162 @@ # @objectstack/plugin-approvals +## 16.0.0 + +### Minor Changes + +- e412fb6: feat(approvals): declare file attachments on approve/reject decisions + + The declared `approval_approve` / `approval_reject` actions on + `sys_approval_request` gain an optional multi-file `attachments` param + (`type: 'file'`, `multiple`). The console renders `type:'file'` action params + through the shared upload widget (objectui ADR-0059) and POSTs the resolved + `attachments: string[]`, so a reviewer can attach supporting files to a + decision through the generic declared-action dialog — letting the approvals + inbox retire its hand-wired attachment composer (objectui#2698). + + Purely additive metadata: the decision route already forwards + `body.attachments` to `ApprovalService.decide`, and the + `sys_approval_action.attachments` column (file, multiple) already persists them + (#3266/#3274). No service or route change. + +- 8efa395: feat(approvals): server-computed `viewer` capability for precise decision-action gating + + `getRequest` / `listRequests` now attach a per-viewer block — + `viewer: { can_act, is_submitter }` — computed from the caller's context + (`ApprovalRequestRow.viewer`): + + - `can_act` — the caller is a _current pending approver_ (their user id is in the + request's resolved `pending_approvers` while it is still `pending`). This is + the same check the decision methods authorize with, so it already reflects + position/team/manager resolution — strictly more accurate than a client-side + identity guess. + - `is_submitter` — the caller submitted the request. + + The declared decision actions on `sys_approval_request` now gate on it: approver + actions (approve/reject/reassign/send-back/request-info) use + `record.viewer.can_act`; submitter levers (remind/recall/resubmit) use + `record.viewer.is_submitter`. Previously approver actions only trimmed the + non-pending case, so a submitter viewing their own pending request saw buttons + they couldn't use (the backend 403'd); a position-addressed approver could be + wrongly hidden by the old client heuristic. Where `viewer` is absent (a row + surfaced outside a service read with a user context), the predicate fails closed. + +- 3a18b60: feat(approvals): rename the `role` approver type to `org_membership_level` (#3133) + + `ApproverType.role` was the last platform surface projecting the reserved word + "role" (ADR-0090 D3). It is not covered by D3's better-auth exception: that + exception protects better-auth's own `sys_member.role` **column**, which we do + not own — `ApproverType` is our own enum, an authoring surface, and D3 mandates + that the projection of that concept is spelled `org_membership_level` and + labelled "organization membership", **never "role"**. + + The sentence licensing the leak was also false: ADR-0090 D3 claims + `sys_member.role` is "already relabelled `org_membership_level` in the platform + projection", but `org_membership_level` existed nowhere in the codebase and + ADR-0057 D7 lists that relabel under "Deferred (evidence-gated, P4)". The + projection never landed, so the word reached authors. + + The name manufactured a real, silent failure — "hotcrm class": every other + surface renamed to `position` (`sys_role`, `ShareRecipientType.role`, + `ctx.roles[]`), so `{ type: 'role', value: 'sales_manager' }` reads as the + legacy spelling of a position. It resolves against the membership tier, finds + no member row, falls back to an inert `role:sales_manager` literal, and the + request waits forever on an approver that cannot exist. + + - **spec**: `ApproverType` gains `org_membership_level`; `role` stays as a + deprecated alias for one window (a published 15.x flow keeps loading) with + `DEPRECATED_APPROVER_TYPES` + `canonicalApproverType()` as the single source + for the mapping. Removed in the next major. + - **plugin-approvals**: resolves on the canonical type and warns on the + deprecated spelling. The `type:value` fallback literal keeps the **authored** + spelling — stored `sys_approval_approver` rows and `pending_approvers` slots + from 15.x carry `role:`, and rewriting it would orphan them. + - **lint**: `approval-role-not-membership-tier` → `approval-approver-not-membership-tier` + (the rule id carried the reserved word too), plus a new + `approval-approver-type-deprecated`. The two are mutually exclusive: a bad + _value_ wins, because prescribing `org_membership_level` for a position name + would be wrong advice — the fix there is `position`. + + Authoring `type: 'role'` keeps working and now says so out loud. Rewrite it as + `org_membership_level`; if the value is an org position, the fix is `position`. + +### Patch Changes + +- 22013aa: **Split the overloaded `managedBy: 'system'` bucket into engine-owned vs. admin-writable, and enforce engine-owned writes (ADR-0103, #3220).** The `system` bucket conflated two incompatible write policies: rows a platform service owns end to end (never user-written), and platform-defined schema whose rows are legitimately admin/user-writable. It carried the same all-false affordance row as `better-auth`/`append-only` but, unlike `better-auth`, had no engine enforcement — a wildcard admin could raw-write these rows through the generic data API (ADR-0049 gap). + + Rather than add a new `managedBy` enum value (which would fall through to fully-editable `platform` defaults on already-deployed Console clients), the write policy is now the **resolved affordance** (`resolveCrudAffordances` = bucket default + `userActions`), and _engine-owned_ is defined as a `system`/`append-only` object that grants no write: + + - **Writable set declares `userActions`** — the RBAC link tables (`sys_user_position`, `sys_user_permission_set`, `sys_position_permission_set`), `sys_user_preference`, `sys_approval_delegation`, and the messaging config grids (`sys_notification_preference` / `…_subscription` / `…_template`) now declare `userActions: { create, edit, delete: true }`. The affordance is a declaration only — the `DelegatedAdminGate` / RLS / permission sets remain the authz. + - **Engine-owned objects locked to reads** — `apiMethods: ['get','list']` added where absent (jobs, notifications, approval request/approver/token/action, `sys_record_share`, `sys_automation_run`, mail/settings/secret audit, the messaging delivery pipeline). `sys_secret` is explicitly read-locked (an empty `apiMethods` array fails open). + - **`sys_import_job`** stays engine-owned: the REST import route now writes its job rows `isSystem`-elevated (attribution preserved via the explicit `created_by` stamp) and the object is locked to `['get','list']`. + - **New engine write guard** (`assertEngineOwnedWriteAllowed`, plugin-security) fail-closed rejects user-context generic writes to engine-owned `system`/`append-only` objects, keyed off the resolved affordance; `isSystem` and context-less engine/service writes bypass by construction. Wired into the security middleware alongside the other data-layer gates. + - **`reconcileManagedApiMethods`** (objectql registry) now runs for **every** managed bucket, not just `better-auth`: any advertised write verb an object's resolved affordances forbid is stripped at registration with a warning (the drift backstop, ADR-0049). + - **`/me/permissions` clamp** (plugin-hono-server) now clamps `system`/`append-only` as well as `better-auth`, so the client hint reflects `permission ∩ guard`. + + **Potentially breaking:** a downstream/third-party `system` object that advertised generic write verbs relying on today's fail-open behaviour will have those verbs stripped (with a warning) and user-context generic writes to it rejected. Declare `userActions` opening the verbs the object legitimately takes from a user context. `better-auth` keeps plugin-auth's identity write guard unchanged; the row-level `managed_by` provenance vocabulary (ADR-0066) is a different axis and is untouched. + +- 62a2117: **Split the overloaded `managedBy: 'system'` bucket with an explicit `engine-owned` value (ADR-0103 addendum, #3343).** ADR-0103 deferred the enum split ("revisitable later as a rename") because a new `managedBy` value would fall through to the fully-editable `platform` default on deployed Console clients. Both reasons against it are now retired — the server-side write guard / `apiMethods` reconciliation / `/me/permissions` clamp make that fallthrough cosmetic (the write is rejected regardless of what the client renders), and objectui#2712 closed the UI union — so v16 lands it, **additively**. + + - **New enum value `engine-owned`** with the same all-locked default affordance row as `system` (`create/import/edit/delete: false`, `exportCsv: true`). It joins `ENGINE_OWNED_BUCKETS` (the engine write guard) and `GUARDED_WRITE_BUCKETS` (the `/me/permissions` clamp); the guard, `reconcileManagedApiMethods`, and the clamp mechanisms are unchanged — `engine-owned` is an explicit member of the set they already covered by resolved affordance. + - **20 objects relabelled `system → engine-owned`** — the ones the engine owns end to end and that declared no write-opening `userActions` (the metadata store, jobs, approval runtime rows, sharing rows, `sys_automation_run`, the messaging delivery/receipt pipeline, `sys_secret`, settings). One-line, behaviour-identical per object. + - **8 admin/user-writable objects keep `managedBy: 'system'`** (the RBAC link tables, `sys_user_preference`, `sys_approval_delegation`, the messaging config grids) — `system` now reads as "engine-managed schema, writable via `userActions`". + + Behaviour-, enforcement- and wire-identical: resolved affordances, the guard verdict, the 405 `apiMethods` reconciliation, and the permissions clamp are the same before and after — this is a self-documenting relabel, not a policy change. No data migration (`managedBy` is schema metadata) and no code branches on the `'system'` literal. Retiring the overloaded `system` entirely (moving the 8 writable objects to a dedicated bucket) is a breaking rename deferred to v17. + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [e057f42] +- Updated dependencies [a3823b2] +- Updated dependencies [bc65105] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [6b51346] +- Updated dependencies [80273c8] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [dd9f223] +- Updated dependencies [46e876c] +- Updated dependencies [7125007] +- Updated dependencies [5f05de2] +- Updated dependencies [021ba4c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [beaf2de] +- Updated dependencies [06cb319] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [290e2f0] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [ea32ec7] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + - @objectstack/platform-objects@16.0.0 + - @objectstack/core@16.0.0 + - @objectstack/formula@16.0.0 + - @objectstack/metadata-core@16.0.0 + ## 16.0.0-rc.1 ### Minor Changes diff --git a/packages/plugins/plugin-approvals/package.json b/packages/plugins/plugin-approvals/package.json index 42505ed303..b68cf5a819 100644 --- a/packages/plugins/plugin-approvals/package.json +++ b/packages/plugins/plugin-approvals/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/plugin-approvals", - "version": "16.0.0-rc.1", + "version": "16.0.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 2c6a59dd94..5361a833a8 100644 --- a/packages/plugins/plugin-audit/CHANGELOG.md +++ b/packages/plugins/plugin-audit/CHANGELOG.md @@ -1,5 +1,105 @@ # @objectstack/plugin-audit +## 16.0.0 + +### Patch Changes + +- 2ea08ee: Flow trigger observability — kill the four-layer silence around record-change flows that never fire (2026-07-17 third-party eval). + + A misauthored auto-launched flow (wrong `objectName`, missing `requires: ['automation','triggers']`, failing start condition) produced ZERO output at every layer: the engine's own registration/binding logs land inside the CLI's boot-quiet stdout window (which swallows debug/info/warn — only error/fatal reach stderr), and each "didn't happen" path was itself silent. Fixes: + + - **Startup banner `Flows:` section** (`os serve`/`os dev`/`os start`): flow count, bound-to-trigger count, registered trigger types, draft count — plus loud `⚠` lines for flows declared with no automation engine enabled (`requires` missing), flows whose trigger type has no registered trigger, and bound record-change flows targeting an unknown object (dead binding). Printed after stdout is restored, so it is immune to the boot-quiet window. + - **Trigger-fired run failures now log at ERROR** (stderr — always visible): the automation engine no longer drops the AutomationResult of a trigger-fired execution; condition-evaluation faults and node failures surface with the flow name. Condition-not-met skips stay at debug (high-frequency, intentional). + - **`RecordChangeTrigger` probes object existence at bind time** and warns when a flow's `objectName` matches no registered object (exact-name matching), instead of silently arming a hook that can never fire. + - **`kernel:bootstrapped` binding audit** in the automation plugin: warns per enabled-but-unbound triggered flow with the reason, and reports registered/bound/draft counts (`AutomationEngine.getTriggerBindingAudit()`, extended `getFlowRuntimeStates()` with `status`/`triggerType`/`object`). + - **`os validate` flow-wiring advisories** (`@objectstack/lint` `validateFlowTriggerReadiness`): warns when a record-triggered flow targets an object the stack does not define, and when an auto-triggered flow's status is `draft` (authored or defaulted — draft flows still fire; declare `active` or `obsolete`). + - Removed leftover boot-debug writes (`registerApp`/`AppPlugin`/`StandaloneStack`/`AuditPlugin` stderr noise) that previous debugging of this same silence had left behind. + +- ee0a499: feat(i18n): localize collaboration notification titles and the storage objects; wire the notifications REST routes + + Three gaps behind one report (a `sys_file "repro.png" assigned to you` + notification that was English on an all-Chinese workspace, opened an English + detail page, and never cleared its unread state): + + - **plugin-audit** — the assignment (`collab.assignment`) and @mention + (`collab.mention`) bell titles were hardcoded English literals built from the + raw object API name. They now resolve through the i18n service with the same + key shapes as the activity summaries (framework#3039): new + `messages.assignedToYou` / `messages.mentionedYou` / + `messages.mentionedYouAnonymous` templates (en / zh-CN / ja-JP / es-ES), the + object named by its translated label (`objects.{name}.label` → authored def + label → API name), and the locale resolved for the **recipient** (they read + the bell), not the acting user. Every step stays best-effort: no locale / no + i18n / key miss degrades to the English literal — which now also prefers the + authored object label over the API name. + + - **service-storage** — `sys_file` / `sys_upload_session` had no translation + bundle at all, so the file detail page (labels, and the Pending Upload / + Committed / Deleted status pipeline) rendered English on every locale. The + service now ships its own ADR-0029 D8 bundle (en / zh-CN / ja-JP / es-ES, + `src/translations` + `scripts/i18n-extract.config.ts`) and contributes it via + `i18n.loadTranslations` on `kernel:ready`, matching service-messaging. + (`sys_attachment` stays in platform-objects' bundles pending the + storage-domain decomposition.) + + - **runtime** — the in-app notifications REST surface (`GET +/api/v1/notifications`, `POST /api/v1/notifications/read`, `POST +/api/v1/notifications/read/all`; ADR-0030) had its `handleNotification` + dispatch branch and discovery entry, but no `server.()` mount in + `dispatcher-plugin`, so only the cloud hosts' hono catch-all reached it — the + standalone / `os dev` server 404'd every request. That left mark-read with no + working endpoint (the console's direct `sys_notification_receipt` write is + rejected by ADR-0103's engine-owned gate), so unread notifications could never + clear. The three routes are now mounted explicitly, guarded by the + route-registration regression test. + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [e057f42] +- Updated dependencies [a3823b2] +- Updated dependencies [bc65105] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [dd9f223] +- Updated dependencies [46e876c] +- Updated dependencies [5f05de2] +- Updated dependencies [021ba4c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [290e2f0] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + - @objectstack/platform-objects@16.0.0 + - @objectstack/core@16.0.0 + ## 16.0.0-rc.1 ### Patch Changes diff --git a/packages/plugins/plugin-audit/package.json b/packages/plugins/plugin-audit/package.json index f6d2ee982d..e8e1ed060e 100644 --- a/packages/plugins/plugin-audit/package.json +++ b/packages/plugins/plugin-audit/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/plugin-audit", - "version": "16.0.0-rc.1", + "version": "16.0.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 3d41ceddd5..a1fa0a014c 100644 --- a/packages/plugins/plugin-auth/CHANGELOG.md +++ b/packages/plugins/plugin-auth/CHANGELOG.md @@ -1,5 +1,110 @@ # Changelog +## 16.0.0 + +### Minor Changes + +- 616e839: **Bulk user import defaults to `auto` — prefer invite per row, temporary only for undeliverable rows (#3236).** The identity import endpoint (`POST /api/v1/auth/admin/import-users`) gains a fourth `passwordPolicy`, **`auto`**, and it is now the **default** (was `none`). + + `auto` decides **per row** instead of forcing one policy on the whole batch: + + - a row with a deliverable channel — a **real email + a wired email service**, or a **phone + a wired SMS-invite path** — is **invited** (a set-your-password email, or an invitation SMS for phone-only rows), so no shared secret ever leaves the server; + - a row with **no** deliverable channel (placeholder email, phone-only without SMS, or an email row when no email service is wired) falls back to a **temporary password**, returned once in the response with `must_change_password` stamped. + + This shrinks the temporary-password blast radius from "the whole batch" to "only the rows that genuinely can't be reached", and — unlike `invite` — `auto` **never rejects the request for missing infrastructure**: with nothing wired, every row simply degrades to temporary. The per-row outcome is surfaced on `rows[].delivery` (`email` / `sms` / `temporary`) with a batch breakdown on `summary.delivery` (also recorded in the run audit). + + The three existing policies are unchanged and still selectable explicitly: + + - `invite` — force the invite path for every row; unreachable rows are **failed** per-row (never downgraded). Pick this when a temporary-password fallback is unacceptable. + - `temporary` — force a generated temporary password for every row. + - `none` — identity only, no password and no invitation. + + **Behavior change to note:** callers that **omit** `passwordPolicy` previously got `none` (no credential, no outbound message); they now get `auto`, which proactively sends invitations to deliverable rows (and returns temporary passwords for the rest). Callers that want the old identity-only behavior must pass `passwordPolicy: 'none'` explicitly. Every call that already passes an explicit policy is unaffected, and the response is a strict superset (adds the `delivery` fields). + +### Patch Changes + +- deb7e7e: fix(plugin-auth): run better-auth adapter WRITES as system context so #2948 doesn't strip readonly identity columns (#3164) + + The better-auth ObjectQL adapter wrapped the engine so its READS carried + `isSystem` (to bypass the control-plane org-scope read hook), but its WRITES + passed through with no context. The static-`readonly` UPDATE strip (#2948) runs + on any non-system update — and since the adapter carries no caller context, + `!ctx?.isSystem` was `true`, so the strip silently DROPPED better-auth's own + writes to readonly `sys_user` columns: `email` (change-email), `banned` / + `ban_reason` / `ban_expires` (admin ban). Those operations returned success but + never persisted. + + `withSystemReadContext` is renamed to `withSystemContext` (a deprecated alias is + kept for one release) and now injects `isSystem` on `insert` / `update` / + `delete` as well as reads. This is correct because these are the identity + authority's own writes — user-context writes to `managedBy: 'better-auth'` tables + are already rejected upstream by the ADR-0092 identity write guard, so the + adapter path only ever carries better-auth's internal writes. + + Found while implementing #3043 (the INSERT-side readonly strip). This is its + UPDATE-side dual: #3043 relocated the insert strip to the external ingress + precisely because internal writers (this adapter included) don't declare + `isSystem`; the pre-existing engine-level UPDATE strip has no such relocation, so + the adapter had to declare its writes system. + +- fdc244e: Dev-loop DX fixes from the 15.1 third-party evaluation (P2 batch): + + - **Hot-added objects are now queryable without a restart.** Adding a `*.object.ts` under `os dev` used to recompile "green" while every query answered `no such table` (or `not registered`) until a manual restart: the artifact reload never notified the ObjectQL registry, tables were only created at boot, and seeds only loaded from the boot-time bundle. The `metadata:reloaded` payload now carries the parsed artifact; ObjectQL ingests the object definitions and re-runs the idempotent schema sync (same `skipSchemaSync` opt-out as boot), and the runtime loads seeds for first-seen objects (dev, single-tenant). `os dev` also prints `✚ new object(s): …` on recompile. + - **Dev admin credentials stay visible.** The `os dev` startup banner only showed `admin@objectos.ai / admin123` on the boot that actually seeded it; with the persistent default DB every later boot hid it, and the Console login page never knew it existed. The hint now re-arms on every dev boot for as long as the account still verifies against the default password, and `GET /api/v1/auth/config` exposes a dev-gated `devSeedAdmin` field (never present outside `NODE_ENV=development`) so the login page can show it. + - **`os doctor` reference analysis understands current metadata shapes.** Objects bound through `defineView` containers (`list`/`listViews`/`form`/`formViews` → `data.object`, subform `childObject`, lookup form fields) and app navigation (`objectName`, nested `children`, `areas`) were reported as "defined but not referenced". The collector now walks the canonical shapes (plus flow node `config.object`/`objectName`) and the orphan-view check descends into containers. + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [e057f42] +- Updated dependencies [a3823b2] +- Updated dependencies [bc65105] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [dd9f223] +- Updated dependencies [46e876c] +- Updated dependencies [5f05de2] +- Updated dependencies [021ba4c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [83e8f7d] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [290e2f0] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [92f5f19] +- Updated dependencies [32899e6] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + - @objectstack/platform-objects@16.0.0 + - @objectstack/rest@16.0.0 + - @objectstack/core@16.0.0 + - @objectstack/types@16.0.0 + ## 16.0.0-rc.1 ### Patch Changes diff --git a/packages/plugins/plugin-auth/package.json b/packages/plugins/plugin-auth/package.json index f2b4aa8f6f..e372a178ae 100644 --- a/packages/plugins/plugin-auth/package.json +++ b/packages/plugins/plugin-auth/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/plugin-auth", - "version": "16.0.0-rc.1", + "version": "16.0.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 20a27362f0..bd30e13889 100644 --- a/packages/plugins/plugin-dev/CHANGELOG.md +++ b/packages/plugins/plugin-dev/CHANGELOG.md @@ -1,5 +1,87 @@ # @objectstack/plugin-dev +## 16.0.0 + +### Patch Changes + +- Updated dependencies [b39c65d] +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [2f3c641] +- Updated dependencies [e38da5b] +- Updated dependencies [f9b118d] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [deb7e7e] +- Updated dependencies [a8aa34c] +- Updated dependencies [e057f42] +- Updated dependencies [a3823b2] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [fdc244e] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [dd9f223] +- Updated dependencies [46e876c] +- Updated dependencies [2ea08ee] +- Updated dependencies [d1d1c40] +- Updated dependencies [616e839] +- Updated dependencies [ee0a499] +- Updated dependencies [5f05de2] +- Updated dependencies [021ba4c] +- Updated dependencies [158aa14] +- Updated dependencies [9d897b3] +- Updated dependencies [62a2117] +- Updated dependencies [83e8f7d] +- Updated dependencies [d2723e2] +- Updated dependencies [674457a] +- Updated dependencies [fefcd54] +- Updated dependencies [efbcfe1] +- Updated dependencies [2049b6a] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [290e2f0] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [92f5f19] +- Updated dependencies [a2d6555] +- Updated dependencies [3a6310c] +- Updated dependencies [32899e6] +- Updated dependencies [515f11a] +- Updated dependencies [4174a07] +- Updated dependencies [ce468c8] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/runtime@16.0.0 + - @objectstack/spec@16.0.0 + - @objectstack/plugin-security@16.0.0 + - @objectstack/objectql@16.0.0 + - @objectstack/plugin-hono-server@16.0.0 + - @objectstack/rest@16.0.0 + - @objectstack/plugin-auth@16.0.0 + - @objectstack/core@16.0.0 + - @objectstack/types@16.0.0 + - @objectstack/account@16.0.0 + - @objectstack/setup@16.0.0 + - @objectstack/driver-memory@16.0.0 + - @objectstack/service-i18n@16.0.0 + ## 16.0.0-rc.1 ### Patch Changes diff --git a/packages/plugins/plugin-dev/package.json b/packages/plugins/plugin-dev/package.json index 0621b80895..2ccfe78330 100644 --- a/packages/plugins/plugin-dev/package.json +++ b/packages/plugins/plugin-dev/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/plugin-dev", - "version": "16.0.0-rc.1", + "version": "16.0.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 9f9b764df0..99b8560c18 100644 --- a/packages/plugins/plugin-email/CHANGELOG.md +++ b/packages/plugins/plugin-email/CHANGELOG.md @@ -1,5 +1,61 @@ # @objectstack/plugin-email +## 16.0.0 + +### Patch Changes + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [e057f42] +- Updated dependencies [a3823b2] +- Updated dependencies [bc65105] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [6b51346] +- Updated dependencies [80273c8] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [dd9f223] +- Updated dependencies [46e876c] +- Updated dependencies [7125007] +- Updated dependencies [5f05de2] +- Updated dependencies [021ba4c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [290e2f0] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [ea32ec7] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + - @objectstack/platform-objects@16.0.0 + - @objectstack/core@16.0.0 + - @objectstack/formula@16.0.0 + ## 16.0.0-rc.1 ### Patch Changes diff --git a/packages/plugins/plugin-email/package.json b/packages/plugins/plugin-email/package.json index e476b14ccf..a589219120 100644 --- a/packages/plugins/plugin-email/package.json +++ b/packages/plugins/plugin-email/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/plugin-email", - "version": "16.0.0-rc.1", + "version": "16.0.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 7135282eb7..9548ad5211 100644 --- a/packages/plugins/plugin-hono-server/CHANGELOG.md +++ b/packages/plugins/plugin-hono-server/CHANGELOG.md @@ -1,5 +1,159 @@ # @objectstack/plugin-hono-server +## 16.0.0 + +### Minor Changes + +- 22013aa: **Split the overloaded `managedBy: 'system'` bucket into engine-owned vs. admin-writable, and enforce engine-owned writes (ADR-0103, #3220).** The `system` bucket conflated two incompatible write policies: rows a platform service owns end to end (never user-written), and platform-defined schema whose rows are legitimately admin/user-writable. It carried the same all-false affordance row as `better-auth`/`append-only` but, unlike `better-auth`, had no engine enforcement — a wildcard admin could raw-write these rows through the generic data API (ADR-0049 gap). + + Rather than add a new `managedBy` enum value (which would fall through to fully-editable `platform` defaults on already-deployed Console clients), the write policy is now the **resolved affordance** (`resolveCrudAffordances` = bucket default + `userActions`), and _engine-owned_ is defined as a `system`/`append-only` object that grants no write: + + - **Writable set declares `userActions`** — the RBAC link tables (`sys_user_position`, `sys_user_permission_set`, `sys_position_permission_set`), `sys_user_preference`, `sys_approval_delegation`, and the messaging config grids (`sys_notification_preference` / `…_subscription` / `…_template`) now declare `userActions: { create, edit, delete: true }`. The affordance is a declaration only — the `DelegatedAdminGate` / RLS / permission sets remain the authz. + - **Engine-owned objects locked to reads** — `apiMethods: ['get','list']` added where absent (jobs, notifications, approval request/approver/token/action, `sys_record_share`, `sys_automation_run`, mail/settings/secret audit, the messaging delivery pipeline). `sys_secret` is explicitly read-locked (an empty `apiMethods` array fails open). + - **`sys_import_job`** stays engine-owned: the REST import route now writes its job rows `isSystem`-elevated (attribution preserved via the explicit `created_by` stamp) and the object is locked to `['get','list']`. + - **New engine write guard** (`assertEngineOwnedWriteAllowed`, plugin-security) fail-closed rejects user-context generic writes to engine-owned `system`/`append-only` objects, keyed off the resolved affordance; `isSystem` and context-less engine/service writes bypass by construction. Wired into the security middleware alongside the other data-layer gates. + - **`reconcileManagedApiMethods`** (objectql registry) now runs for **every** managed bucket, not just `better-auth`: any advertised write verb an object's resolved affordances forbid is stripped at registration with a warning (the drift backstop, ADR-0049). + - **`/me/permissions` clamp** (plugin-hono-server) now clamps `system`/`append-only` as well as `better-auth`, so the client hint reflects `permission ∩ guard`. + + **Potentially breaking:** a downstream/third-party `system` object that advertised generic write verbs relying on today's fail-open behaviour will have those verbs stripped (with a warning) and user-context generic writes to it rejected. Declare `userActions` opening the verbs the object legitimately takes from a user context. `better-auth` keeps plugin-auth's identity write guard unchanged; the row-level `managed_by` provenance vocabulary (ADR-0066) is a different axis and is untouched. + +- bfa3c3f: **Broadcast a `transactionalBatch` capability bit in discovery so clients negotiate the atomic cross-object batch declaratively, instead of runtime-probing 404/405/501 (#3298).** + + The atomic cross-object batch endpoint (`POST {basePath}/batch`, #1604 / ADR-0034 item 4) and its typed SDK surface (`client.data.batchTransaction`, #3271) already shipped, but discovery never told a client whether a backend actually supports it. Consumers (notably ObjectUI's `ObjectStackAdapter`) had to _probe_: fire a `/batch`, read `404`/`405` (no route) or `501` (no runtime transaction), and only then fall back to non-atomic client-side simulation. That is "find out by calling", not capability negotiation — it cannot be decided at connect time and cannot serve as the "minimum backend supports `/batch`" gate that blocks hard-deleting the non-atomic fallback downstream. + + `WellKnownCapabilitiesSchema` gains a required `transactionalBatch: boolean`, and **every** discovery producer fills it honestly (`declared === enforced`), so it never becomes a declared-but-unpopulated bit: + + - **`@objectstack/metadata-protocol`** (`getDiscovery`) — reports whether the runtime engine can honour a transaction (`typeof engine.transaction === 'function'`). The `/batch` handler runs its ops inside `engine.transaction()`, which degrades to a non-atomic passthrough (or 501) without one. + - **`@objectstack/rest`** (`/discovery`) — ANDs the engine signal with whether it actually mounts the route (`api.enableBatch`), so a server with batch disabled reports `false` even on a transaction-capable engine (never advertise an endpoint that would 404). + - **`@objectstack/plugin-hono-server`** (standalone discovery) — reports `false`: this minimal surface registers CRUD only and does not mount `/batch` (that ships with `@objectstack/rest`). Under-reporting is the safe direction — a client keeps its correct-but-slower fallback rather than losing atomicity. + - **`@objectstack/client`** — already normalizes hierarchical `capabilities` to flat booleans, so `client.capabilities.transactionalBatch` is exposed (and now typed) for declarative consumers. + + The bit follows the existing capability semantics: `true` ⟺ the `/batch` route is mounted **and** the runtime can honour a transaction — the exact condition under which the endpoint returns `200` rather than `404`/`405`/`501`. Additive and behavior-preserving; only the discovery payload gains a field. + +- 62a2117: **Split the overloaded `managedBy: 'system'` bucket with an explicit `engine-owned` value (ADR-0103 addendum, #3343).** ADR-0103 deferred the enum split ("revisitable later as a rename") because a new `managedBy` value would fall through to the fully-editable `platform` default on deployed Console clients. Both reasons against it are now retired — the server-side write guard / `apiMethods` reconciliation / `/me/permissions` clamp make that fallthrough cosmetic (the write is rejected regardless of what the client renders), and objectui#2712 closed the UI union — so v16 lands it, **additively**. + + - **New enum value `engine-owned`** with the same all-locked default affordance row as `system` (`create/import/edit/delete: false`, `exportCsv: true`). It joins `ENGINE_OWNED_BUCKETS` (the engine write guard) and `GUARDED_WRITE_BUCKETS` (the `/me/permissions` clamp); the guard, `reconcileManagedApiMethods`, and the clamp mechanisms are unchanged — `engine-owned` is an explicit member of the set they already covered by resolved affordance. + - **20 objects relabelled `system → engine-owned`** — the ones the engine owns end to end and that declared no write-opening `userActions` (the metadata store, jobs, approval runtime rows, sharing rows, `sys_automation_run`, the messaging delivery/receipt pipeline, `sys_secret`, settings). One-line, behaviour-identical per object. + - **8 admin/user-writable objects keep `managedBy: 'system'`** (the RBAC link tables, `sys_user_preference`, `sys_approval_delegation`, the messaging config grids) — `system` now reads as "engine-managed schema, writable via `userActions`". + + Behaviour-, enforcement- and wire-identical: resolved affordances, the guard verdict, the 405 `apiMethods` reconciliation, and the permissions clamp are the same before and after — this is a self-documenting relabel, not a policy change. No data migration (`managedBy` is schema metadata) and no code branches on the `'system'` literal. Retiring the overloaded `system` entirely (moving the 8 writable objects to a dedicated bucket) is a breaking rename deferred to v17. + +- efbcfe1: feat(observability): admin-only richer per-request timing detail via `X-OS-Debug-Timing: json` (#2408) + + Completes the optional "richer JSON" diagnostic from #2408. In addition to the + basic `Server-Timing` header, an admin/service caller can now request a + per-query breakdown — the slowest SQL statements and a query count — by sending + `X-OS-Debug-Timing: json`. The detail is returned in a separate + `X-OS-Debug-Timing-Detail` response header (compact JSON) and is **admin-only, + even under global mode**: an ordinary caller never sees SQL shapes. + + - **observability**: `PerfTiming` gains opt-in per-event detail capture + (`enableDetail` / `recordDetail` / `details`) plus the ambient + `recordServerTimingDetail`. The disclosure gate gains a `privileged` level + (set by `allowPerfDisclosure`, read via `isPerfDisclosurePrivileged`) so the + richer detail can be gated independently of the basic header. + - **driver-sql**: when detail capture is on, the query listener additionally + records each query's **parametrized** statement (knex's `q.sql`, `?` + placeholders) — never the bindings, so no literal row value ever enters the + collector. Zero overhead when detail is off. + - **plugin-hono-server**: `X-OS-Debug-Timing: json` enables detail capture; the + middleware emits `X-OS-Debug-Timing-Detail` (slowest queries, capped and + sanitized to header-safe ASCII) only when the principal is a proven admin. + + Basic and global behavior are unchanged; `json` is purely additive. + +- 2049b6a: feat(observability): admin-gated per-request `Server-Timing` via `X-OS-Debug-Timing` (#2408) + + Perf-tuning mode was previously global-only (`serverTiming` option / + `OS_SERVER_TIMING`), which discloses internal phase durations — a mild + backend-fingerprinting surface — to every caller. This adds the per-request + gating path from the design so an operator can pull a single request's + `Server-Timing` breakdown on a live environment without turning the header on + for everyone. + + - **observability**: a request-scoped disclosure gate (`runWithPerfDisclosure`, + `allowPerfDisclosure`, `isPerfDisclosureAllowed`, `PerfDisclosureGate`) kept + separate from the pure `PerfTiming` collector and pinned to its own + `Symbol.for` store so the middleware and dispatcher share it across module + copies. + - **plugin-hono-server**: the Server-Timing middleware is registered by default + (unless `serverTiming: false`). It runs the collector when timing is global + **or** the request sends `X-OS-Debug-Timing: 1`, and emits the header only + when the gate is open. `OS_PERF_TIMING=1` now also enables global mode. + - **runtime**: after resolving the execution context, the dispatcher opens the + gate for admin/service/system principals, so ordinary callers never receive + the header even if they send the debug header. + + Existing global-mode behavior is unchanged. + +### Patch Changes + +- ce468c8: feat(observability): decompose `Server-Timing` into auth / db / hooks / serialize spans (perf-tuning mode) + + The opt-in `Server-Timing` header now breaks a request's server time into the phases that actually explain it, so an operator can open DevTools → Network → Timing and see where the time went without standing up an external tracing backend: + + - **`db`** — total SQL time with a **query count**. The SQL driver wires knex's `query` / `query-response` events (keyed by `__knexQueryUid`) and folds each query into one aggregate member (`db;dur=210;desc="6 queries"`) — the query count is the number most useful for spotting N sequential round-trips. Timing is attributed to the originating request via `AsyncLocalStorage`, so it is correct under concurrency and never cross-attributes. SQL text is never emitted, only durations and a count. + - **`auth`** — identity / session resolution in the dispatcher, the prime suspect for unexplained data-API overhead. + - **`hooks`** — total business-hook execution time with a hook count, fed through the engine's existing `HookMetricsRecorder` seam (wired from the runtime, so `@objectstack/objectql`'s lean `core` tier stays observability-free). + - **`serialize`** — response JSON encoding in the HTTP adapter. + + Adds `countServerTiming(name, dur, unit)` (and `PerfTiming.count`) to fold high-frequency phases into a single aggregate member instead of flooding the header. Every phase is a no-op when perf-tuning is off (`serverTiming: true` / `OS_SERVER_TIMING=true`), so there is zero measurable overhead on the normal path. + + Closes #2408. + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [e057f42] +- Updated dependencies [a3823b2] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [dd9f223] +- Updated dependencies [46e876c] +- Updated dependencies [5f05de2] +- Updated dependencies [021ba4c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [83e8f7d] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [efbcfe1] +- Updated dependencies [2049b6a] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [290e2f0] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [92f5f19] +- Updated dependencies [32899e6] +- Updated dependencies [ce468c8] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + - @objectstack/core@16.0.0 + - @objectstack/types@16.0.0 + - @objectstack/observability@16.0.0 + ## 16.0.0-rc.1 ### Minor Changes diff --git a/packages/plugins/plugin-hono-server/package.json b/packages/plugins/plugin-hono-server/package.json index cd4d8125eb..6dc4c9174f 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": "16.0.0-rc.1", + "version": "16.0.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 index fdf05bc20a..9f56076291 100644 --- a/packages/plugins/plugin-pinyin-search/CHANGELOG.md +++ b/packages/plugins/plugin-pinyin-search/CHANGELOG.md @@ -1,5 +1,34 @@ # @objectstack/plugin-pinyin-search +## 16.0.0 + +### Patch Changes + +- Updated dependencies [22013aa] +- Updated dependencies [a8aa34c] +- Updated dependencies [e057f42] +- Updated dependencies [a3823b2] +- Updated dependencies [fdc244e] +- Updated dependencies [dd9f223] +- Updated dependencies [2ea08ee] +- Updated dependencies [5f05de2] +- Updated dependencies [021ba4c] +- Updated dependencies [83e8f7d] +- Updated dependencies [d2723e2] +- Updated dependencies [674457a] +- Updated dependencies [6c270a6] +- Updated dependencies [290e2f0] +- Updated dependencies [668dd17] +- Updated dependencies [92f5f19] +- Updated dependencies [32899e6] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [86d30af] +- Updated dependencies [2018df9] + - @objectstack/objectql@16.0.0 + - @objectstack/core@16.0.0 + - @objectstack/types@16.0.0 + ## 16.0.0-rc.1 ### Patch Changes diff --git a/packages/plugins/plugin-pinyin-search/package.json b/packages/plugins/plugin-pinyin-search/package.json index 194f34246f..8f02d5f5cb 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": "16.0.0-rc.1", + "version": "16.0.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 fbecb05aa0..9ecc9f4976 100644 --- a/packages/plugins/plugin-reports/CHANGELOG.md +++ b/packages/plugins/plugin-reports/CHANGELOG.md @@ -1,5 +1,56 @@ # @objectstack/plugin-reports +## 16.0.0 + +### Patch Changes + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [e057f42] +- Updated dependencies [a3823b2] +- Updated dependencies [bc65105] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [dd9f223] +- Updated dependencies [46e876c] +- Updated dependencies [5f05de2] +- Updated dependencies [021ba4c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [290e2f0] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + - @objectstack/platform-objects@16.0.0 + - @objectstack/core@16.0.0 + ## 16.0.0-rc.1 ### Patch Changes diff --git a/packages/plugins/plugin-reports/package.json b/packages/plugins/plugin-reports/package.json index 75fe8e3510..20c36c50e2 100644 --- a/packages/plugins/plugin-reports/package.json +++ b/packages/plugins/plugin-reports/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/plugin-reports", - "version": "16.0.0-rc.1", + "version": "16.0.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 de63c08e23..44cfd69b41 100644 --- a/packages/plugins/plugin-security/CHANGELOG.md +++ b/packages/plugins/plugin-security/CHANGELOG.md @@ -1,5 +1,100 @@ # @objectstack/plugin-security +## 16.0.0 + +### Minor Changes + +- 22013aa: **Split the overloaded `managedBy: 'system'` bucket into engine-owned vs. admin-writable, and enforce engine-owned writes (ADR-0103, #3220).** The `system` bucket conflated two incompatible write policies: rows a platform service owns end to end (never user-written), and platform-defined schema whose rows are legitimately admin/user-writable. It carried the same all-false affordance row as `better-auth`/`append-only` but, unlike `better-auth`, had no engine enforcement — a wildcard admin could raw-write these rows through the generic data API (ADR-0049 gap). + + Rather than add a new `managedBy` enum value (which would fall through to fully-editable `platform` defaults on already-deployed Console clients), the write policy is now the **resolved affordance** (`resolveCrudAffordances` = bucket default + `userActions`), and _engine-owned_ is defined as a `system`/`append-only` object that grants no write: + + - **Writable set declares `userActions`** — the RBAC link tables (`sys_user_position`, `sys_user_permission_set`, `sys_position_permission_set`), `sys_user_preference`, `sys_approval_delegation`, and the messaging config grids (`sys_notification_preference` / `…_subscription` / `…_template`) now declare `userActions: { create, edit, delete: true }`. The affordance is a declaration only — the `DelegatedAdminGate` / RLS / permission sets remain the authz. + - **Engine-owned objects locked to reads** — `apiMethods: ['get','list']` added where absent (jobs, notifications, approval request/approver/token/action, `sys_record_share`, `sys_automation_run`, mail/settings/secret audit, the messaging delivery pipeline). `sys_secret` is explicitly read-locked (an empty `apiMethods` array fails open). + - **`sys_import_job`** stays engine-owned: the REST import route now writes its job rows `isSystem`-elevated (attribution preserved via the explicit `created_by` stamp) and the object is locked to `['get','list']`. + - **New engine write guard** (`assertEngineOwnedWriteAllowed`, plugin-security) fail-closed rejects user-context generic writes to engine-owned `system`/`append-only` objects, keyed off the resolved affordance; `isSystem` and context-less engine/service writes bypass by construction. Wired into the security middleware alongside the other data-layer gates. + - **`reconcileManagedApiMethods`** (objectql registry) now runs for **every** managed bucket, not just `better-auth`: any advertised write verb an object's resolved affordances forbid is stripped at registration with a warning (the drift backstop, ADR-0049). + - **`/me/permissions` clamp** (plugin-hono-server) now clamps `system`/`append-only` as well as `better-auth`, so the client hint reflects `permission ∩ guard`. + + **Potentially breaking:** a downstream/third-party `system` object that advertised generic write verbs relying on today's fail-open behaviour will have those verbs stripped (with a warning) and user-context generic writes to it rejected. Declare `userActions` opening the verbs the object legitimately takes from a user context. `better-auth` keeps plugin-auth's identity write guard unchanged; the row-level `managed_by` provenance vocabulary (ADR-0066) is a different axis and is untouched. + +- 62a2117: **Split the overloaded `managedBy: 'system'` bucket with an explicit `engine-owned` value (ADR-0103 addendum, #3343).** ADR-0103 deferred the enum split ("revisitable later as a rename") because a new `managedBy` value would fall through to the fully-editable `platform` default on deployed Console clients. Both reasons against it are now retired — the server-side write guard / `apiMethods` reconciliation / `/me/permissions` clamp make that fallthrough cosmetic (the write is rejected regardless of what the client renders), and objectui#2712 closed the UI union — so v16 lands it, **additively**. + + - **New enum value `engine-owned`** with the same all-locked default affordance row as `system` (`create/import/edit/delete: false`, `exportCsv: true`). It joins `ENGINE_OWNED_BUCKETS` (the engine write guard) and `GUARDED_WRITE_BUCKETS` (the `/me/permissions` clamp); the guard, `reconcileManagedApiMethods`, and the clamp mechanisms are unchanged — `engine-owned` is an explicit member of the set they already covered by resolved affordance. + - **20 objects relabelled `system → engine-owned`** — the ones the engine owns end to end and that declared no write-opening `userActions` (the metadata store, jobs, approval runtime rows, sharing rows, `sys_automation_run`, the messaging delivery/receipt pipeline, `sys_secret`, settings). One-line, behaviour-identical per object. + - **8 admin/user-writable objects keep `managedBy: 'system'`** (the RBAC link tables, `sys_user_preference`, `sys_approval_delegation`, the messaging config grids) — `system` now reads as "engine-managed schema, writable via `userActions`". + + Behaviour-, enforcement- and wire-identical: resolved affordances, the guard verdict, the 405 `apiMethods` reconciliation, and the permissions clamp are the same before and after — this is a self-documenting relabel, not a policy change. No data migration (`managedBy` is schema metadata) and no code branches on the `'system'` literal. Retiring the overloaded `system` entirely (moving the 8 writable objects to a dedicated bucket) is a breaking rename deferred to v17. + +### Patch Changes + +- 2f3c641: ADR-0099 P0: land the probe-vs-carried-rung equivalence gate in the authz matrix (`authz-matrix-gate.test.ts`) — seeded-shape equivalence cells, two adversarial `KNOWN DIVERGENCE` pins (scoped `admin_full_access` grant; piecemeal platform-exclusive capability), the I2 nesting and I3 narrowing invariant cells, posture-blindness staging pins for the P1 flip, and the EXTERNAL dead-branch cell. Extracts the platform-admin capability probe as the exported pure `hasPlatformAdminCapability` (mechanical, behavior unchanged). Test-only gate; the ADR-0099 P1 flip lands behind it (#3211). +- e38da5b: ADR-0099 P1 (#3211 M2): the Layer 0 cross-tenant exemption gate now reads the carried `ctx.posture` rung (#2956) as authoritative, with the platform-admin capability probe demoted to a fallback for resolver-less contexts (delegated-admin bridge, sharing service, `getReadFilter`). The read and write (insert/update post-image) tenant checks share one decision (`computeLayeredRlsFilter`), so they cannot drift. A probe↔rung disagreement logs a defect breadcrumb and enforces the narrower rung verdict. + + **Behavior change (security narrowing, multi-org / `@objectstack/organizations` only):** a principal whose carried rung is not `PLATFORM_ADMIN` no longer crosses the tenant wall on private / platform-global / better-auth-managed objects, even when its resolved permission sets carry a platform-exclusive capability. Two shapes are affected: (a) a **scoped** `admin_full_access` grant (`sys_user_permission_set.organization_id` non-null), and (b) a custom set granting a platform capability (e.g. `studio.access`) piecemeal alongside a superuser bit. Both are now walled to their own org — the fail-safe direction (the carried rung is a strict subset of the probe). Single-org / env-per-database deployments are unaffected (Layer 0 is inert). + + **Upgrade check:** before upgrading, scan `sys_user_permission_set` for `admin_full_access` rows with a non-null `organization_id`, and custom permission sets whose `systemPermissions` intersect `{manage_metadata, manage_platform_settings, studio.access, manage_users}`. To restore cross-tenant operator access for such a principal, grant the **unscoped** `admin_full_access` instead. The `[authz/ADR-0099]` warn log names any principal hitting the divergence at runtime. + +- f9b118d: ADR-0099 P2′ (#3211 M3′): pin the two-axis Amendment in the authz matrix. The original P2 (collapse the Layer 1 tier onto posture) was rejected — Layer 1's tier input is the per-object super-bit, a per-principal × per-object delegation primitive posture cannot represent. New cells pin: seeded-face agreement (seeded super-bit holders are already ≥ TENANT_ADMIN), the load-bearing delegation cell (a MEMBER with a delegated per-object `viewAllRecords`/`modifyAllRecords` short-circuits Layer 1 yet stays walled by Layer 0 — the auditor pattern), invariant I7 (the scope axis never crosses a boundary posture has not opened), and the contrast that the bit is a real grantable capability, not conditionally inert. Test-only; zero behavior change. +- 9d897b3: **Derive the better-auth managed-object write denies from the live registry (#3325, follow-through of ADR-0092 / ADR-0103).** The default permission sets deny generic writes on better-auth identity tables via a hand-maintained `BETTER_AUTH_MANAGED_OBJECTS` list — exactly the drift ADR-0092 forbids, and it had already drifted (the list carried 17 names while 22 schemas declare `managedBy: 'better-auth'`, leaving `sys_scim_provider`, `sys_sso_provider`, and three `sys_oauth_*` tables wildcard-granted for writes at the permission-evaluator layer; the identity write guard still 403'd the actual write, so this was a defense-in-depth gap, not a live hole). + + - New `applyManagedWriteDenies` (`managed-object-write-denies.ts`) injects a read-only-write deny for every registered `managedBy: 'better-auth'` object into the four write-granting default sets (`organization_admin`, `member_default`, `viewer_readonly`, MCP write) at `kernel:ready`, mutating the shared in-memory `bootstrapPermissionSets` in place (the array the evaluator resolves and the seeder serializes — a DB-row-only fix would be dead code). Never touches `admin_full_access`, never overrides an existing explicit entry, ignores `userActions` (the better-auth bucket is hard-denied — `sys_user`'s `userActions.edit` opens only a field-level whitelist the identity guard enforces). + - The static `BETTER_AUTH_MANAGED_OBJECTS` list is completed to 22 and kept as a compile-time baseline (covers the pre-`kernel:ready` window), now pinned bidirectionally against the `@objectstack/platform-objects` schemas by a test so it cannot silently rot again. + - Engine-owned `system`/`append-only` objects are deliberately NOT given deny entries — a per-object entry overrides the wildcard and would drop `viewAllRecords`; their writes are already rejected by the ADR-0103 engine guard. + + No public API change; the helper is internal. Behavior is byte-preserving for the 17 already-listed tables and closes the gap on the 5 that had drifted. + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [e057f42] +- Updated dependencies [a3823b2] +- Updated dependencies [bc65105] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [6b51346] +- Updated dependencies [80273c8] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [dd9f223] +- Updated dependencies [46e876c] +- Updated dependencies [7125007] +- Updated dependencies [5f05de2] +- Updated dependencies [021ba4c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [290e2f0] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [ea32ec7] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + - @objectstack/platform-objects@16.0.0 + - @objectstack/core@16.0.0 + - @objectstack/formula@16.0.0 + ## 16.0.0-rc.1 ### Minor Changes diff --git a/packages/plugins/plugin-security/package.json b/packages/plugins/plugin-security/package.json index ad8957af19..dd41b7a279 100644 --- a/packages/plugins/plugin-security/package.json +++ b/packages/plugins/plugin-security/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/plugin-security", - "version": "16.0.0-rc.1", + "version": "16.0.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 364907f2f2..f5ef85df16 100644 --- a/packages/plugins/plugin-sharing/CHANGELOG.md +++ b/packages/plugins/plugin-sharing/CHANGELOG.md @@ -1,5 +1,86 @@ # @objectstack/plugin-sharing +## 16.0.0 + +### Patch Changes + +- 22013aa: **Split the overloaded `managedBy: 'system'` bucket into engine-owned vs. admin-writable, and enforce engine-owned writes (ADR-0103, #3220).** The `system` bucket conflated two incompatible write policies: rows a platform service owns end to end (never user-written), and platform-defined schema whose rows are legitimately admin/user-writable. It carried the same all-false affordance row as `better-auth`/`append-only` but, unlike `better-auth`, had no engine enforcement — a wildcard admin could raw-write these rows through the generic data API (ADR-0049 gap). + + Rather than add a new `managedBy` enum value (which would fall through to fully-editable `platform` defaults on already-deployed Console clients), the write policy is now the **resolved affordance** (`resolveCrudAffordances` = bucket default + `userActions`), and _engine-owned_ is defined as a `system`/`append-only` object that grants no write: + + - **Writable set declares `userActions`** — the RBAC link tables (`sys_user_position`, `sys_user_permission_set`, `sys_position_permission_set`), `sys_user_preference`, `sys_approval_delegation`, and the messaging config grids (`sys_notification_preference` / `…_subscription` / `…_template`) now declare `userActions: { create, edit, delete: true }`. The affordance is a declaration only — the `DelegatedAdminGate` / RLS / permission sets remain the authz. + - **Engine-owned objects locked to reads** — `apiMethods: ['get','list']` added where absent (jobs, notifications, approval request/approver/token/action, `sys_record_share`, `sys_automation_run`, mail/settings/secret audit, the messaging delivery pipeline). `sys_secret` is explicitly read-locked (an empty `apiMethods` array fails open). + - **`sys_import_job`** stays engine-owned: the REST import route now writes its job rows `isSystem`-elevated (attribution preserved via the explicit `created_by` stamp) and the object is locked to `['get','list']`. + - **New engine write guard** (`assertEngineOwnedWriteAllowed`, plugin-security) fail-closed rejects user-context generic writes to engine-owned `system`/`append-only` objects, keyed off the resolved affordance; `isSystem` and context-less engine/service writes bypass by construction. Wired into the security middleware alongside the other data-layer gates. + - **`reconcileManagedApiMethods`** (objectql registry) now runs for **every** managed bucket, not just `better-auth`: any advertised write verb an object's resolved affordances forbid is stripped at registration with a warning (the drift backstop, ADR-0049). + - **`/me/permissions` clamp** (plugin-hono-server) now clamps `system`/`append-only` as well as `better-auth`, so the client hint reflects `permission ∩ guard`. + + **Potentially breaking:** a downstream/third-party `system` object that advertised generic write verbs relying on today's fail-open behaviour will have those verbs stripped (with a warning) and user-context generic writes to it rejected. Declare `userActions` opening the verbs the object legitimately takes from a user context. `better-auth` keeps plugin-auth's identity write guard unchanged; the row-level `managed_by` provenance vocabulary (ADR-0066) is a different axis and is untouched. + +- 62a2117: **Split the overloaded `managedBy: 'system'` bucket with an explicit `engine-owned` value (ADR-0103 addendum, #3343).** ADR-0103 deferred the enum split ("revisitable later as a rename") because a new `managedBy` value would fall through to the fully-editable `platform` default on deployed Console clients. Both reasons against it are now retired — the server-side write guard / `apiMethods` reconciliation / `/me/permissions` clamp make that fallthrough cosmetic (the write is rejected regardless of what the client renders), and objectui#2712 closed the UI union — so v16 lands it, **additively**. + + - **New enum value `engine-owned`** with the same all-locked default affordance row as `system` (`create/import/edit/delete: false`, `exportCsv: true`). It joins `ENGINE_OWNED_BUCKETS` (the engine write guard) and `GUARDED_WRITE_BUCKETS` (the `/me/permissions` clamp); the guard, `reconcileManagedApiMethods`, and the clamp mechanisms are unchanged — `engine-owned` is an explicit member of the set they already covered by resolved affordance. + - **20 objects relabelled `system → engine-owned`** — the ones the engine owns end to end and that declared no write-opening `userActions` (the metadata store, jobs, approval runtime rows, sharing rows, `sys_automation_run`, the messaging delivery/receipt pipeline, `sys_secret`, settings). One-line, behaviour-identical per object. + - **8 admin/user-writable objects keep `managedBy: 'system'`** (the RBAC link tables, `sys_user_preference`, `sys_approval_delegation`, the messaging config grids) — `system` now reads as "engine-managed schema, writable via `userActions`". + + Behaviour-, enforcement- and wire-identical: resolved affordances, the guard verdict, the 405 `apiMethods` reconciliation, and the permissions clamp are the same before and after — this is a self-documenting relabel, not a policy change. No data migration (`managedBy` is schema metadata) and no code branches on the `'system'` literal. Retiring the overloaded `system` entirely (moving the 8 writable objects to a dedicated bucket) is a breaking rename deferred to v17. + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [e057f42] +- Updated dependencies [a3823b2] +- Updated dependencies [bc65105] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [6b51346] +- Updated dependencies [80273c8] +- Updated dependencies [fdc244e] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [dd9f223] +- Updated dependencies [46e876c] +- Updated dependencies [2ea08ee] +- Updated dependencies [7125007] +- Updated dependencies [5f05de2] +- Updated dependencies [021ba4c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [d2723e2] +- Updated dependencies [674457a] +- Updated dependencies [fefcd54] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [290e2f0] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [ea32ec7] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + - @objectstack/platform-objects@16.0.0 + - @objectstack/objectql@16.0.0 + - @objectstack/core@16.0.0 + - @objectstack/formula@16.0.0 + ## 16.0.0-rc.1 ### Patch Changes diff --git a/packages/plugins/plugin-sharing/package.json b/packages/plugins/plugin-sharing/package.json index 513cf73860..69d6509764 100644 --- a/packages/plugins/plugin-sharing/package.json +++ b/packages/plugins/plugin-sharing/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/plugin-sharing", - "version": "16.0.0-rc.1", + "version": "16.0.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 206295e0d2..ca19a3fe5f 100644 --- a/packages/plugins/plugin-webhooks/CHANGELOG.md +++ b/packages/plugins/plugin-webhooks/CHANGELOG.md @@ -1,5 +1,62 @@ # @objectstack/plugin-webhooks +## 16.0.0 + +### Patch Changes + +- 4b6fde8: Trim the dead `undelete` and `api` webhook triggers (#3196). `WebhookTriggerType` declared five triggers but only three ever fired: + + - `undelete` had no event source — the engine has no soft-delete/restore capability (`delete` is a hard delete; no `deleted_at` convention, no restore operation, and `data.record.undeleted` is never emitted). The `undeleted` case in the auto-enqueuer's action mapper was dead code awaiting a producer that doesn't exist. + - `api` ("manually triggered") had no fire path — the only webhook HTTP surface re-queues already-failed deliveries; nothing originates a manual fire. + + Both are removed from the enum (contract-first, matching #3184/#3195): authoring a webhook on a removed trigger now fails loudly at `os validate` / registration instead of registering a webhook that silently never fires. No shipped webhook metadata used either. The auto-enqueuer now also warns when a persisted `sys_webhook` row carries a trigger it can't map to an emitted record event (a drift-guard, so a dead trigger can't silently no-op again). Reintroduce `undelete` only alongside a real restore subsystem, and `api` only alongside a real manual-fire endpoint. Updated the `sys_webhook` trigger options, field help (all locales), docs, and reference; added rejection tests. + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [e057f42] +- Updated dependencies [a3823b2] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [dd9f223] +- Updated dependencies [46e876c] +- Updated dependencies [5f05de2] +- Updated dependencies [021ba4c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [290e2f0] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + - @objectstack/service-messaging@16.0.0 + - @objectstack/core@16.0.0 + ## 16.0.0-rc.1 ### Patch Changes diff --git a/packages/plugins/plugin-webhooks/package.json b/packages/plugins/plugin-webhooks/package.json index 0a659641d8..283e8c0c01 100644 --- a/packages/plugins/plugin-webhooks/package.json +++ b/packages/plugins/plugin-webhooks/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/plugin-webhooks", - "version": "16.0.0-rc.1", + "version": "16.0.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 260ae175c7..1656cbe3f5 100644 --- a/packages/qa/dogfood/CHANGELOG.md +++ b/packages/qa/dogfood/CHANGELOG.md @@ -1,5 +1,75 @@ # @objectstack/dogfood +## 0.0.38 + +### Patch Changes + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [41e703b] +- Updated dependencies [2f3c641] +- Updated dependencies [e38da5b] +- Updated dependencies [f9b118d] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [deb7e7e] +- Updated dependencies [a8aa34c] +- Updated dependencies [e057f42] +- Updated dependencies [a3823b2] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [fdc244e] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [46e876c] +- Updated dependencies [2ea08ee] +- Updated dependencies [616e839] +- Updated dependencies [ee0a499] +- Updated dependencies [158aa14] +- Updated dependencies [9d897b3] +- Updated dependencies [62a2117] +- Updated dependencies [15dbe18] +- Updated dependencies [83e8f7d] +- Updated dependencies [230358c] +- Updated dependencies [d2723e2] +- Updated dependencies [674457a] +- Updated dependencies [fefcd54] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + - @objectstack/connector-openapi@16.0.0 + - @objectstack/plugin-security@16.0.0 + - @objectstack/objectql@16.0.0 + - @objectstack/plugin-auth@16.0.0 + - @objectstack/plugin-audit@16.0.0 + - @objectstack/service-storage@16.0.0 + - @objectstack/mcp@16.0.0 + - @objectstack/example-crm@4.0.90 + - @objectstack/example-showcase@0.3.12 + - @objectstack/verify@16.0.0 + - @objectstack/connector-mcp@16.0.0 + - @objectstack/connector-rest@16.0.0 + ## 0.0.38-rc.1 ### Patch Changes diff --git a/packages/qa/dogfood/package.json b/packages/qa/dogfood/package.json index b272d9b3ae..42a94ba858 100644 --- a/packages/qa/dogfood/package.json +++ b/packages/qa/dogfood/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/dogfood", - "version": "0.0.38-rc.1", + "version": "0.0.38", "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 242b4f7e70..e5cf7e4ee2 100644 --- a/packages/qa/downstream-contract/CHANGELOG.md +++ b/packages/qa/downstream-contract/CHANGELOG.md @@ -1,5 +1,48 @@ # @objectstack/downstream-contract +## 0.0.36 + +### Patch Changes + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [a3823b2] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [46e876c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + ## 0.0.36-rc.1 ### Patch Changes diff --git a/packages/qa/downstream-contract/package.json b/packages/qa/downstream-contract/package.json index 76fa779fe2..5ec4d14048 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.36-rc.1", + "version": "0.0.36", "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 index bf9929eef4..d9eef0bf64 100644 --- a/packages/qa/http-conformance/CHANGELOG.md +++ b/packages/qa/http-conformance/CHANGELOG.md @@ -1,5 +1,16 @@ # @objectstack/http-conformance +## 0.0.4 + +### Patch Changes + +- Updated dependencies [e057f42] +- Updated dependencies [dd9f223] +- Updated dependencies [5f05de2] +- Updated dependencies [021ba4c] +- Updated dependencies [290e2f0] + - @objectstack/core@16.0.0 + ## 0.0.4-rc.1 ### Patch Changes diff --git a/packages/qa/http-conformance/package.json b/packages/qa/http-conformance/package.json index 225f690e78..4bec23a722 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.4-rc.1", + "version": "0.0.4", "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 d9f8521773..f25416e989 100644 --- a/packages/rest/CHANGELOG.md +++ b/packages/rest/CHANGELOG.md @@ -1,5 +1,145 @@ # @objectstack/rest +## 16.0.0 + +### Minor Changes + +- bfa3c3f: **Broadcast a `transactionalBatch` capability bit in discovery so clients negotiate the atomic cross-object batch declaratively, instead of runtime-probing 404/405/501 (#3298).** + + The atomic cross-object batch endpoint (`POST {basePath}/batch`, #1604 / ADR-0034 item 4) and its typed SDK surface (`client.data.batchTransaction`, #3271) already shipped, but discovery never told a client whether a backend actually supports it. Consumers (notably ObjectUI's `ObjectStackAdapter`) had to _probe_: fire a `/batch`, read `404`/`405` (no route) or `501` (no runtime transaction), and only then fall back to non-atomic client-side simulation. That is "find out by calling", not capability negotiation — it cannot be decided at connect time and cannot serve as the "minimum backend supports `/batch`" gate that blocks hard-deleting the non-atomic fallback downstream. + + `WellKnownCapabilitiesSchema` gains a required `transactionalBatch: boolean`, and **every** discovery producer fills it honestly (`declared === enforced`), so it never becomes a declared-but-unpopulated bit: + + - **`@objectstack/metadata-protocol`** (`getDiscovery`) — reports whether the runtime engine can honour a transaction (`typeof engine.transaction === 'function'`). The `/batch` handler runs its ops inside `engine.transaction()`, which degrades to a non-atomic passthrough (or 501) without one. + - **`@objectstack/rest`** (`/discovery`) — ANDs the engine signal with whether it actually mounts the route (`api.enableBatch`), so a server with batch disabled reports `false` even on a transaction-capable engine (never advertise an endpoint that would 404). + - **`@objectstack/plugin-hono-server`** (standalone discovery) — reports `false`: this minimal surface registers CRUD only and does not mount `/batch` (that ships with `@objectstack/rest`). Under-reporting is the safe direction — a client keeps its correct-but-slower fallback rather than losing atomicity. + - **`@objectstack/client`** — already normalizes hierarchical `capabilities` to flat booleans, so `client.capabilities.transactionalBatch` is exposed (and now typed) for declarative consumers. + + The bit follows the existing capability semantics: `true` ⟺ the `/batch` route is mounted **and** the runtime can honour a transaction — the exact condition under which the endpoint returns `200` rather than `404`/`405`/`501`. Additive and behavior-preserving; only the discovery payload gains a field. + +### Patch Changes + +- 22013aa: **Split the overloaded `managedBy: 'system'` bucket into engine-owned vs. admin-writable, and enforce engine-owned writes (ADR-0103, #3220).** The `system` bucket conflated two incompatible write policies: rows a platform service owns end to end (never user-written), and platform-defined schema whose rows are legitimately admin/user-writable. It carried the same all-false affordance row as `better-auth`/`append-only` but, unlike `better-auth`, had no engine enforcement — a wildcard admin could raw-write these rows through the generic data API (ADR-0049 gap). + + Rather than add a new `managedBy` enum value (which would fall through to fully-editable `platform` defaults on already-deployed Console clients), the write policy is now the **resolved affordance** (`resolveCrudAffordances` = bucket default + `userActions`), and _engine-owned_ is defined as a `system`/`append-only` object that grants no write: + + - **Writable set declares `userActions`** — the RBAC link tables (`sys_user_position`, `sys_user_permission_set`, `sys_position_permission_set`), `sys_user_preference`, `sys_approval_delegation`, and the messaging config grids (`sys_notification_preference` / `…_subscription` / `…_template`) now declare `userActions: { create, edit, delete: true }`. The affordance is a declaration only — the `DelegatedAdminGate` / RLS / permission sets remain the authz. + - **Engine-owned objects locked to reads** — `apiMethods: ['get','list']` added where absent (jobs, notifications, approval request/approver/token/action, `sys_record_share`, `sys_automation_run`, mail/settings/secret audit, the messaging delivery pipeline). `sys_secret` is explicitly read-locked (an empty `apiMethods` array fails open). + - **`sys_import_job`** stays engine-owned: the REST import route now writes its job rows `isSystem`-elevated (attribution preserved via the explicit `created_by` stamp) and the object is locked to `['get','list']`. + - **New engine write guard** (`assertEngineOwnedWriteAllowed`, plugin-security) fail-closed rejects user-context generic writes to engine-owned `system`/`append-only` objects, keyed off the resolved affordance; `isSystem` and context-less engine/service writes bypass by construction. Wired into the security middleware alongside the other data-layer gates. + - **`reconcileManagedApiMethods`** (objectql registry) now runs for **every** managed bucket, not just `better-auth`: any advertised write verb an object's resolved affordances forbid is stripped at registration with a warning (the drift backstop, ADR-0049). + - **`/me/permissions` clamp** (plugin-hono-server) now clamps `system`/`append-only` as well as `better-auth`, so the client hint reflects `permission ∩ guard`. + + **Potentially breaking:** a downstream/third-party `system` object that advertised generic write verbs relying on today's fail-open behaviour will have those verbs stripped (with a warning) and user-context generic writes to it rejected. Declare `userActions` opening the verbs the object legitimately takes from a user context. `better-auth` keeps plugin-auth's identity write guard unchanged; the row-level `managed_by` provenance vocabulary (ADR-0066) is a different axis and is untouched. + +- e057f42: fix: harden the bulk-write path — retries, idempotency, contracts, and summary visibility (#3147–#3152) + + Six reliability fixes to the batched seed/import + `engine.insert(array)` path + introduced by the #2678 bulk-write rework: + + - **#3151** `bulkWrite` validates that `writeBatch` returns one record per input + row (a short/long/non-array return is degraded per-row, not backfilled as + phantom success); `engine.insert(array)` likewise rejects a short driver + `bulkCreate` return instead of padding afterInsert with `undefined`. + - **#3150** wraps the two remaining un-retried write points (seed + `writeRecord`/`resolveDeferredUpdates`, import's no-`createManyData` + fallback) in `withTransientRetry`; `defaultIsTransientError` short-circuits + definitive logical errors to non-transient. + - **#3148** import `resolveRef` flushes pending creates on a same-object miss so + a later row can reference an earlier same-file CREATE, and no longer + negatively caches a miss. + - **#3149** threads an `attempt` counter through `bulkWrite`; seed rechecks by + `externalId` and import by `matchFields` before re-writing, so a + commit-then-lost-response retry cannot duplicate a batch. + - **#3147** `recomputeSummaries` retries transient failures and, on exhaustion, + surfaces `SummaryRecomputeError` (`ERR_SUMMARY_RECOMPUTE`) instead of a + silent warn; seed/import recover it to a warning without re-writing. + - **#3152** autonumbers are assigned after validation, so a batch that dies in + validation consumes no sequence value (no number-range gaps). + +- 43a3efb: fix(rest): gate the cross-object transactional batch by the same per-object API rules as single-record writes (#1604) + + The `POST {basePath}/batch` route (issue #1604 / ADR-0034) wraps N cross-object + create/update/delete ops in one engine transaction, but it skipped the + per-object API-exposure gate every single-record route applies — an + authenticated caller could write to an `apiEnabled: false` object, or run an + operation outside an object's `apiMethods` whitelist, straight through the batch + surface (ADR-0049 / #1889 — the same "declared ≠ enforced" hole closed for the + generic write path in #3220 / #3213). + + The route now: + + - validates the body against a new `CrossObjectBatchRequestSchema` + (`@objectstack/spec/api`, Zod-First) — a malformed op, an unknown action, or a + missing `object` is a `400` instead of a `500`; + - enforces `enable.apiEnabled` / `enable.apiMethods` for **every** op (metadata + fetched once, each distinct `(object, action)` checked) BEFORE opening the + transaction — `404 OBJECT_API_DISABLED` / `405 OBJECT_API_METHOD_NOT_ALLOWED`; + - requires an `id` for `update` / `delete` (`400`); + - rejects an unresolvable `{ $ref }` with `400 BATCH_UNRESOLVED_REF` instead of + silently writing a `null` FK; + - rejects an explicit `atomic: false` (`400 BATCH_NOT_ATOMIC`) rather than + silently applying atomically — non-atomic per-object batches stay on + `POST /data/:object/batch`. + + `enforceApiAccess` is refactored to share the pure `apiAccessDenialFromEnable` + check + a `loadObjectItems` helper with the batch route (single-record behavior + unchanged). Adds `rest-batch-endpoint.test.ts` — the REST-boundary coverage + ADR-0034 flagged as missing (commit, `$ref`, rollback surfacing, API-access + denial, request validation). + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [e057f42] +- Updated dependencies [a3823b2] +- Updated dependencies [bc65105] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [dd9f223] +- Updated dependencies [46e876c] +- Updated dependencies [5f05de2] +- Updated dependencies [021ba4c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [83e8f7d] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [290e2f0] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [92f5f19] +- Updated dependencies [32899e6] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + - @objectstack/platform-objects@16.0.0 + - @objectstack/core@16.0.0 + - @objectstack/types@16.0.0 + - @objectstack/service-package@16.0.0 + ## 16.0.0-rc.1 ### Minor Changes diff --git a/packages/rest/package.json b/packages/rest/package.json index f363dd3123..ac6aeeb3f3 100644 --- a/packages/rest/package.json +++ b/packages/rest/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/rest", - "version": "16.0.0-rc.1", + "version": "16.0.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 fe07ae48f7..966464cd12 100644 --- a/packages/runtime/CHANGELOG.md +++ b/packages/runtime/CHANGELOG.md @@ -1,5 +1,380 @@ # @objectstack/runtime +## 16.0.0 + +### Major Changes + +- 6c270a6: **BREAKING: remove the deprecated `ctx.session.tenantId` / `ctx.user.tenantId` alias from the hook & action authoring surface — converge on `organizationId` (#3290).** + + #3280 made `organizationId` the blessed developer-facing name for the caller's active org across the JS authoring surface and kept `tenantId` as a `@deprecated` alias carrying the identical value. That alias is now **removed** from the hook `ctx.session`, the action-body `ctx.session`, and the action-body `ctx.user`. Read the caller's active org under the single blessed name: + + ```diff + - const org = ctx.session.tenantId; // hook or action body + + const org = ctx.user?.organizationId ?? ctx.session?.organizationId; + ``` + + **FROM → TO migration** (in any `*.hook.ts` / `*.action.ts` body): + + - `ctx.session.tenantId` → `ctx.session.organizationId` + - `ctx.user.tenantId` (action body) → `ctx.user.organizationId` + + The value is unchanged — `organizationId` is the same active-org id, matching the `organization_id` column and `current_user.organizationId` in RLS/sharing. `ctx.user` is `undefined` for system / unauthenticated writes, so read `ctx.session?.organizationId` when a hook or action must work regardless of a resolved user. + + What changed internally: + + - **`@objectstack/spec`** — `HookContextSchema.session` drops the `tenantId` field (only `organizationId` remains). A stray `tenantId` on a constructed session is now stripped by the schema. + - **`@objectstack/objectql`** — the engine's `buildSession()` no longer emits `session.tenantId`; the audit-stamp plugin sources the `tenant_id` column from `session.organizationId`. + - **`@objectstack/runtime`** — `buildActionSession()` and the REST action `ctx.user` no longer emit `tenantId`. + - **`@objectstack/trigger-record-change`** — reads `session.organizationId` (was `session.tenantId`) when forwarding the writer's org to a `runAs:'user'` flow; behavior is identical. + + **Explicit non-goal (unchanged):** the generic **driver-layer** tenancy abstraction is _not_ touched — `ExecutionContext.tenantId`, `DriverOptions.tenantId`, `SqlDriver.applyTenantScope` / `TenancyConfig.tenantField`, and `ExecutionLog.tenantId`. That isolation column is configurable and legitimately carries an _environment_ id in database-per-tenant kernels; it is a distinct axis from the developer-facing org. The build-time `check:org-identifier` guard now also covers `packages/**` to keep reference bodies off the removed name. + +### Minor Changes + +- 2049b6a: feat(observability): admin-gated per-request `Server-Timing` via `X-OS-Debug-Timing` (#2408) + + Perf-tuning mode was previously global-only (`serverTiming` option / + `OS_SERVER_TIMING`), which discloses internal phase durations — a mild + backend-fingerprinting surface — to every caller. This adds the per-request + gating path from the design so an operator can pull a single request's + `Server-Timing` breakdown on a live environment without turning the header on + for everyone. + + - **observability**: a request-scoped disclosure gate (`runWithPerfDisclosure`, + `allowPerfDisclosure`, `isPerfDisclosureAllowed`, `PerfDisclosureGate`) kept + separate from the pure `PerfTiming` collector and pinned to its own + `Symbol.for` store so the middleware and dispatcher share it across module + copies. + - **plugin-hono-server**: the Server-Timing middleware is registered by default + (unless `serverTiming: false`). It runs the collector when timing is global + **or** the request sends `X-OS-Debug-Timing: 1`, and emits the header only + when the gate is open. `OS_PERF_TIMING=1` now also enables global mode. + - **runtime**: after resolving the execution context, the dispatcher opens the + gate for admin/service/system principals, so ordinary callers never receive + the header even if they send the debug header. + + Existing global-mode behavior is unchanged. + +- 92f5f19: feat(runtime): sandbox budget is script CPU-time, not wall clock (ADR-0102 D1, #3295) + + The QuickJS sandbox now meters each hook/action invocation against how much + **VM-active (CPU) time** the body burns, not wall clock. Idle host-await time and + a nested hook's own execution (which runs host-side while the caller's VM is + parked) are no longer charged to the caller — so a slow/loaded host or a deep + nested-write chain can't trip the budget while a script is merely waiting (the + root cause of the #3259 CI flake). A separate, generous **wall-clock ceiling** + (default 30s, `max(ceiling, cpuBudget)`) remains as the backstop for a body stuck + on a host call that never settles. + + What changes for consumers (behaviour, not API signatures): + + - **Meaning of the timeout knobs.** `body.timeoutMs`, the `hookTimeoutMs` / + `actionTimeoutMs` runner options, and `OS_SANDBOX_HOOK_TIMEOUT_MS` / + `OS_SANDBOX_ACTION_TIMEOUT_MS` keep their **names, defaults (250ms / 5000ms), + and precedence** — but now bound CPU-time instead of wall-clock. In practice + this only _loosens_ legitimate slow/nested work; a runaway synchronous script + is still cut at the same budget. + - **Error messages.** `exceeded timeout of Nms` → either `exceeded CPU budget of +Nms` (script burned its CPU budget) or `exceeded wall-clock ceiling of Nms +while awaiting host calls` (stuck on a never-settling host call). Update any + code/tests matching the old string. + + New knobs (additive): + + - `QuickJSScriptRunner` option `wallCeilingMs` and env `OS_SANDBOX_WALL_CEILING_MS` + — tune the wall ceiling (explicit option › env › 30s). + - `resolveSandboxTimeoutMs` (`@objectstack/types`) gains a `'wallCeiling'` kind. + + Also fixes a latent init bug in the new accounting where the interrupt handler + could fire during `installCtx` and corrupt ctx marshalling. The nested-write + integration suites now run at the stock 250ms budget (previously forced to 10s), + which is itself the regression guard for the nested-charging fix. + +- 32899e6: feat(runtime): env-overridable sandbox hook/action timeout default (#3259) + + The QuickJS sandbox enforces a wall-clock deadline on every hook/action + invocation (250ms hooks / 5000ms actions). Each invocation compiles a fresh + WASM module, and a nested hook compiles ANOTHER one inside the parent's budget, + so on a heavily loaded or slow host — an oversubscribed CI runner, constrained + production hardware — that fixed VM-creation cost alone can trip the hook + default even while the VM is still making progress. On CI this surfaced as an + intermittent `hook '…' exceeded timeout of 250ms` flake on PRs that never + touched the sandbox path. + + The per-invocation timeout DEFAULT is now resolvable from the environment via + `resolveSandboxTimeoutMs` (`@objectstack/types`), which `QuickJSScriptRunner` + consults, so an operator can raise the floor once, deployment-wide, instead of + re-tuning every call site: + + - `OS_SANDBOX_HOOK_TIMEOUT_MS` — default hook budget (ms) + - `OS_SANDBOX_ACTION_TIMEOUT_MS` — default action budget (ms) + + Precedence is unchanged: an explicit `hookTimeoutMs` / `actionTimeoutMs` passed + to the runner still wins over the env var, and a body's own declared `timeoutMs` + still wins over the resolved default (the smaller of the explicit values). Only + a positive integer is honored; unset / empty / non-numeric / non-positive keeps + the built-in 250ms / 5000ms defaults, so behaviour is byte-for-byte unchanged + when the vars are absent — production is unaffected unless it opts in. + + CI's Test Core now sets `OS_SANDBOX_HOOK_TIMEOUT_MS=10000` so the shared-runner + load flake can't recur; genuine hangs stay bounded by each test's own timeout. + +### Patch Changes + +- b39c65d: **Extend the blessed `organizationId` org name to the action-body surface (follow-up to #3280).** Hooks now teach `ctx.user.organizationId` / `ctx.session.organizationId` as the blessed name for the caller's active org; action bodies — the sibling authoring surface that shares the same sandbox runner — were left behind: the REST dispatch path exposed only `ctx.user.tenantId` (the deprecated name) and no `ctx.session` at all, and the MCP `run_action` path exposed neither. + + Both action-dispatch sites (`handleActions`, MCP `runAction`) now populate: + + - **`ctx.user.organizationId`** — the blessed name (matches the `organization_id` column and `current_user.organizationId` in RLS); `ctx.user.tenantId` is kept as a deprecated alias with the identical value on the REST path. + - **`ctx.session`** (`{ userId, organizationId, tenantId, roles? }`) — mirrors the hook `ctx.session` shape, `undefined` for a context-less / self-invoked call. + + Action bodies execute trusted (the `ctx.engine` / `ctx.api` facade bypasses RLS/FLS), so a body that must scope by org has to read it from `ctx` — now under the same name a hook author uses. Additive and behavior-preserving; the objectstack-ui skill documents the action-body `ctx` and the `organizationId` read. + +- fdc244e: Dev-loop DX fixes from the 15.1 third-party evaluation (P2 batch): + + - **Hot-added objects are now queryable without a restart.** Adding a `*.object.ts` under `os dev` used to recompile "green" while every query answered `no such table` (or `not registered`) until a manual restart: the artifact reload never notified the ObjectQL registry, tables were only created at boot, and seeds only loaded from the boot-time bundle. The `metadata:reloaded` payload now carries the parsed artifact; ObjectQL ingests the object definitions and re-runs the idempotent schema sync (same `skipSchemaSync` opt-out as boot), and the runtime loads seeds for first-seen objects (dev, single-tenant). `os dev` also prints `✚ new object(s): …` on recompile. + - **Dev admin credentials stay visible.** The `os dev` startup banner only showed `admin@objectos.ai / admin123` on the boot that actually seeded it; with the persistent default DB every later boot hid it, and the Console login page never knew it existed. The hint now re-arms on every dev boot for as long as the account still verifies against the default password, and `GET /api/v1/auth/config` exposes a dev-gated `devSeedAdmin` field (never present outside `NODE_ENV=development`) so the login page can show it. + - **`os doctor` reference analysis understands current metadata shapes.** Objects bound through `defineView` containers (`list`/`listViews`/`form`/`formViews` → `data.object`, subform `childObject`, lookup form fields) and app navigation (`objectName`, nested `children`, `areas`) were reported as "defined but not referenced". The collector now walks the canonical shapes (plus flow node `config.object`/`objectName`) and the orphan-view check descends into containers. + +- 2ea08ee: Flow trigger observability — kill the four-layer silence around record-change flows that never fire (2026-07-17 third-party eval). + + A misauthored auto-launched flow (wrong `objectName`, missing `requires: ['automation','triggers']`, failing start condition) produced ZERO output at every layer: the engine's own registration/binding logs land inside the CLI's boot-quiet stdout window (which swallows debug/info/warn — only error/fatal reach stderr), and each "didn't happen" path was itself silent. Fixes: + + - **Startup banner `Flows:` section** (`os serve`/`os dev`/`os start`): flow count, bound-to-trigger count, registered trigger types, draft count — plus loud `⚠` lines for flows declared with no automation engine enabled (`requires` missing), flows whose trigger type has no registered trigger, and bound record-change flows targeting an unknown object (dead binding). Printed after stdout is restored, so it is immune to the boot-quiet window. + - **Trigger-fired run failures now log at ERROR** (stderr — always visible): the automation engine no longer drops the AutomationResult of a trigger-fired execution; condition-evaluation faults and node failures surface with the flow name. Condition-not-met skips stay at debug (high-frequency, intentional). + - **`RecordChangeTrigger` probes object existence at bind time** and warns when a flow's `objectName` matches no registered object (exact-name matching), instead of silently arming a hook that can never fire. + - **`kernel:bootstrapped` binding audit** in the automation plugin: warns per enabled-but-unbound triggered flow with the reason, and reports registered/bound/draft counts (`AutomationEngine.getTriggerBindingAudit()`, extended `getFlowRuntimeStates()` with `status`/`triggerType`/`object`). + - **`os validate` flow-wiring advisories** (`@objectstack/lint` `validateFlowTriggerReadiness`): warns when a record-triggered flow targets an object the stack does not define, and when an auto-triggered flow's status is `draft` (authored or defaulted — draft flows still fire; declare `active` or `obsolete`). + - Removed leftover boot-debug writes (`registerApp`/`AppPlugin`/`StandaloneStack`/`AuditPlugin` stderr noise) that previous debugging of this same silence had left behind. + +- d1d1c40: fix(runtime): honor a hook body's declared `timeoutMs` so nested cross-object writes aren't clamped to 250ms (#1867) + + Hook bodies run in the QuickJS sandbox with a default 250ms timeout. The runner + folded that engine default straight into `Math.min(...)` when resolving the + effective timeout, so it _always_ dominated for hooks: a body that declared a + larger `timeoutMs` (the spec permits up to 30_000ms — `ScriptBody.timeoutMs`) to + give a legitimate nested write — "when a child changes, update the parent" — + room to settle was silently clamped back to 250ms and killed mid-flight. The + declared knob was never enforced. + + The engine default is now a FALLBACK used only when no explicit timeout is + supplied, not a hard ceiling. An explicit `body.timeoutMs` (and/or an enclosing + hook/action timeout) is honored; when both are present the smaller wins. Bodies + that declare nothing still get the 250ms hook / 5000ms action default, and a + body may still LOWER its own timeout below the default. + + This clears the last reliability blocker for nested cross-object writes from + hooks — the sandbox crash itself (`memory access out of bounds`) was already + fixed by the deferred-promise host-call model — so header/rollup fields no + longer need denormalized, hand-maintained workarounds. + +- ee0a499: feat(i18n): localize collaboration notification titles and the storage objects; wire the notifications REST routes + + Three gaps behind one report (a `sys_file "repro.png" assigned to you` + notification that was English on an all-Chinese workspace, opened an English + detail page, and never cleared its unread state): + + - **plugin-audit** — the assignment (`collab.assignment`) and @mention + (`collab.mention`) bell titles were hardcoded English literals built from the + raw object API name. They now resolve through the i18n service with the same + key shapes as the activity summaries (framework#3039): new + `messages.assignedToYou` / `messages.mentionedYou` / + `messages.mentionedYouAnonymous` templates (en / zh-CN / ja-JP / es-ES), the + object named by its translated label (`objects.{name}.label` → authored def + label → API name), and the locale resolved for the **recipient** (they read + the bell), not the acting user. Every step stays best-effort: no locale / no + i18n / key miss degrades to the English literal — which now also prefers the + authored object label over the API name. + + - **service-storage** — `sys_file` / `sys_upload_session` had no translation + bundle at all, so the file detail page (labels, and the Pending Upload / + Committed / Deleted status pipeline) rendered English on every locale. The + service now ships its own ADR-0029 D8 bundle (en / zh-CN / ja-JP / es-ES, + `src/translations` + `scripts/i18n-extract.config.ts`) and contributes it via + `i18n.loadTranslations` on `kernel:ready`, matching service-messaging. + (`sys_attachment` stays in platform-objects' bundles pending the + storage-domain decomposition.) + + - **runtime** — the in-app notifications REST surface (`GET +/api/v1/notifications`, `POST /api/v1/notifications/read`, `POST +/api/v1/notifications/read/all`; ADR-0030) had its `handleNotification` + dispatch branch and discovery entry, but no `server.()` mount in + `dispatcher-plugin`, so only the cloud hosts' hono catch-all reached it — the + standalone / `os dev` server 404'd every request. That left mark-read with no + working endpoint (the console's direct `sys_notification_receipt` write is + rejected by ADR-0103's engine-owned gate), so unread notifications could never + clear. The three routes are now mounted explicitly, guarded by the + route-registration regression test. + +- a2d6555: perf(runtime): drop asyncify — sandbox runs on the sync QuickJS variant (ADR-0102 D2, #3296) + + Phase 2 of #3275. The QuickJS sandbox switches from the asyncify build + (`newAsyncContext`) to the already-installed sync release variant + (`newQuickJSWASMModule().newContext()`), keeping one physically isolated WASM + module per invocation (ADR-0102 D2/D4). Asyncify's only justification — suspending + the WASM stack across a host call — disappeared with the #1867 deferred-promise + + pump redesign, so nothing depended on it. Wins: smaller binary, faster + compile/instantiate, faster per-instruction, and removal of an entire class of + suspended-stack failure modes. + + Also fixes a latent resource leak surfaced by the stricter sync teardown: host + `ctx.api` calls hand the VM a `vm.newPromise()` deferred that was never + `dispose()`d (the newPromise contract requires it). The asyncify build tolerated + the leak; the sync build's `JS_FreeRuntime` aborted (`Assertion failed: +list_empty`) when a context was torn down with a pending, never-settled host call + (the timeout path). Deferreds are now tracked and disposed before context + teardown. + + Memory: the sync `QuickJSWASMModule` has no `dispose()`; its WebAssembly instance + + - linear memory are GC-reclaimed when the reference is dropped. A new RSS soak + test guards that per-invocation modules don't ratchet RSS. + +- 3a6310c: perf(runtime): stop the sandbox pump loop from idle-spinning while awaiting a host call (#3233) + + The QuickJS hook/action runner drives a script's async continuations with a + pump loop that, on every iteration, yielded via `setImmediate` and then drained + the VM job queue. While the body was only _waiting_ on an in-flight host promise + (a slow `ctx.api` read/write, or one call that settles after many event-loop + turns), that queue was empty every iteration, so the loop woke ~200k times/sec + doing nothing — a ~50,000-iteration burn for a 250ms wait. + + The yield is now adaptive: it stays on `setImmediate` (near-zero latency) while + the script is making progress, and once a pump executes zero VM jobs it ramps up + to a small capped `setTimeout` (≤8ms). Any executed job — a settled host call, a + resumed continuation — resets it to the fast path, so sequential host calls and + multi-turn work keep their low latency; only a genuinely idle wait backs off. + Deadline enforcement and every existing pump-budget/timeout/transaction + guarantee are unchanged. + +- 515f11a: fix(seed): replaying seeds no longer corrupts lookup natural keys on the upsert update path + + Every dev-server restart replayed package seeds in upsert mode, and any record whose + lookup/master_detail was authored as a natural key could have that reference overwritten + with NULL on the update path (`NOT NULL constraint failed` on required columns; silent + link loss on nullable ones). Four fixes: + + - An unresolved reference now leaves the column untouched (deferred to pass 2) or drops + the record loudly — it is never written as NULL over an existing row. + - DB-side reference resolution probes the target dataset's declared `externalId` (e.g. + `email`) before falling back to `name` and `id`, matching how in-memory resolution + already keyed records. + - A rejected update (e.g. a `state_machine` rule vetoing the replay) no longer severs + natural-key resolution for downstream child datasets. + - Replays are idempotent: an upsert/update whose declared fields already match the + existing row is skipped instead of rewritten (no more `updated_at` churn or lifecycle + re-validation on every boot). + +- 4174a07: feat(runtime): seed-replayer reports `skipped` so hosts can stamp seed-once on progress + + The `seed-replayer` kernel service returned `{ inserted, updated, errors }` but + not `skipped`. A cloud host therefore could not tell an **all-skip replay** + (the env's seed data is already present — a no-op) apart from the + zero-summary early-returns that never ran the loader (no organization, no + metadata service, no datasets). Both looked like `inserted = updated = 0`, so + the host could not safely stamp its seed-once record for the all-skip case and + re-ran the full remote replay on every cold boot. + + Add `skipped: result.summary.totalSkipped` to the replayer's return; the + early-returns report `skipped: 0`. This lets a host (cloud#853's + `decideSeedStamp`) stamp on progress — including an all-skip replay — while + still declining to stamp a genuine no-loader zero-summary. Additive and + backward compatible; existing consumers ignore the new field. + +- ce468c8: feat(observability): decompose `Server-Timing` into auth / db / hooks / serialize spans (perf-tuning mode) + + The opt-in `Server-Timing` header now breaks a request's server time into the phases that actually explain it, so an operator can open DevTools → Network → Timing and see where the time went without standing up an external tracing backend: + + - **`db`** — total SQL time with a **query count**. The SQL driver wires knex's `query` / `query-response` events (keyed by `__knexQueryUid`) and folds each query into one aggregate member (`db;dur=210;desc="6 queries"`) — the query count is the number most useful for spotting N sequential round-trips. Timing is attributed to the originating request via `AsyncLocalStorage`, so it is correct under concurrency and never cross-attributes. SQL text is never emitted, only durations and a count. + - **`auth`** — identity / session resolution in the dispatcher, the prime suspect for unexplained data-API overhead. + - **`hooks`** — total business-hook execution time with a hook count, fed through the engine's existing `HookMetricsRecorder` seam (wired from the runtime, so `@objectstack/objectql`'s lean `core` tier stays observability-free). + - **`serialize`** — response JSON encoding in the HTTP adapter. + + Adds `countServerTiming(name, dur, unit)` (and `PerfTiming.count`) to fold high-frequency phases into a single aggregate member instead of flooding the header. Every phase is a no-op when perf-tuning is off (`serverTiming: true` / `OS_SERVER_TIMING=true`), so there is zero measurable overhead on the normal path. + + Closes #2408. + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [2f3c641] +- Updated dependencies [e38da5b] +- Updated dependencies [f9b118d] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [deb7e7e] +- Updated dependencies [a8aa34c] +- Updated dependencies [e057f42] +- Updated dependencies [a3823b2] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [6b51346] +- Updated dependencies [80273c8] +- Updated dependencies [fdc244e] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [dd9f223] +- Updated dependencies [47d923c] +- Updated dependencies [46e876c] +- Updated dependencies [2ea08ee] +- Updated dependencies [7125007] +- Updated dependencies [616e839] +- Updated dependencies [5f05de2] +- Updated dependencies [021ba4c] +- Updated dependencies [158aa14] +- Updated dependencies [9d897b3] +- Updated dependencies [62a2117] +- Updated dependencies [83e8f7d] +- Updated dependencies [d2723e2] +- Updated dependencies [674457a] +- Updated dependencies [fefcd54] +- Updated dependencies [efbcfe1] +- Updated dependencies [2049b6a] +- Updated dependencies [beaf2de] +- Updated dependencies [06cb319] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [290e2f0] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [92f5f19] +- Updated dependencies [32899e6] +- Updated dependencies [ce468c8] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [ea32ec7] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + - @objectstack/plugin-security@16.0.0 + - @objectstack/objectql@16.0.0 + - @objectstack/rest@16.0.0 + - @objectstack/plugin-auth@16.0.0 + - @objectstack/core@16.0.0 + - @objectstack/formula@16.0.0 + - @objectstack/metadata@16.0.0 + - @objectstack/driver-sql@16.0.0 + - @objectstack/metadata-core@16.0.0 + - @objectstack/types@16.0.0 + - @objectstack/observability@16.0.0 + - @objectstack/driver-memory@16.0.0 + - @objectstack/driver-sqlite-wasm@16.0.0 + - @objectstack/service-cluster@16.0.0 + - @objectstack/service-datasource@16.0.0 + - @objectstack/service-i18n@16.0.0 + ## 16.0.0-rc.1 ### Patch Changes diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 4db13a5d86..6d5196099d 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/runtime", - "version": "16.0.0-rc.1", + "version": "16.0.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 df11e5cb5b..8053d267c8 100644 --- a/packages/sdui-parser/CHANGELOG.md +++ b/packages/sdui-parser/CHANGELOG.md @@ -1,5 +1,7 @@ # @objectstack/sdui-parser +## 16.0.0 + ## 16.0.0-rc.1 ## 16.0.0-rc.0 diff --git a/packages/sdui-parser/package.json b/packages/sdui-parser/package.json index e77ebd602a..2f3f5be4ed 100644 --- a/packages/sdui-parser/package.json +++ b/packages/sdui-parser/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/sdui-parser", - "version": "16.0.0-rc.1", + "version": "16.0.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 e07fbefb80..a666eef3c0 100644 --- a/packages/services/service-analytics/CHANGELOG.md +++ b/packages/services/service-analytics/CHANGELOG.md @@ -1,5 +1,103 @@ # Changelog — @objectstack/service-analytics +## 16.0.0 + +### Minor Changes + +- a9459e6: Analytics drill metadata now snapshots raw grouped values for totals/subtotal rows too (#3214). The ADR-0021 D2 drill sidecar (`drillRawRows`, #2080) only covered `result.rows`, but the totals rows added in #1753 carry dimension values and go through the same label resolution — which overwrote their stored value (select option value, lookup/master_detail FK id) with the display label, leaving a subtotal drill nothing to exact-match on. + + `queryDataset` now also emits `drillRawTotals`, aligned to `result.totals` by index (`drillRawTotals[i][j]` ↔ `result.totals[i].rows[j]`), captured in the same pre-label-resolution pass. Each map is restricted to the drillable dimensions the grouping actually groups by, so the grand-total grouping (`[]`) contributes an empty map per row. Purely additive result props (same as #2080) — no spec-contract change. + +- dd9f223: feat(analytics): scope a datetime date-bucket drill to the reference-tz midnight instants (#1752 follow-up) + + Closes the one gap left by the initial #1752 change: a `datetime` date dimension + bucketed under a **non-UTC reference timezone** previously fell back to a superset + drill (its bucket boundary is that tz's midnight _instant_, which `YYYY-MM-DD` + calendar bounds can't express). + + - **`@objectstack/core`** adds `zonedDateStartToUtcMs(ymd, tz)` — the UTC instant + at which a calendar day begins in a reference timezone (the inverse of + `calendarPartsInTz`). DST-safe: the offset is read from the platform tz + database via `Intl`, with a two-pass resolution for the rare offset-boundary + case; an unset/`'UTC'`/invalid zone returns plain UTC midnight. + - **`@objectstack/service-analytics`** now emits `drillRanges` bounds per the + field's temporal type (ADR-0053): a `datetime` field → ISO **instant** bounds + at the reference tz's midnight (works under any tz, incl. DST); a `date` field + → `YYYY-MM-DD` calendar bounds (tz-naive, exact under any tz). An unknown field + type is still emitted only under UTC and omitted (superset) under a non-UTC tz. + + No objectui change is needed — the client already forwards whatever bound values + the server sends into the drill filter and the `filter[field][gte|lt]` URL. + +- 290e2f0: feat(analytics): emit a half-open date-range drill scope for granularity-bucketed date dimensions (#1752) + + A report/dashboard cell grouped by a `dateGranularity` date dimension ("2026-Q2") + covers a SPAN of records, so drilling it needs a range (`>= start AND < nextStart`), + which the equality drill contract (`drillRawRows`) can't express — date dims were + therefore excluded from drill metadata and a drill landed on an unscoped superset. + + - **`@objectstack/core`** adds `bucketKeyToCalendarRange(key, granularity)`, the + inverse of `bucketDateValue`: it turns a canonical bucket key into its half-open + `[start, end)` calendar span (`YYYY-MM-DD`, `end` exclusive). Pure, timezone-naive + calendar arithmetic; returns `null` for unbucketable / out-of-range keys so the + caller falls back to an unscoped (superset) drill rather than emit a wrong bound. + - **`@objectstack/service-analytics`** emits a `drillRanges` sidecar (aligned to + `rows` by index — the range companion to `drillRawRows`) for `date` + + `dateGranularity` dimensions, computed from the canonical bucket key in the + pre-label-resolution snapshot pass. A `datetime` field under a non-UTC reference + timezone is omitted (host drills a superset) until instant-boundary support + lands; a tz-naive `date` field is exact under any timezone (ADR-0053). + + Consumed by objectui's report drill-through to scope the drilled record list to the + clicked time bucket. + +### Patch Changes + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [e057f42] +- Updated dependencies [a3823b2] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [dd9f223] +- Updated dependencies [46e876c] +- Updated dependencies [5f05de2] +- Updated dependencies [021ba4c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [290e2f0] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + - @objectstack/core@16.0.0 + ## 16.0.0-rc.1 ### Patch Changes diff --git a/packages/services/service-analytics/package.json b/packages/services/service-analytics/package.json index 04c2c697fb..32ebee0399 100644 --- a/packages/services/service-analytics/package.json +++ b/packages/services/service-analytics/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-analytics", - "version": "16.0.0-rc.1", + "version": "16.0.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 c7f2b6ed04..ca7b708473 100644 --- a/packages/services/service-automation/CHANGELOG.md +++ b/packages/services/service-automation/CHANGELOG.md @@ -1,5 +1,251 @@ # @objectstack/service-automation +## 16.0.0 + +### Minor Changes + +- 780b4b5: feat(automation): schema-aware flow-condition validation at registration (#1928) + + `registerFlow` now runs the same schema-aware condition checks as + `objectstack build` — so a flow registered dynamically (via the API / Studio, + bypassing the build lint) still gets the guardrail. When the host wires an + object-schema resolver, a flow condition that references an unknown field, + likely-typos a field name, or does arithmetic/ordering on a text/boolean field + against a number is surfaced as an **advisory warning** (logged), pointing at + the object's real schema. + + - New `AutomationEngine.setObjectSchemaResolver(resolver)` bridge (mirrors + `setFunctionResolver`); `AutomationServicePlugin` wires it to + `objectql.registry.getObject` in `start()`, before the flow pull, so + registry-sourced flows are covered too. + - **Strictly additive / zero regression**: the fatal set is unchanged (syntax, + brace-in-CEL, unknown-function still throw); everything the schema pass adds is + logged, never thrown, and the whole thing is a no-op when no resolver is wired. + Flow conditions bind fields flat, so the check runs in `flattened` scope + (flow variables stay `dyn` and are never flagged; equality is runtime-safe). + + Builds on the tier-4 type-soundness check in `@objectstack/formula` / + `@objectstack/lint` (#1928). + +- 2ea08ee: Flow trigger observability — kill the four-layer silence around record-change flows that never fire (2026-07-17 third-party eval). + + A misauthored auto-launched flow (wrong `objectName`, missing `requires: ['automation','triggers']`, failing start condition) produced ZERO output at every layer: the engine's own registration/binding logs land inside the CLI's boot-quiet stdout window (which swallows debug/info/warn — only error/fatal reach stderr), and each "didn't happen" path was itself silent. Fixes: + + - **Startup banner `Flows:` section** (`os serve`/`os dev`/`os start`): flow count, bound-to-trigger count, registered trigger types, draft count — plus loud `⚠` lines for flows declared with no automation engine enabled (`requires` missing), flows whose trigger type has no registered trigger, and bound record-change flows targeting an unknown object (dead binding). Printed after stdout is restored, so it is immune to the boot-quiet window. + - **Trigger-fired run failures now log at ERROR** (stderr — always visible): the automation engine no longer drops the AutomationResult of a trigger-fired execution; condition-evaluation faults and node failures surface with the flow name. Condition-not-met skips stay at debug (high-frequency, intentional). + - **`RecordChangeTrigger` probes object existence at bind time** and warns when a flow's `objectName` matches no registered object (exact-name matching), instead of silently arming a hook that can never fire. + - **`kernel:bootstrapped` binding audit** in the automation plugin: warns per enabled-but-unbound triggered flow with the reason, and reports registered/bound/draft counts (`AutomationEngine.getTriggerBindingAudit()`, extended `getFlowRuntimeStates()` with `status`/`triggerType`/`object`). + - **`os validate` flow-wiring advisories** (`@objectstack/lint` `validateFlowTriggerReadiness`): warns when a record-triggered flow targets an object the stack does not define, and when an auto-triggered flow's status is `draft` (authored or defaulted — draft flows still fire; declare `active` or `obsolete`). + - Removed leftover boot-debug writes (`registerApp`/`AppPlugin`/`StandaloneStack`/`AuditPlugin` stderr noise) that previous debugging of this same silence had left behind. + +- 1e145eb: fix(automation): region-aware run-history compaction keeps loop containers + early failures (#3234) + + `compactStepsForHistory` bounded a terminal run's persisted step log to the last + `MAX_PERSISTED_HISTORY_STEPS` entries with a plain tail-slice. With the ADR-0031 + structured-region step logs (#1505) a single `loop` can emit + `iterations × body-steps` entries, so the tail-slice dropped the + `loop`/`parallel`/`try_catch` **container** step (it precedes all its body steps) + and every early iteration — leaving `getRun`/`listRuns` (after a process restart + or ring-buffer eviction) with body steps the Runs surface could no longer nest, + and silently hiding an early failure. + + Compaction is now region-aware (new exported `compactStepLogForHistory`): over + budget it keeps the run's structural backbone — every top-level step (including + the region container steps) and every failure, each pulled in with its ancestor + container chain — plus the most recent body steps, order-preserving and + hard-capped at `max` so `steps_json` stays bounded (#2585). Every retained body + step keeps its enclosing container(s), so the compacted log never contains an + orphan and the observability surface's per-iteration / per-region nesting still + reconstructs. + +- a2795f6: feat(triggers): declarative time-relative trigger — daily sweep instead of fragile date-equality (#1874) + + Time-relative business rules ("alert 60 days before a contract's `end_date`") + could only be expressed as a `record_change` flow gated on a date-equality + condition like `end_date == daysFromNow(60)`. That predicate is only evaluated + when the record _happens to change_, so it fires only if a record is edited on + exactly the threshold day — i.e. almost never, unattended. The robust + alternative was a hand-written cron + range query that every author + re-implemented (contracts `renewal_alert`, hr `document_expiring_soon`, + procurement `po_overdue`, …). + + A flow's start node can now declare a `timeRelative` descriptor instead: + + ```ts + config: { + timeRelative: { + object: 'contracts', + dateField: 'end_date', + offsetDays: [60, 30, 7], // T-minus reminders — fires on each threshold day + // — or — withinDays: 30 // "expiring soon" range; negative = overdue lookback + filter: { status: 'active' }, // optional, ANDed with the date window + }, + schedule: { type: 'cron', expression: '0 8 * * *' }, // optional; defaults to daily 08:00 UTC + } + ``` + + The new `time_relative` trigger (shipped in `@objectstack/trigger-schedule` as + `TimeRelativeTriggerPlugin`) sweeps the object on that schedule and launches the + flow **once per matching record**, with the record on the automation context — + so the start-node `condition` gate and `{record.}` interpolation work + exactly as for a record-change flow. Because the window is evaluated every day, + a threshold is never missed regardless of when the record last changed. The + discovery query runs as a system operation (RLS-bypassing) and is capped + (`maxRecords`, default 1000) so a mis-scoped window can't fan out unboundedly; + per-record failures are isolated so one bad row never aborts the sweep. + + The automation engine routes a start node carrying `config.timeRelative` to the + `time_relative` trigger (ahead of the plain `schedule` trigger, whose behavior is + unchanged), and `os validate` gains readiness checks for the new descriptor + (unknown swept object, ambiguous draft status). New authorable spec key: + `TimeRelativeTriggerSchema` (`@objectstack/spec/automation`). + +### Patch Changes + +- 22013aa: **Split the overloaded `managedBy: 'system'` bucket into engine-owned vs. admin-writable, and enforce engine-owned writes (ADR-0103, #3220).** The `system` bucket conflated two incompatible write policies: rows a platform service owns end to end (never user-written), and platform-defined schema whose rows are legitimately admin/user-writable. It carried the same all-false affordance row as `better-auth`/`append-only` but, unlike `better-auth`, had no engine enforcement — a wildcard admin could raw-write these rows through the generic data API (ADR-0049 gap). + + Rather than add a new `managedBy` enum value (which would fall through to fully-editable `platform` defaults on already-deployed Console clients), the write policy is now the **resolved affordance** (`resolveCrudAffordances` = bucket default + `userActions`), and _engine-owned_ is defined as a `system`/`append-only` object that grants no write: + + - **Writable set declares `userActions`** — the RBAC link tables (`sys_user_position`, `sys_user_permission_set`, `sys_position_permission_set`), `sys_user_preference`, `sys_approval_delegation`, and the messaging config grids (`sys_notification_preference` / `…_subscription` / `…_template`) now declare `userActions: { create, edit, delete: true }`. The affordance is a declaration only — the `DelegatedAdminGate` / RLS / permission sets remain the authz. + - **Engine-owned objects locked to reads** — `apiMethods: ['get','list']` added where absent (jobs, notifications, approval request/approver/token/action, `sys_record_share`, `sys_automation_run`, mail/settings/secret audit, the messaging delivery pipeline). `sys_secret` is explicitly read-locked (an empty `apiMethods` array fails open). + - **`sys_import_job`** stays engine-owned: the REST import route now writes its job rows `isSystem`-elevated (attribution preserved via the explicit `created_by` stamp) and the object is locked to `['get','list']`. + - **New engine write guard** (`assertEngineOwnedWriteAllowed`, plugin-security) fail-closed rejects user-context generic writes to engine-owned `system`/`append-only` objects, keyed off the resolved affordance; `isSystem` and context-less engine/service writes bypass by construction. Wired into the security middleware alongside the other data-layer gates. + - **`reconcileManagedApiMethods`** (objectql registry) now runs for **every** managed bucket, not just `better-auth`: any advertised write verb an object's resolved affordances forbid is stripped at registration with a warning (the drift backstop, ADR-0049). + - **`/me/permissions` clamp** (plugin-hono-server) now clamps `system`/`append-only` as well as `better-auth`, so the client hint reflects `permission ∩ guard`. + + **Potentially breaking:** a downstream/third-party `system` object that advertised generic write verbs relying on today's fail-open behaviour will have those verbs stripped (with a warning) and user-context generic writes to it rejected. Declare `userActions` opening the verbs the object legitimately takes from a user context. `better-auth` keeps plugin-auth's identity write guard unchanged; the row-level `managed_by` provenance vocabulary (ADR-0066) is a different axis and is untouched. + +- 02eafa5: test(automation): end-to-end coverage for the #1928 object-schema resolver wiring + + Adds a kernel-level integration test proving `AutomationServicePlugin` bridges + the engine's object-schema resolver to the live `objectql.registry.getObject` at + `start()` (fields + types resolved from the registry), and that a flow + registered through the running kernel with a text field misused in arithmetic + emits the tier-4 advisory — while a sound condition stays quiet. Locks in the + production integration point that the engine-level unit tests (which set the + resolver by hand) could not exercise. Test-only; no behavior change. + +- b320158: feat(automation): publish configSchemas for the keyValue-capable nodes (flow designer parity, #3304) + + The `assignment`, `create_record` / `update_record` / `delete_record` / + `get_record`, and `screen` nodes shipped no `configSchema`, so the flow designer + had no server-driven form for them. Each descriptor now carries one that mirrors + the objectui hardcoded field group field-for-field: object references as `xRef`, + the screen repeater's `visibleWhen` as `xExpression: 'expression'`, and the + free-form maps (`fields` / `filter` / `assignments` / `defaults`) as JSON-Schema + open objects (`additionalProperties: true`, no fixed `properties`) — the shape + the designer's schema adapter renders with its flat keyValue editor. Values stay + fully permissive because real metadata carries operator objects (`{"$ne": null}`), + `{var}` templates, and non-string literals. + + Deliberately still schemaless (no online/offline divergence exists for a node + with no configSchema, and a partial schema would drop editors): `decision` + (virtual Target column derived from edges), `wait` (top-level `waitEventConfig`), + `script` (actionType-conditional form), `subflow` (top-level `timeoutMs`). + + Additive and backward-compatible: descriptor metadata only, no runtime behavior + change. Requires an objectui with the keyValue schema mapping (objectui #2708) + for the maps to render as structured editors; older designers keep their + hardcoded forms. + +- 158aa14: feat(automation): mark the loop `collection` config field as an interpolate() template so designer forms render it correctly (#3304) + + The flow designer generates a node's config form from its published + `configSchema` (ADR-0018). A string property can now carry an `xExpression: +'expression' | 'template'` marker — riding the same Zod `.meta()` → JSON-Schema + channel as `xRef` / `xEnumDeprecated` — that declares whether the string is bare + CEL or an `interpolate()` single-brace `{var}` template. + + The `loop` node's `collection` (e.g. `{tasks}`) is a template, so it is now + marked `xExpression: 'template'` on both the canonical `LoopConfigSchema` and the + shipped descriptor's `configSchema` literal (service-automation loop-node). + Without the marker the designer rendered `collection` as plain text online while + the offline hardcoded form rendered it as a mono expression editor, and the CEL + brace-trap false-flagged `{tasks}` as a malformed condition. The marker closes + that divergence — objectui #2670 Phase 3 (#2699) already consumes it. + + Additive and backward-compatible: an unknown `xExpression` value is ignored by + the designer, and runtime behavior is unchanged. Filling the same marker in on + the remaining node types (map/decision/script and the node types that publish no + `configSchema` yet) is tracked as follow-up in #3304. + +- 62a2117: **Split the overloaded `managedBy: 'system'` bucket with an explicit `engine-owned` value (ADR-0103 addendum, #3343).** ADR-0103 deferred the enum split ("revisitable later as a rename") because a new `managedBy` value would fall through to the fully-editable `platform` default on deployed Console clients. Both reasons against it are now retired — the server-side write guard / `apiMethods` reconciliation / `/me/permissions` clamp make that fallthrough cosmetic (the write is rejected regardless of what the client renders), and objectui#2712 closed the UI union — so v16 lands it, **additively**. + + - **New enum value `engine-owned`** with the same all-locked default affordance row as `system` (`create/import/edit/delete: false`, `exportCsv: true`). It joins `ENGINE_OWNED_BUCKETS` (the engine write guard) and `GUARDED_WRITE_BUCKETS` (the `/me/permissions` clamp); the guard, `reconcileManagedApiMethods`, and the clamp mechanisms are unchanged — `engine-owned` is an explicit member of the set they already covered by resolved affordance. + - **20 objects relabelled `system → engine-owned`** — the ones the engine owns end to end and that declared no write-opening `userActions` (the metadata store, jobs, approval runtime rows, sharing rows, `sys_automation_run`, the messaging delivery/receipt pipeline, `sys_secret`, settings). One-line, behaviour-identical per object. + - **8 admin/user-writable objects keep `managedBy: 'system'`** (the RBAC link tables, `sys_user_preference`, `sys_approval_delegation`, the messaging config grids) — `system` now reads as "engine-managed schema, writable via `userActions`". + + Behaviour-, enforcement- and wire-identical: resolved affordances, the guard verdict, the 405 `apiMethods` reconciliation, and the permissions clamp are the same before and after — this is a self-documenting relabel, not a policy change. No data migration (`managedBy` is schema metadata) and no code branches on the `'system'` literal. Retiring the overloaded `system` entirely (moving the 8 writable objects to a dedicated bucket) is a breaking rename deferred to v17. + +- f8c1b69: feat(automation): publish a configSchema for the `map` node (flow designer parity, #3304) + + The `map` (sequential multi-instance) node shipped no `configSchema`, so the flow + designer fell back to its hardcoded field group online and to raw Advanced-JSON + where that wasn't present. Its descriptor now carries a structured `configSchema` + that mirrors the objectui hardcoded `map` field group field-for-field — + `collection` (marked `xExpression: 'template'`, an `interpolate()` `{items}` + template, same as `loop.collection`), `flowName` + `itemObject` as typed + references (`xRef`), and `iteratorVariable` / `outputVariable` as plain text — so + the online (schema-driven) and offline forms match. + + `map` is the one previously-schemaless flow node whose fields are all scalars and + typed references, so it maps cleanly through objectui's `jsonSchemaToFlowFields` + with zero regression. The remaining schemaless nodes lean on editor kinds the + schema→fields adapter does not yet reproduce (`keyValue` maps, the decision + virtual `target` column, `wait`'s top-level block), and are deferred to #3304 + until that adapter is extended. Additive and backward-compatible: no runtime + behavior change; an older designer that ignores the schema is unaffected. + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [e057f42] +- Updated dependencies [a3823b2] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [6b51346] +- Updated dependencies [80273c8] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [dd9f223] +- Updated dependencies [46e876c] +- Updated dependencies [7125007] +- Updated dependencies [5f05de2] +- Updated dependencies [021ba4c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [290e2f0] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [ea32ec7] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + - @objectstack/core@16.0.0 + - @objectstack/formula@16.0.0 + ## 16.0.0-rc.1 ### Patch Changes diff --git a/packages/services/service-automation/package.json b/packages/services/service-automation/package.json index 41ecf0a6ae..9c19468236 100644 --- a/packages/services/service-automation/package.json +++ b/packages/services/service-automation/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-automation", - "version": "16.0.0-rc.1", + "version": "16.0.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 70f7ae2823..3c089a2876 100644 --- a/packages/services/service-cache/CHANGELOG.md +++ b/packages/services/service-cache/CHANGELOG.md @@ -1,5 +1,58 @@ # @objectstack/service-cache +## 16.0.0 + +### Patch Changes + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [e057f42] +- Updated dependencies [a3823b2] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [dd9f223] +- Updated dependencies [46e876c] +- Updated dependencies [5f05de2] +- Updated dependencies [021ba4c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [efbcfe1] +- Updated dependencies [2049b6a] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [290e2f0] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [ce468c8] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + - @objectstack/core@16.0.0 + - @objectstack/observability@16.0.0 + ## 16.0.0-rc.1 ### Patch Changes diff --git a/packages/services/service-cache/package.json b/packages/services/service-cache/package.json index 89e7a96ac1..5304e7319c 100644 --- a/packages/services/service-cache/package.json +++ b/packages/services/service-cache/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-cache", - "version": "16.0.0-rc.1", + "version": "16.0.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 eed21db475..6329fa019f 100644 --- a/packages/services/service-cluster-redis/CHANGELOG.md +++ b/packages/services/service-cluster-redis/CHANGELOG.md @@ -1,5 +1,49 @@ # @objectstack/service-cluster-redis +## 16.0.0 + +### Patch Changes + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [a3823b2] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [46e876c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + - @objectstack/service-cluster@16.0.0 + ## 16.0.0-rc.1 ### Patch Changes diff --git a/packages/services/service-cluster-redis/package.json b/packages/services/service-cluster-redis/package.json index ecd962244a..73b4405d53 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": "16.0.0-rc.1", + "version": "16.0.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 35f4c346a4..d12e4e1cbe 100644 --- a/packages/services/service-cluster/CHANGELOG.md +++ b/packages/services/service-cluster/CHANGELOG.md @@ -1,5 +1,54 @@ # @objectstack/service-cluster +## 16.0.0 + +### Patch Changes + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [e057f42] +- Updated dependencies [a3823b2] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [dd9f223] +- Updated dependencies [46e876c] +- Updated dependencies [5f05de2] +- Updated dependencies [021ba4c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [290e2f0] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + - @objectstack/core@16.0.0 + ## 16.0.0-rc.1 ### Patch Changes diff --git a/packages/services/service-cluster/package.json b/packages/services/service-cluster/package.json index 09b32e8ae3..d5eea58fbf 100644 --- a/packages/services/service-cluster/package.json +++ b/packages/services/service-cluster/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-cluster", - "version": "16.0.0-rc.1", + "version": "16.0.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 8d4d254a08..364dc2ac2d 100644 --- a/packages/services/service-datasource/CHANGELOG.md +++ b/packages/services/service-datasource/CHANGELOG.md @@ -1,5 +1,54 @@ # @objectstack/service-external-datasource +## 16.0.0 + +### Patch Changes + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [e057f42] +- Updated dependencies [a3823b2] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [dd9f223] +- Updated dependencies [46e876c] +- Updated dependencies [5f05de2] +- Updated dependencies [021ba4c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [290e2f0] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + - @objectstack/core@16.0.0 + ## 16.0.0-rc.1 ### Patch Changes diff --git a/packages/services/service-datasource/package.json b/packages/services/service-datasource/package.json index 3624b4fe36..57d43fb398 100644 --- a/packages/services/service-datasource/package.json +++ b/packages/services/service-datasource/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-datasource", - "version": "16.0.0-rc.1", + "version": "16.0.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 34f21c4b07..c0176d5584 100644 --- a/packages/services/service-i18n/CHANGELOG.md +++ b/packages/services/service-i18n/CHANGELOG.md @@ -1,5 +1,54 @@ # @objectstack/service-i18n +## 16.0.0 + +### Patch Changes + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [e057f42] +- Updated dependencies [a3823b2] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [dd9f223] +- Updated dependencies [46e876c] +- Updated dependencies [5f05de2] +- Updated dependencies [021ba4c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [290e2f0] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + - @objectstack/core@16.0.0 + ## 16.0.0-rc.1 ### Patch Changes diff --git a/packages/services/service-i18n/package.json b/packages/services/service-i18n/package.json index fd4dd59293..b5b704c640 100644 --- a/packages/services/service-i18n/package.json +++ b/packages/services/service-i18n/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-i18n", - "version": "16.0.0-rc.1", + "version": "16.0.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 5375a8bff1..5c43240f4a 100644 --- a/packages/services/service-job/CHANGELOG.md +++ b/packages/services/service-job/CHANGELOG.md @@ -1,5 +1,56 @@ # @objectstack/service-job +## 16.0.0 + +### Patch Changes + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [e057f42] +- Updated dependencies [a3823b2] +- Updated dependencies [bc65105] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [dd9f223] +- Updated dependencies [46e876c] +- Updated dependencies [5f05de2] +- Updated dependencies [021ba4c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [290e2f0] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + - @objectstack/platform-objects@16.0.0 + - @objectstack/core@16.0.0 + ## 16.0.0-rc.1 ### Patch Changes diff --git a/packages/services/service-job/package.json b/packages/services/service-job/package.json index ffec857dd9..0512496962 100644 --- a/packages/services/service-job/package.json +++ b/packages/services/service-job/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-job", - "version": "16.0.0-rc.1", + "version": "16.0.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 2b90bbcef6..78e86ed013 100644 --- a/packages/services/service-knowledge/CHANGELOG.md +++ b/packages/services/service-knowledge/CHANGELOG.md @@ -1,5 +1,54 @@ # @objectstack/service-knowledge +## 16.0.0 + +### Patch Changes + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [e057f42] +- Updated dependencies [a3823b2] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [dd9f223] +- Updated dependencies [46e876c] +- Updated dependencies [5f05de2] +- Updated dependencies [021ba4c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [290e2f0] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + - @objectstack/core@16.0.0 + ## 16.0.0-rc.1 ### Patch Changes diff --git a/packages/services/service-knowledge/package.json b/packages/services/service-knowledge/package.json index edcba9edcb..9cdfe24a8a 100644 --- a/packages/services/service-knowledge/package.json +++ b/packages/services/service-knowledge/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-knowledge", - "version": "16.0.0-rc.1", + "version": "16.0.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 08b2b84840..9b479542a5 100644 --- a/packages/services/service-messaging/CHANGELOG.md +++ b/packages/services/service-messaging/CHANGELOG.md @@ -1,5 +1,75 @@ # @objectstack/service-messaging +## 16.0.0 + +### Patch Changes + +- 22013aa: **Split the overloaded `managedBy: 'system'` bucket into engine-owned vs. admin-writable, and enforce engine-owned writes (ADR-0103, #3220).** The `system` bucket conflated two incompatible write policies: rows a platform service owns end to end (never user-written), and platform-defined schema whose rows are legitimately admin/user-writable. It carried the same all-false affordance row as `better-auth`/`append-only` but, unlike `better-auth`, had no engine enforcement — a wildcard admin could raw-write these rows through the generic data API (ADR-0049 gap). + + Rather than add a new `managedBy` enum value (which would fall through to fully-editable `platform` defaults on already-deployed Console clients), the write policy is now the **resolved affordance** (`resolveCrudAffordances` = bucket default + `userActions`), and _engine-owned_ is defined as a `system`/`append-only` object that grants no write: + + - **Writable set declares `userActions`** — the RBAC link tables (`sys_user_position`, `sys_user_permission_set`, `sys_position_permission_set`), `sys_user_preference`, `sys_approval_delegation`, and the messaging config grids (`sys_notification_preference` / `…_subscription` / `…_template`) now declare `userActions: { create, edit, delete: true }`. The affordance is a declaration only — the `DelegatedAdminGate` / RLS / permission sets remain the authz. + - **Engine-owned objects locked to reads** — `apiMethods: ['get','list']` added where absent (jobs, notifications, approval request/approver/token/action, `sys_record_share`, `sys_automation_run`, mail/settings/secret audit, the messaging delivery pipeline). `sys_secret` is explicitly read-locked (an empty `apiMethods` array fails open). + - **`sys_import_job`** stays engine-owned: the REST import route now writes its job rows `isSystem`-elevated (attribution preserved via the explicit `created_by` stamp) and the object is locked to `['get','list']`. + - **New engine write guard** (`assertEngineOwnedWriteAllowed`, plugin-security) fail-closed rejects user-context generic writes to engine-owned `system`/`append-only` objects, keyed off the resolved affordance; `isSystem` and context-less engine/service writes bypass by construction. Wired into the security middleware alongside the other data-layer gates. + - **`reconcileManagedApiMethods`** (objectql registry) now runs for **every** managed bucket, not just `better-auth`: any advertised write verb an object's resolved affordances forbid is stripped at registration with a warning (the drift backstop, ADR-0049). + - **`/me/permissions` clamp** (plugin-hono-server) now clamps `system`/`append-only` as well as `better-auth`, so the client hint reflects `permission ∩ guard`. + + **Potentially breaking:** a downstream/third-party `system` object that advertised generic write verbs relying on today's fail-open behaviour will have those verbs stripped (with a warning) and user-context generic writes to it rejected. Declare `userActions` opening the verbs the object legitimately takes from a user context. `better-auth` keeps plugin-auth's identity write guard unchanged; the row-level `managed_by` provenance vocabulary (ADR-0066) is a different axis and is untouched. + +- 62a2117: **Split the overloaded `managedBy: 'system'` bucket with an explicit `engine-owned` value (ADR-0103 addendum, #3343).** ADR-0103 deferred the enum split ("revisitable later as a rename") because a new `managedBy` value would fall through to the fully-editable `platform` default on deployed Console clients. Both reasons against it are now retired — the server-side write guard / `apiMethods` reconciliation / `/me/permissions` clamp make that fallthrough cosmetic (the write is rejected regardless of what the client renders), and objectui#2712 closed the UI union — so v16 lands it, **additively**. + + - **New enum value `engine-owned`** with the same all-locked default affordance row as `system` (`create/import/edit/delete: false`, `exportCsv: true`). It joins `ENGINE_OWNED_BUCKETS` (the engine write guard) and `GUARDED_WRITE_BUCKETS` (the `/me/permissions` clamp); the guard, `reconcileManagedApiMethods`, and the clamp mechanisms are unchanged — `engine-owned` is an explicit member of the set they already covered by resolved affordance. + - **20 objects relabelled `system → engine-owned`** — the ones the engine owns end to end and that declared no write-opening `userActions` (the metadata store, jobs, approval runtime rows, sharing rows, `sys_automation_run`, the messaging delivery/receipt pipeline, `sys_secret`, settings). One-line, behaviour-identical per object. + - **8 admin/user-writable objects keep `managedBy: 'system'`** (the RBAC link tables, `sys_user_preference`, `sys_approval_delegation`, the messaging config grids) — `system` now reads as "engine-managed schema, writable via `userActions`". + + Behaviour-, enforcement- and wire-identical: resolved affordances, the guard verdict, the 405 `apiMethods` reconciliation, and the permissions clamp are the same before and after — this is a self-documenting relabel, not a policy change. No data migration (`managedBy` is schema metadata) and no code branches on the `'system'` literal. Retiring the overloaded `system` entirely (moving the 8 writable objects to a dedicated bucket) is a breaking rename deferred to v17. + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [e057f42] +- Updated dependencies [a3823b2] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [dd9f223] +- Updated dependencies [46e876c] +- Updated dependencies [5f05de2] +- Updated dependencies [021ba4c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [290e2f0] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + - @objectstack/core@16.0.0 + ## 16.0.0-rc.1 ### Patch Changes diff --git a/packages/services/service-messaging/package.json b/packages/services/service-messaging/package.json index 362750c05d..cab16270d6 100644 --- a/packages/services/service-messaging/package.json +++ b/packages/services/service-messaging/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-messaging", - "version": "16.0.0-rc.1", + "version": "16.0.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 01c90a2ec1..3e92970f1d 100644 --- a/packages/services/service-package/CHANGELOG.md +++ b/packages/services/service-package/CHANGELOG.md @@ -1,5 +1,56 @@ # @objectstack/service-package +## 16.0.0 + +### Patch Changes + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [e057f42] +- Updated dependencies [a3823b2] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [dd9f223] +- Updated dependencies [46e876c] +- Updated dependencies [5f05de2] +- Updated dependencies [021ba4c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [beaf2de] +- Updated dependencies [06cb319] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [290e2f0] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + - @objectstack/core@16.0.0 + - @objectstack/metadata-core@16.0.0 + ## 16.0.0-rc.1 ### Patch Changes diff --git a/packages/services/service-package/package.json b/packages/services/service-package/package.json index 2ac951ad9f..6a5d035a40 100644 --- a/packages/services/service-package/package.json +++ b/packages/services/service-package/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-package", - "version": "16.0.0-rc.1", + "version": "16.0.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 6c7e8178f0..374213b3e5 100644 --- a/packages/services/service-queue/CHANGELOG.md +++ b/packages/services/service-queue/CHANGELOG.md @@ -1,5 +1,56 @@ # @objectstack/service-queue +## 16.0.0 + +### Patch Changes + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [e057f42] +- Updated dependencies [a3823b2] +- Updated dependencies [bc65105] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [dd9f223] +- Updated dependencies [46e876c] +- Updated dependencies [5f05de2] +- Updated dependencies [021ba4c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [290e2f0] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + - @objectstack/platform-objects@16.0.0 + - @objectstack/core@16.0.0 + ## 16.0.0-rc.1 ### Patch Changes diff --git a/packages/services/service-queue/package.json b/packages/services/service-queue/package.json index 36a08d5961..6d792c397f 100644 --- a/packages/services/service-queue/package.json +++ b/packages/services/service-queue/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-queue", - "version": "16.0.0-rc.1", + "version": "16.0.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 f6b127aa2b..c49190d0ab 100644 --- a/packages/services/service-realtime/CHANGELOG.md +++ b/packages/services/service-realtime/CHANGELOG.md @@ -1,5 +1,85 @@ # @objectstack/service-realtime +## 16.0.0 + +### Patch Changes + +- 06cb319: fix(identity): close the generic-write apiMethods hole on sys_presence and sys_metadata (#3220) + + Follow-through on #1591/#3213 (better-auth apiMethods reconciliation) for two + non-better-auth managed objects that shipped the same contradiction: their + `enable.apiMethods` advertised generic `create`/`update`/`delete` while their + `managedBy` bucket forbids user-context writes, leaving the generic `/data` + route open to a write the bucket does not permit. + + - `sys_presence` (`managedBy: 'append-only'`) advertised `create`/`update`/`delete` + (update/delete on an append-only object at that) but is written only over the + realtime websocket/in-memory path, never through ObjectQL. Narrowed to + `['get', 'list']`. + - `sys_metadata` (`managedBy: 'system'`) advertised full CRUD but customization + overlays are authored only through the metadata-protocol RPC (engine writes + carry a transaction context, not a user session); neither the framework nor + the Console (objectui) POSTs `/data/sys_metadata`. Narrowed to `['get', 'list']`. + + Reads stay open. The metadata-protocol / realtime write paths are engine-level + and bypass the HTTP exposure gate, so they are unaffected — verified by the + metadata-authoring dogfood and the objectql overlay tests. + + A blast-radius audit confirmed the broader `system`/`append-only` buckets are NOT + safe to guard wholesale: several `system` objects (`sys_user_position`, + `sys_user_permission_set`, `sys_position_permission_set`, `sys_user_preference`, + `sys_import_job`) are legitimately user-writable by design (delegated + administration, user preferences, imports). Generalizing the engine write guard + to those buckets is intentionally NOT done here — see #3220 for the bucket-taxonomy + root cause. + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [e057f42] +- Updated dependencies [a3823b2] +- Updated dependencies [bc65105] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [dd9f223] +- Updated dependencies [46e876c] +- Updated dependencies [5f05de2] +- Updated dependencies [021ba4c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [290e2f0] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + - @objectstack/platform-objects@16.0.0 + - @objectstack/core@16.0.0 + ## 16.0.0-rc.1 ### Patch Changes diff --git a/packages/services/service-realtime/package.json b/packages/services/service-realtime/package.json index b11612e2a6..49f3e14f6c 100644 --- a/packages/services/service-realtime/package.json +++ b/packages/services/service-realtime/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-realtime", - "version": "16.0.0-rc.1", + "version": "16.0.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 ec54b6fea5..1078f566b4 100644 --- a/packages/services/service-settings/CHANGELOG.md +++ b/packages/services/service-settings/CHANGELOG.md @@ -1,5 +1,60 @@ # @objectstack/service-settings +## 16.0.0 + +### Patch Changes + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [e057f42] +- Updated dependencies [a3823b2] +- Updated dependencies [bc65105] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [dd9f223] +- Updated dependencies [46e876c] +- Updated dependencies [5f05de2] +- Updated dependencies [021ba4c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [83e8f7d] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [290e2f0] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [92f5f19] +- Updated dependencies [32899e6] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + - @objectstack/platform-objects@16.0.0 + - @objectstack/core@16.0.0 + - @objectstack/types@16.0.0 + ## 16.0.0-rc.1 ### Patch Changes diff --git a/packages/services/service-settings/package.json b/packages/services/service-settings/package.json index 2a3daf6544..371d209a90 100644 --- a/packages/services/service-settings/package.json +++ b/packages/services/service-settings/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-settings", - "version": "16.0.0-rc.1", + "version": "16.0.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 2c7ae85349..b9ddc12eec 100644 --- a/packages/services/service-sms/CHANGELOG.md +++ b/packages/services/service-sms/CHANGELOG.md @@ -1,5 +1,54 @@ # @objectstack/service-sms +## 16.0.0 + +### Patch Changes + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [e057f42] +- Updated dependencies [a3823b2] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [dd9f223] +- Updated dependencies [46e876c] +- Updated dependencies [5f05de2] +- Updated dependencies [021ba4c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [290e2f0] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + - @objectstack/core@16.0.0 + ## 16.0.0-rc.1 ### Patch Changes diff --git a/packages/services/service-sms/package.json b/packages/services/service-sms/package.json index 856d9302c9..5a42f2e6b3 100644 --- a/packages/services/service-sms/package.json +++ b/packages/services/service-sms/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-sms", - "version": "16.0.0-rc.1", + "version": "16.0.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 8252cd3b3f..f4f1b3129b 100644 --- a/packages/services/service-storage/CHANGELOG.md +++ b/packages/services/service-storage/CHANGELOG.md @@ -1,5 +1,98 @@ # @objectstack/service-storage +## 16.0.0 + +### Patch Changes + +- ee0a499: feat(i18n): localize collaboration notification titles and the storage objects; wire the notifications REST routes + + Three gaps behind one report (a `sys_file "repro.png" assigned to you` + notification that was English on an all-Chinese workspace, opened an English + detail page, and never cleared its unread state): + + - **plugin-audit** — the assignment (`collab.assignment`) and @mention + (`collab.mention`) bell titles were hardcoded English literals built from the + raw object API name. They now resolve through the i18n service with the same + key shapes as the activity summaries (framework#3039): new + `messages.assignedToYou` / `messages.mentionedYou` / + `messages.mentionedYouAnonymous` templates (en / zh-CN / ja-JP / es-ES), the + object named by its translated label (`objects.{name}.label` → authored def + label → API name), and the locale resolved for the **recipient** (they read + the bell), not the acting user. Every step stays best-effort: no locale / no + i18n / key miss degrades to the English literal — which now also prefers the + authored object label over the API name. + + - **service-storage** — `sys_file` / `sys_upload_session` had no translation + bundle at all, so the file detail page (labels, and the Pending Upload / + Committed / Deleted status pipeline) rendered English on every locale. The + service now ships its own ADR-0029 D8 bundle (en / zh-CN / ja-JP / es-ES, + `src/translations` + `scripts/i18n-extract.config.ts`) and contributes it via + `i18n.loadTranslations` on `kernel:ready`, matching service-messaging. + (`sys_attachment` stays in platform-objects' bundles pending the + storage-domain decomposition.) + + - **runtime** — the in-app notifications REST surface (`GET +/api/v1/notifications`, `POST /api/v1/notifications/read`, `POST +/api/v1/notifications/read/all`; ADR-0030) had its `handleNotification` + dispatch branch and discovery entry, but no `server.()` mount in + `dispatcher-plugin`, so only the cloud hosts' hono catch-all reached it — the + standalone / `os dev` server 404'd every request. That left mark-read with no + working endpoint (the console's direct `sys_notification_receipt` write is + rejected by ADR-0103's engine-owned gate), so unread notifications could never + clear. The three routes are now mounted explicitly, guarded by the + route-registration regression test. + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [e057f42] +- Updated dependencies [a3823b2] +- Updated dependencies [bc65105] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [dd9f223] +- Updated dependencies [46e876c] +- Updated dependencies [5f05de2] +- Updated dependencies [021ba4c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [efbcfe1] +- Updated dependencies [2049b6a] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [290e2f0] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [ce468c8] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + - @objectstack/platform-objects@16.0.0 + - @objectstack/core@16.0.0 + - @objectstack/observability@16.0.0 + ## 16.0.0-rc.1 ### Patch Changes diff --git a/packages/services/service-storage/package.json b/packages/services/service-storage/package.json index cd58e2e8f1..5551dd3ecc 100644 --- a/packages/services/service-storage/package.json +++ b/packages/services/service-storage/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-storage", - "version": "16.0.0-rc.1", + "version": "16.0.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 1c2a4ec3e0..a6efd51939 100644 --- a/packages/spec/CHANGELOG.md +++ b/packages/spec/CHANGELOG.md @@ -1,5 +1,771 @@ # @objectstack/spec +## 16.0.0 + +### Major Changes + +- 6c270a6: **BREAKING: remove the deprecated `ctx.session.tenantId` / `ctx.user.tenantId` alias from the hook & action authoring surface — converge on `organizationId` (#3290).** + + #3280 made `organizationId` the blessed developer-facing name for the caller's active org across the JS authoring surface and kept `tenantId` as a `@deprecated` alias carrying the identical value. That alias is now **removed** from the hook `ctx.session`, the action-body `ctx.session`, and the action-body `ctx.user`. Read the caller's active org under the single blessed name: + + ```diff + - const org = ctx.session.tenantId; // hook or action body + + const org = ctx.user?.organizationId ?? ctx.session?.organizationId; + ``` + + **FROM → TO migration** (in any `*.hook.ts` / `*.action.ts` body): + + - `ctx.session.tenantId` → `ctx.session.organizationId` + - `ctx.user.tenantId` (action body) → `ctx.user.organizationId` + + The value is unchanged — `organizationId` is the same active-org id, matching the `organization_id` column and `current_user.organizationId` in RLS/sharing. `ctx.user` is `undefined` for system / unauthenticated writes, so read `ctx.session?.organizationId` when a hook or action must work regardless of a resolved user. + + What changed internally: + + - **`@objectstack/spec`** — `HookContextSchema.session` drops the `tenantId` field (only `organizationId` remains). A stray `tenantId` on a constructed session is now stripped by the schema. + - **`@objectstack/objectql`** — the engine's `buildSession()` no longer emits `session.tenantId`; the audit-stamp plugin sources the `tenant_id` column from `session.organizationId`. + - **`@objectstack/runtime`** — `buildActionSession()` and the REST action `ctx.user` no longer emit `tenantId`. + - **`@objectstack/trigger-record-change`** — reads `session.organizationId` (was `session.tenantId`) when forwarding the writer's org to a `runAs:'user'` flow; behavior is identical. + + **Explicit non-goal (unchanged):** the generic **driver-layer** tenancy abstraction is _not_ touched — `ExecutionContext.tenantId`, `DriverOptions.tenantId`, `SqlDriver.applyTenantScope` / `TenancyConfig.tenantField`, and `ExecutionLog.tenantId`. That isolation column is configurable and legitimately carries an _environment_ id in database-per-tenant kernels; it is a distinct axis from the developer-facing org. The build-time `check:org-identifier` guard now also covers `packages/**` to keep reference bodies off the removed name. + +### Minor Changes + +- f972574: feat(spec): `ActionParamSchema` gains optional widget config — `multiple`, `accept`, `maxSize` + + The console now renders action params through the same field-widget renderer + the record form uses (objectui#2700, objectui ADR-0059), so inline params can + declare the widget config the form widgets consume: `multiple` (array value + shape, mirrors `FieldSchema.multiple`), and the upload constraints `accept` + (MIME types / extensions) and `maxSize` (bytes) for `file`/`image` params. + Field-backed params (`{ field }`) keep inheriting these from the referenced + field at runtime; inline values override. Purely additive — no existing + schema changes shape. + +- 6289ec3: feat(i18n): translation slot for action `resultDialog` copy — the one-shot secret-reveal dialogs are now localizable + + The post-success `resultDialog` (temporary passwords, 2FA backup codes, OAuth + client secrets) had no slot in the translation protocol, so its title / + description / acknowledge button / field labels always rendered the hardcoded + English metadata literals even on fully-translated locales. + + - **spec.** `_actions.` (object + object-first node) and + `globalActions.` gain an optional `resultDialog` translation node + (`ActionResultDialogTranslationSchema`): `title`, `description`, + `acknowledge`, and `fields` keyed by the **literal** result-field path + (e.g. `"user.email"` — keys may contain dots; resolvers index the record + directly, never split on `.`). New `resolveActionResultDialog` overlay + resolver, wired into `translateAction` for API-boundary translation. + - **cli.** `os i18n extract` emits the new `resultDialog.*` keys (title / + description / acknowledge / `fields.` for labelled fields), so + coverage and skeleton generation see them. + - **platform-objects.** en / zh-CN / ja-JP / es-ES bundles ship the + resultDialog copy for all six shipped dialogs: `sys_user.create_user`, + `sys_user.set_user_password`, `sys_two_factor.enable_two_factor`, + `sys_two_factor.regenerate_backup_codes`, + `sys_oauth_application.create_oauth_application`, and + `sys_oauth_application.rotate_client_secret`. + + Client-side rendering lands in objectui (`actionResultDialog` resolver in + `@object-ui/i18n` + result-dialog handlers). Purely additive — untranslated + locales keep falling back to the metadata literals. + +- 8efa395: feat(approvals): server-computed `viewer` capability for precise decision-action gating + + `getRequest` / `listRequests` now attach a per-viewer block — + `viewer: { can_act, is_submitter }` — computed from the caller's context + (`ApprovalRequestRow.viewer`): + + - `can_act` — the caller is a _current pending approver_ (their user id is in the + request's resolved `pending_approvers` while it is still `pending`). This is + the same check the decision methods authorize with, so it already reflects + position/team/manager resolution — strictly more accurate than a client-side + identity guess. + - `is_submitter` — the caller submitted the request. + + The declared decision actions on `sys_approval_request` now gate on it: approver + actions (approve/reject/reassign/send-back/request-info) use + `record.viewer.can_act`; submitter levers (remind/recall/resubmit) use + `record.viewer.is_submitter`. Previously approver actions only trimmed the + non-pending case, so a submitter viewing their own pending request saw buttons + they couldn't use (the backend 403'd); a position-addressed approver could be + wrongly hidden by the old client heuristic. Where `viewer` is absent (a row + surfaced outside a service read with a user context), the predicate fails closed. + +- 3a18b60: feat(approvals): rename the `role` approver type to `org_membership_level` (#3133) + + `ApproverType.role` was the last platform surface projecting the reserved word + "role" (ADR-0090 D3). It is not covered by D3's better-auth exception: that + exception protects better-auth's own `sys_member.role` **column**, which we do + not own — `ApproverType` is our own enum, an authoring surface, and D3 mandates + that the projection of that concept is spelled `org_membership_level` and + labelled "organization membership", **never "role"**. + + The sentence licensing the leak was also false: ADR-0090 D3 claims + `sys_member.role` is "already relabelled `org_membership_level` in the platform + projection", but `org_membership_level` existed nowhere in the codebase and + ADR-0057 D7 lists that relabel under "Deferred (evidence-gated, P4)". The + projection never landed, so the word reached authors. + + The name manufactured a real, silent failure — "hotcrm class": every other + surface renamed to `position` (`sys_role`, `ShareRecipientType.role`, + `ctx.roles[]`), so `{ type: 'role', value: 'sales_manager' }` reads as the + legacy spelling of a position. It resolves against the membership tier, finds + no member row, falls back to an inert `role:sales_manager` literal, and the + request waits forever on an approver that cannot exist. + + - **spec**: `ApproverType` gains `org_membership_level`; `role` stays as a + deprecated alias for one window (a published 15.x flow keeps loading) with + `DEPRECATED_APPROVER_TYPES` + `canonicalApproverType()` as the single source + for the mapping. Removed in the next major. + - **plugin-approvals**: resolves on the canonical type and warns on the + deprecated spelling. The `type:value` fallback literal keeps the **authored** + spelling — stored `sys_approval_approver` rows and `pending_approvers` slots + from 15.x carry `role:`, and rewriting it would orphan them. + - **lint**: `approval-role-not-membership-tier` → `approval-approver-not-membership-tier` + (the rule id carried the reserved word too), plus a new + `approval-approver-type-deprecated`. The two are mutually exclusive: a bad + _value_ wins, because prescribing `org_membership_level` for a position name + would be wrong advice — the fix there is `position`. + + Authoring `type: 'role'` keeps working and now says so out loud. Rewrite it as + `org_membership_level`; if the value is an org position, the fix is `position`. + +- 43a3efb: fix(rest): gate the cross-object transactional batch by the same per-object API rules as single-record writes (#1604) + + The `POST {basePath}/batch` route (issue #1604 / ADR-0034) wraps N cross-object + create/update/delete ops in one engine transaction, but it skipped the + per-object API-exposure gate every single-record route applies — an + authenticated caller could write to an `apiEnabled: false` object, or run an + operation outside an object's `apiMethods` whitelist, straight through the batch + surface (ADR-0049 / #1889 — the same "declared ≠ enforced" hole closed for the + generic write path in #3220 / #3213). + + The route now: + + - validates the body against a new `CrossObjectBatchRequestSchema` + (`@objectstack/spec/api`, Zod-First) — a malformed op, an unknown action, or a + missing `object` is a `400` instead of a `500`; + - enforces `enable.apiEnabled` / `enable.apiMethods` for **every** op (metadata + fetched once, each distinct `(object, action)` checked) BEFORE opening the + transaction — `404 OBJECT_API_DISABLED` / `405 OBJECT_API_METHOD_NOT_ALLOWED`; + - requires an `id` for `update` / `delete` (`400`); + - rejects an unresolvable `{ $ref }` with `400 BATCH_UNRESOLVED_REF` instead of + silently writing a `null` FK; + - rejects an explicit `atomic: false` (`400 BATCH_NOT_ATOMIC`) rather than + silently applying atomically — non-atomic per-object batches stay on + `POST /data/:object/batch`. + + `enforceApiAccess` is refactored to share the pure `apiAccessDenialFromEnable` + check + a `loadObjectItems` helper with the batch route (single-record behavior + unchanged). Adds `rest-batch-endpoint.test.ts` — the REST-boundary coverage + ADR-0034 flagged as missing (commit, `$ref`, rollback surfacing, API-access + denial, request validation). + +- 524696a: feat(spec)!: `DashboardWidgetSchema.strict()` — reject undeclared widget keys (framework#3251) + + The ADR-0021 analytics endpoint. `DashboardWidgetSchema` now rejects any + undeclared top-level key instead of silently stripping it, moving a whole class + of author error (a hallucinated or legacy key that renders as a silent no-op) + from fallible human review to deterministic CI. `options: z.unknown()` remains + the escape hatch for renderer-specific extras. + + A custom error map names the offending key(s) and, when a key is a removed + pre-ADR-0021 inline-analytics key (`object` / `categoryField` / `valueField` / + `aggregate`, pivot `rowField` / `columnField`) or an objectui-internal prop + (`component`, inline `data`), points the author at the dataset shape + (`dataset` + `dimensions` + `values`). + + Recorded as protocol-16 migration `step16` + (`dashboard-widget-strict-unknown-keys`), mirroring protocol-15's `step15` + strict flip on the form/page schemas (ADR-0089 D3a). The inline-analytics shape + itself was already removed at protocol 9 (single-form cutover), so there is no + mechanical rewrite — the residue is the strictness, delegated to the author. + + **Breaking:** shipped as `minor` per the launch-window policy (a breaking change + does not burn a major while the stack is in lockstep), riding the already-pending + 16.0.0 train. The release train's Version-Packages PR must set + `PROTOCOL_VERSION = '16.0.0'`; until then `step16` is inert + (`composeMigrationChain` caps at `PROTOCOL_MAJOR`). + + `@objectstack/lint` — the `widget-legacy-analytics-shape` / + `widget-legacy-analytics-unrenderable` rules are retained as the friendly, + suppressible bridge on the raw-config lint/doctor paths (strict preempts them on + the schema-parsed compile/validate paths); doc comment updated to explain the + interplay. + +- bfa3c3f: **Broadcast a `transactionalBatch` capability bit in discovery so clients negotiate the atomic cross-object batch declaratively, instead of runtime-probing 404/405/501 (#3298).** + + The atomic cross-object batch endpoint (`POST {basePath}/batch`, #1604 / ADR-0034 item 4) and its typed SDK surface (`client.data.batchTransaction`, #3271) already shipped, but discovery never told a client whether a backend actually supports it. Consumers (notably ObjectUI's `ObjectStackAdapter`) had to _probe_: fire a `/batch`, read `404`/`405` (no route) or `501` (no runtime transaction), and only then fall back to non-atomic client-side simulation. That is "find out by calling", not capability negotiation — it cannot be decided at connect time and cannot serve as the "minimum backend supports `/batch`" gate that blocks hard-deleting the non-atomic fallback downstream. + + `WellKnownCapabilitiesSchema` gains a required `transactionalBatch: boolean`, and **every** discovery producer fills it honestly (`declared === enforced`), so it never becomes a declared-but-unpopulated bit: + + - **`@objectstack/metadata-protocol`** (`getDiscovery`) — reports whether the runtime engine can honour a transaction (`typeof engine.transaction === 'function'`). The `/batch` handler runs its ops inside `engine.transaction()`, which degrades to a non-atomic passthrough (or 501) without one. + - **`@objectstack/rest`** (`/discovery`) — ANDs the engine signal with whether it actually mounts the route (`api.enableBatch`), so a server with batch disabled reports `false` even on a transaction-capable engine (never advertise an endpoint that would 404). + - **`@objectstack/plugin-hono-server`** (standalone discovery) — reports `false`: this minimal surface registers CRUD only and does not mount `/batch` (that ships with `@objectstack/rest`). Under-reporting is the safe direction — a client keeps its correct-but-slower fallback rather than losing atomicity. + - **`@objectstack/client`** — already normalizes hierarchical `capabilities` to flat booleans, so `client.capabilities.transactionalBatch` is exposed (and now typed) for declarative consumers. + + The bit follows the existing capability semantics: `true` ⟺ the `/batch` route is mounted **and** the runtime can honour a transaction — the exact condition under which the endpoint returns `200` rather than `404`/`405`/`501`. Additive and behavior-preserving; only the discovery payload gains a field. + +- 62a2117: **Split the overloaded `managedBy: 'system'` bucket with an explicit `engine-owned` value (ADR-0103 addendum, #3343).** ADR-0103 deferred the enum split ("revisitable later as a rename") because a new `managedBy` value would fall through to the fully-editable `platform` default on deployed Console clients. Both reasons against it are now retired — the server-side write guard / `apiMethods` reconciliation / `/me/permissions` clamp make that fallthrough cosmetic (the write is rejected regardless of what the client renders), and objectui#2712 closed the UI union — so v16 lands it, **additively**. + + - **New enum value `engine-owned`** with the same all-locked default affordance row as `system` (`create/import/edit/delete: false`, `exportCsv: true`). It joins `ENGINE_OWNED_BUCKETS` (the engine write guard) and `GUARDED_WRITE_BUCKETS` (the `/me/permissions` clamp); the guard, `reconcileManagedApiMethods`, and the clamp mechanisms are unchanged — `engine-owned` is an explicit member of the set they already covered by resolved affordance. + - **20 objects relabelled `system → engine-owned`** — the ones the engine owns end to end and that declared no write-opening `userActions` (the metadata store, jobs, approval runtime rows, sharing rows, `sys_automation_run`, the messaging delivery/receipt pipeline, `sys_secret`, settings). One-line, behaviour-identical per object. + - **8 admin/user-writable objects keep `managedBy: 'system'`** (the RBAC link tables, `sys_user_preference`, `sys_approval_delegation`, the messaging config grids) — `system` now reads as "engine-managed schema, writable via `userActions`". + + Behaviour-, enforcement- and wire-identical: resolved affordances, the guard verdict, the 405 `apiMethods` reconciliation, and the permissions clamp are the same before and after — this is a self-documenting relabel, not a policy change. No data migration (`managedBy` is schema metadata) and no code branches on the `'system'` literal. Retiring the overloaded `system` entirely (moving the 8 writable objects to a dedicated bucket) is a breaking rename deferred to v17. + +- fefcd54: fix(spec): declare `ownership` as a first-class ObjectSchema field (#3175) + + The object-level record-ownership model — `ownership: 'user' | 'org' | 'none'`, + which drives the registry's `owner_id` auto-provisioning (`applySystemFields`) — + was read by the engine via `(schema as any).ownership` while `ObjectSchema.create()` + **rejected** it as an unknown top-level key (ADR-0032 / #1535). So a tested engine + opt-out (`ownership: 'org' | 'none'` on catalog / junction tables) could not be + set through the sanctioned authoring path, and the same `ownership` word was read + elsewhere as the unrelated package-contribution kind (`own` / `extend`). + + - **spec**: `ObjectSchema` now declares `ownership: z.enum(['user','org','none']).optional()`. + Authoring the record-ownership opt-out validates cleanly; the registry reads it + off the typed schema (no `as any`). A retired `ownership: 'own'` / `'extend'` + value fails with guidance pointing at the record-ownership model and noting that + `own`/`extend` is the contribution kind (`registerObject`), not an object-schema value. + - **cli**: the `object` scaffold no longer emits the now-invalid `ownership: 'own'` + (owner injection is the default), and `objectstack info` labels the record model + with the correct `user` default. + + No runtime behavior change: `applySystemFields` and its `owner_id` injection logic + are unchanged — this makes the property the engine already honors legally authorable + and consistently typed. + +- 369eb6e: refactor(spec): remove unenforced agent `visibility` field (ADR-0056 D8, #1901) + + The agent `visibility` (`global`/`organization`/`private`) field is **removed** + from `AgentSchema`. It was never enforced: the chat-access evaluator excluded it + and the agent list route did not filter by it, so setting `private` never hid an + agent. Per ADR-0049 / ADR-0056 D8 ("design+enforce or remove"), a security-shaped + field with no runtime consumer is a liability — authors who set `private` believe + they've restricted an agent when they have not. + + Unlike `field-encryption` (kept `[EXPERIMENTAL]` — it has a stable schema shape on + a real roadmap), correct `visibility` enforcement is undesigned: it needs + owner/org anchors that do not exist today. `agent.tenantId` was already removed + (#2377), agents carry no owner field, and the `EXTERNAL` posture rung is defined + but never derived — so `organization` vs `global` is runtime-indistinguishable. + The semantics, not just the plumbing, are unresolved, so the field is dropped + rather than carried marked. + + - `AgentSchema` is not `.strict()`, so existing metadata that still sets + `visibility` parses cleanly — the unknown key is stripped, not rejected. + - Use `access` / `permissions` to restrict who can use an agent — both **enforced** + at the chat route (#1884). + - Re-introduce `visibility` when the agent listing surface gains real owner/org + semantics; tracked in #1901. + + Also updated: authoring form (`agent.form.ts`), liveness ledger + (`liveness/agent.json`), the ADR-0056 D10 authz-conformance matrix (moved from + `experimental` to `removed`), and the generated schema reference docs. + +- 06ff734: feat(spec)!: remove deprecated `aiStudio`/`aiSeat` capability aliases (#3308) + + **BREAKING** (shipped as minor per the launch-window convention). The one-cycle + deprecation window from #3265 is over: the legacy camelCase `requires` spellings + `aiStudio`/`aiSeat` are no longer canonicalized to `ai-studio`/`ai-seat` — they + are now plain unknown tokens, rejected by `defineStack` like any other typo. + + - Removed exports `DEPRECATED_PLATFORM_CAPABILITY_ALIASES` and + `canonicalizePlatformCapability` from `@objectstack/spec`; `isKnownPlatformCapability` + no longer canonicalizes. + - `defineStack` no longer rewrites aliases (the `canonicalizeStackRequires` pass + is gone); the serve resolver no longer canonicalizes raw-artifact `requires`. + + Migration: use the canonical kebab-case tokens `ai-studio` / `ai-seat`. All + first-party configs were migrated in #862/#863; only stacks still carrying the + legacy spelling are affected. Cloud's `objectos-runtime` (pinned to an older + framework) follows on its next `.framework-sha` bump. + +- b659111: feat(spec)!: remove dead author-facing metadata properties (#2377, ADR-0049 enforce-or-remove) + + Breaking spec-surface removal, versioned as `minor` per the launch-window changeset + policy (a `major` would promote the whole fixed-group monorepo; breaking cleanups ride + the minor line, as with #2402 → 11.1.0). + + Removes a batch of spec properties that parsed but had **no runtime consumer** — + authoring them was a false affordance (especially dangerous for AI-authored + metadata). Verified dead against the liveness ledger (`packages/spec/liveness/*.json`) + and a repo-wide grep of readers. This is the follow-up slice to #2402. + + ## Removed (each was `dead` + no reader anywhere) + + - **field** (`field.zod.ts`): `vectorConfig` (+ `VectorConfigSchema` + types), + `fileAttachmentConfig` (+ `FileAttachmentConfigSchema` + types), `dependencies`. + Vector fields keep the live flat `dimensions` prop; file/image fields keep the + live flat `multiple`/`accept`/`maxSize` siblings. + - **object** (`object.zod.ts`): `versioning` (+ `VersioningConfigSchema`), + `softDelete` (+ `SoftDeleteConfigSchema`), `search` (+ `SearchConfigSchema`), + `recordName`, `keyPrefix`. Each is now a **rejecting tombstone** in + `UNKNOWN_KEY_GUIDANCE` carrying the upgrade prescription. + - **action** (`action.zod.ts`): `timeout` (server uses `body.timeoutMs`; no + action-level timeout is enforced). + - **agent** (`agent.zod.ts`): `planning.strategy`, `planning.allowReplan` + (only `planning.maxIterations` is read by the runtime). + - **dataset** (`dataset.zod.ts`): `measures.certified` (declared-but-unenforced + governance flag — never compiled into the Cube). + + Liveness ledgers, the ledger README table, and `api-surface.json` are updated; + the removed sub-schema keys are dropped from `json-schema.manifest.json`. + + ## Migration + + - **field/agent/dataset/action props**: authoring them is now silently stripped + (they never did anything). Remove them. Vector → set flat `dimensions`; + file/image → set flat `multiple`/`accept`/`maxSize`. + - **object props**: `ObjectSchema.create()` now throws a located error naming the + replacement — `versioning`/`softDelete` → hard deletes + `Field.trackHistory` / + `lifecycle`; `search` → `searchableFields`; `recordName` → an `autonumber` + `Field` designated as `nameField`; `keyPrefix` → remove (never had an effect). + + ## Deliberately NOT removed (dead, but entangled — a scoped follow-up) + + `field.index`/`columnName`/`referenceFilters` and object + `tags`/`active`/`isSystem`/`abstract`/`enable.searchable`/`enable.trash`/`enable.mru` + and `agent.tenantId` are surfaced in the Studio metadata-authoring forms + (`*.form.ts`) — removing them cascades into i18n bundle regeneration, so they are + deferred. `action.type:'form'` has a dedicated build-time lint (`lint-view-refs.ts`) + and a first-party showcase usage, so it needs a UX decision. `field.columnName` + additionally has an ADR-0062 D7 lint. These stay `dead` + `authorWarn` in the + ledgers. + +- 5754a23: feat(spec)!: remove form-surfaced dead metadata props + correct 3 misclassified-live entries (#2377, ADR-0049) + + The next enforce-or-remove slice of #2377. Versioned `minor` per the launch-window + policy (the fixed group makes a `major` promote the whole monorepo). + + ## Removed (dead, no runtime reader — verified in both framework and objectui) + + - **field**: `columnName`, `index`, `referenceFilters`. This empties the field + dead-prop set. `columnName` also removed its now-moot **ADR-0062 D7** lint + (`validate-expressions.ts`), the dead `StorageNameMapping.resolveColumnName` / + `buildColumnMap` / `buildReverseColumnMap` helpers, and closes ADR-0062 R10 — + external physical-column mapping is `external.columnMap` only. + - **object**: `tags`, `active`, `abstract` — now rejecting tombstones in + `UNKNOWN_KEY_GUIDANCE`. + - **agent**: `tenantId`. + + The removed props are dropped from the authoring forms (`field/object/agent.form.ts`) + and the regenerated metadata-forms i18n bundles. + + ## Corrected to `live` (the ledger was wrong — readers existed) + + - **object `isSystem`** — `plugin-sharing` `effectiveSharingModel` defaults a + no-`sharingModel` `isSystem` object to public; also read by the security-posture + lint. KEPT. + - **object `enable.searchable`** — `metadata-protocol` global search (`searchAll`) + uses `enable.searchable === false` as an opt-out. KEPT. + - **action `type:'form'`** — objectui `ActionRunner.executeForm` routes it to the + FormView at `/forms/:target`; a build-time lint validates the target. KEPT. + + ## Deliberately deferred + + `object.enable.trash` / `enable.mru` — dead, but inert `default(true)` flags set by + ~35 `sys-*.object.ts` files; removing them is high-churn / low-value. Left `dead` + (authorWarn-skipped). + + ## Migration + + - field/agent props: authoring them was already a no-op; they now strip silently. + `columnName` → the physical column is always the field key (rename the field, or + use `external.columnMap` for external objects); `index` → declare it in object + `indexes[]`; `referenceFilters` → `lookupFilters`. + - object `tags`/`active`/`abstract`: `ObjectSchema.create()` now throws a located + error naming the removal. None gated anything at runtime — remove them. + +- 668dd17: **Breaking (npm type surface): retire the vestigial feed contracts + protocol surface (ADR-0052 §5 follow-up, #1959).** + + The `service-feed` runtime was deleted in #1955; `sys_comment` / `sys_activity` + are the canonical record-collaboration/timeline backend. This removes the dead + type surface that still pointed at the deleted runtime — every removed method was + already unreachable (the feed REST route was never mounted → 404; the protocol + implementation was never wired with a feed service, so `requireFeedService()` + could only throw). No behavior changes. + + No authorable metadata key is removed (the `feeds:` object capability flag and + the `RecordActivity` UI component config are unchanged), so `PROTOCOL_MAJOR` + stays 15 and this ships as `minor` rather than a protocol major. + + FROM → TO migration for every removed export: + + - `@objectstack/spec/contracts` — `IFeedService`, `CreateFeedItemInput`, + `UpdateFeedItemInput`, `ListFeedOptions`, `FeedListResult` → **removed, no + replacement**. Comments/activity are plain records: write `sys_comment` / read + `sys_activity` via the data engine or the REST data API. + - `@objectstack/spec/api` — `FeedApiContracts`, `FeedApiErrorCode`, + `FeedProtocol`, and all feed request/response schemas + types (`GetFeed*`, + `CreateFeedItem*`, `UpdateFeedItem*`, `DeleteFeedItem*`, `AddReaction*`, + `RemoveReaction*`, `PinFeedItem*`, `UnpinFeedItem*`, `StarFeedItem*`, + `UnstarFeedItem*`, `SearchFeed*`, `GetChangelog*`, `ChangelogEntry`, + `SubscribeRequest/Response`, `FeedUnsubscribeRequest`, `UnsubscribeResponse`, + `FeedPathParams`, `FeedItemPathParams`, `FeedListFilterType`) → **removed**. Use + the data API against `sys_comment` / `sys_activity` (`/api/v1/data/sys_comment/…`); + reactions and threaded replies are fields on `sys_comment`. + - `@objectstack/spec/data` — `FeedItemSchema`/`FeedItem`, `FeedActorSchema`/`FeedActor`, + `MentionSchema`/`Mention`, `ReactionSchema`/`Reaction`, + `FieldChangeEntrySchema`/`FieldChangeEntry`, `FeedVisibility`, + `RecordSubscriptionSchema`/`RecordSubscription`, `SubscriptionEventType`, and the + `data`-namespace `NotificationChannel` → **removed**. `FeedItemType` and + `FeedFilterMode` are **kept** (live UI activity-timeline config). For notification + channels use `NotificationChannelSchema` from `@objectstack/spec/system`. + - `@objectstack/client` — `client.feed.*` (`list` / `create` / `update` / `delete` / + `addReaction` / `removeReaction` / `pin` / `unpin` / `star` / `unstar` / `search` / + `getChangelog` / `subscribe` / `unsubscribe`) and the re-exported feed response + types → **removed**. One-line fix: use `client.data.*` on `sys_comment` / + `sys_activity`, e.g. `client.data.create('sys_comment', { object, record_id, body })` + and `client.data.find('sys_activity', { filters: [['record_id', '=', id]] })`. + - `@objectstack/metadata-protocol` — `ObjectStackProtocolImplementation` no longer + implements the 14 feed methods; its constructor + `(engine, getServicesRegistry?, getFeedService?, environmentId?)` becomes + `(engine, getServicesRegistry?, environmentId?)`. One-line fix: delete the third + argument. + +- 8abf133: **Breaking (discovery response shape): retire the residual feed capability surface (#3180, follow-up to #1959 / ADR-0052 §5).** + + The feed backend was retired long ago; #1959 removed the feed contracts + SDK. This + removes the last discovery/dispatcher references to it, and fixes a real bug where the + `comments` capability was permanently `false`. + + - `@objectstack/spec` — `WellKnownCapabilitiesSchema.feed` and `ApiRoutesSchema.feed` + (`routes.feed`) are **removed**, and the `/api/v1/feed` entry is dropped from + `DEFAULT_DISPATCHER_ROUTES`. FROM → TO: clients reading `discovery.capabilities.feed` + or `discovery.routes.feed` → use `discovery.capabilities.comments`; comments/activity + are served by the generic data API on `sys_comment` / `sys_activity` + (`/api/v1/data/sys_comment/…`). + - `@objectstack/metadata-protocol` — `getDiscovery()` no longer emits the always-`false` + `feed` service/capability. **Bug fix:** the `comments` capability previously keyed off + the deleted `'feed'` service (so it was permanently `false` after #1955); it now tracks + the presence of the `sys_comment` object (provided by the always-on audit slate), so + `declared === enforced`. + - `@objectstack/client` — the internal `feed: '/api/v1/feed'` route constant is removed + (it only existed to satisfy the now-removed `ApiRoutes.feed` type; no client code used it). + +- 04ecd4e: feat(validation): `state_machine.initialStates` enforces the FSM entry point on INSERT (#3165) + + A `state_machine` rule's `transitions` only governs UPDATE — on INSERT the rule + was a no-op, and a `select` field permits ANY declared option as the initial + value. So a record could be born mid-flow (created already `approved`), skipping + the whole state machine. This was the gap #3043's mitigation idea assumed didn't + exist (declared ≠ enforced, ADR-0049). + + `state_machine` rules gain an optional `initialStates: string[]` — the states a + record may be CREATED in. When set, an insert whose (defaulted) state-field value + is outside the list is rejected server-side with `code: 'invalid_initial_state'`. + Omit it to keep the legacy behavior (no initial-state check on insert). A missing + / empty value is left to required-validation; `transitions` (UPDATE) is + unaffected. Enforced at the same `evaluateValidationRules(..., 'insert')` seam the + engine already runs after field defaults. + +- 4d5a892: feat(objectql): roll-up `summary` fields can filter which child rows they aggregate (#1868) + + `summaryOperations` gains an optional `filter` — a query `where` FilterCondition + evaluated against each child row, so a summary aggregates only the matching + children instead of the whole collection. This is what lets a single child object + feed several distinct parent totals, which the cross-object rollup templates need: + + ```typescript + // One `engagement` child → distinct filtered totals. + total_signups: { + type: 'summary', + summaryOperations: { object: 'engagement', field: 'id', function: 'count', filter: { type: 'signup' } }, + } + // Sum only received receipt lines (3-way match). + received_amount: { + type: 'summary', + summaryOperations: { object: 'procurement_receipt', field: 'amount', function: 'sum', filter: { status: 'received' } }, + } + ``` + + The engine ANDs the predicate with the parent-FK match when it recomputes, and + because the whole filtered aggregate is re-run on every child write, a child that + moves in or out of the predicate (e.g. a status change) keeps the parent current + with no extra wiring. Operator and compound forms work too + (`filter: { type: { $in: ['signup', 'trial'] }, amount: { $gte: 100 } }`). + + Purely additive: omitting `filter` aggregates every child exactly as before. + +- 16cebeb: fix(spec): drop the dead `systemFields.owner` key (#3175 follow-up) + + `ObjectSchema.systemFields` exposed an `owner?: boolean` opt-out key that nothing + read — the registry (`applySystemFields`) only consumes `systemFields.tenant` and + `systemFields.audit`, and `owner_id` provisioning is governed by the object-level + `ownership` property (`'user' | 'org' | 'none'`, made first-class in #3185). The + key was declared but wired to nothing. + + Removed it so the schema only advertises the two opt-outs it actually honors + (`tenant`, `audit`). Backward-compatible at runtime: the key was ignored before and + is stripped now (both no-ops). A TypeScript author who set `systemFields.owner` + will now see an excess-property error — the fix is to delete the key (it never did + anything) or use `ownership: 'org' | 'none'` to skip `owner_id`. Also corrected the + stale `objectql/security` doc that called `audit` "reserved" (it is active). + +- 86d30af: fix(tenancy): platform-global (`tenancy.enabled:false`) objects are never driver-org-scoped (#3249) + + An org-context read of a platform-global object (e.g. `sys_license`, ADR-0066) + could return 0 rows for an authenticated caller while an anonymous read saw the + data: the engine stamped `execCtx.tenantId` into driver options unconditionally, + and the SQL driver's tenant-field cache could be re-corrupted to + `organization_id` by a partial re-registration (lifecycle archive `syncSchema`, + schema-drift re-sync) whose schema omitted the `tenancy` block. + + - New `isTenancyDisabled(schema)` export from `@objectstack/spec/data` — the + single source of truth for the ADR-0066 platform-global posture, now shared by + the registry (tenant-column injection), the ObjectQL engine, and the SQL + driver. + - `ObjectQL.buildDriverOptions` no longer stamps `tenantId` for objects whose + registered schema declares `tenancy.enabled: false` (an explicitly-passed + options `tenantId` still wins — deliberate caller intent). + - `SqlDriver` (and `SqliteWasmDriver`) now keep a sticky record of an explicit + `tenancy.enabled:false` declaration: a later registration without a `tenancy` + block preserves the opt-out instead of re-scoping via the implicit + `organization_id` heuristic; a registration that carries a `tenancy` + declaration stays authoritative. + +- a2795f6: feat(triggers): declarative time-relative trigger — daily sweep instead of fragile date-equality (#1874) + + Time-relative business rules ("alert 60 days before a contract's `end_date`") + could only be expressed as a `record_change` flow gated on a date-equality + condition like `end_date == daysFromNow(60)`. That predicate is only evaluated + when the record _happens to change_, so it fires only if a record is edited on + exactly the threshold day — i.e. almost never, unattended. The robust + alternative was a hand-written cron + range query that every author + re-implemented (contracts `renewal_alert`, hr `document_expiring_soon`, + procurement `po_overdue`, …). + + A flow's start node can now declare a `timeRelative` descriptor instead: + + ```ts + config: { + timeRelative: { + object: 'contracts', + dateField: 'end_date', + offsetDays: [60, 30, 7], // T-minus reminders — fires on each threshold day + // — or — withinDays: 30 // "expiring soon" range; negative = overdue lookback + filter: { status: 'active' }, // optional, ANDed with the date window + }, + schedule: { type: 'cron', expression: '0 8 * * *' }, // optional; defaults to daily 08:00 UTC + } + ``` + + The new `time_relative` trigger (shipped in `@objectstack/trigger-schedule` as + `TimeRelativeTriggerPlugin`) sweeps the object on that schedule and launches the + flow **once per matching record**, with the record on the automation context — + so the start-node `condition` gate and `{record.}` interpolation work + exactly as for a record-change flow. Because the window is evaluated every day, + a threshold is never missed regardless of when the record last changed. The + discovery query runs as a system operation (RLS-bypassing) and is capped + (`maxRecords`, default 1000) so a mis-scoped window can't fan out unboundedly; + per-record failures are isolated so one bad row never aborts the sweep. + + The automation engine routes a start node carrying `config.timeRelative` to the + `time_relative` trigger (ahead of the plain `schedule` trigger, whose behavior is + unchanged), and `os validate` gains readiness checks for the new descriptor + (unknown swept object, ambiguous draft status). New authorable spec key: + `TimeRelativeTriggerSchema` (`@objectstack/spec/automation`). + +### Patch Changes + +- 22013aa: **Split the overloaded `managedBy: 'system'` bucket into engine-owned vs. admin-writable, and enforce engine-owned writes (ADR-0103, #3220).** The `system` bucket conflated two incompatible write policies: rows a platform service owns end to end (never user-written), and platform-defined schema whose rows are legitimately admin/user-writable. It carried the same all-false affordance row as `better-auth`/`append-only` but, unlike `better-auth`, had no engine enforcement — a wildcard admin could raw-write these rows through the generic data API (ADR-0049 gap). + + Rather than add a new `managedBy` enum value (which would fall through to fully-editable `platform` defaults on already-deployed Console clients), the write policy is now the **resolved affordance** (`resolveCrudAffordances` = bucket default + `userActions`), and _engine-owned_ is defined as a `system`/`append-only` object that grants no write: + + - **Writable set declares `userActions`** — the RBAC link tables (`sys_user_position`, `sys_user_permission_set`, `sys_position_permission_set`), `sys_user_preference`, `sys_approval_delegation`, and the messaging config grids (`sys_notification_preference` / `…_subscription` / `…_template`) now declare `userActions: { create, edit, delete: true }`. The affordance is a declaration only — the `DelegatedAdminGate` / RLS / permission sets remain the authz. + - **Engine-owned objects locked to reads** — `apiMethods: ['get','list']` added where absent (jobs, notifications, approval request/approver/token/action, `sys_record_share`, `sys_automation_run`, mail/settings/secret audit, the messaging delivery pipeline). `sys_secret` is explicitly read-locked (an empty `apiMethods` array fails open). + - **`sys_import_job`** stays engine-owned: the REST import route now writes its job rows `isSystem`-elevated (attribution preserved via the explicit `created_by` stamp) and the object is locked to `['get','list']`. + - **New engine write guard** (`assertEngineOwnedWriteAllowed`, plugin-security) fail-closed rejects user-context generic writes to engine-owned `system`/`append-only` objects, keyed off the resolved affordance; `isSystem` and context-less engine/service writes bypass by construction. Wired into the security middleware alongside the other data-layer gates. + - **`reconcileManagedApiMethods`** (objectql registry) now runs for **every** managed bucket, not just `better-auth`: any advertised write verb an object's resolved affordances forbid is stripped at registration with a warning (the drift backstop, ADR-0049). + - **`/me/permissions` clamp** (plugin-hono-server) now clamps `system`/`append-only` as well as `better-auth`, so the client hint reflects `permission ∩ guard`. + + **Potentially breaking:** a downstream/third-party `system` object that advertised generic write verbs relying on today's fail-open behaviour will have those verbs stripped (with a warning) and user-context generic writes to it rejected. Declare `userActions` opening the verbs the object legitimately takes from a user context. `better-auth` keeps plugin-auth's identity write guard unchanged; the row-level `managed_by` provenance vocabulary (ADR-0066) is a different axis and is untouched. + +- 3ad3dd5: Annotate the schema-only event/subscription/connector surfaces flagged by the #3197 audit with explicit "not yet enforced / not yet implemented" notes in their doc comments and `.describe()` texts, so authoring metadata against them is no longer silently swallowed. No runtime behavior or schema shape changes — documentation only. + + Surfaces annotated (each trace re-confirmed against the current tree before annotating): + + - `GraphQLSubscriptionConfigSchema` (`api/graphql.zod.ts`) — no subscription transport exists; the GraphQL HTTP entry serves query/mutation only. + - `WebSocketMessageType` + module header (`api/websocket.zod.ts`) — no WebSocket server is mounted (#2462); the protocol is a future wire contract. + - `RealtimeEventType` (`api/realtime.zod.ts`) — zero runtime importers; the engine emits `data.record.*` names (which don't match this enum's members) and nothing emits `field.changed`. + - Connector `webhooks`/`WebhookConfigSchema`/`WebhookEventSchema` and `triggers`/`ConnectorTriggerSchema` (`integration/connector.zod.ts`) — `AutomationEngine.registerConnector` reads only `actions`; webhook events and trigger definitions parse but are never dispatched or polled. + - Automation `ConnectorTriggerSchema`/`TriggerRegistrySchema` (`automation/trigger-registry.zod.ts`) — no runtime importer; the `stream` trigger mechanism exists only here. + - `NotificationChannelSchema` (`system/notification.zod.ts`) + the mirrored `NotificationChannel` contract type — implemented delivery channels are `inbox`/`email`/`sms`; `push`/`slack`/`teams`/`webhook` dead-letter, and the enum's `in-app` does not match the registered `inbox` channel id. + + The audit's sixth row (`SubscriptionEventType`, formerly `data/subscription.zod.ts`) needed no annotation — it was already removed outright by the feed-contract retirement (#1959). + +- a8aa34c: Enforce validation rules, `requiredWhen`, and per-option `visibleWhen` on multi-row updates (#3106). The bulk branch of `engine.update` (`options.multi` → `driver.updateMany`) previously never called `evaluateValidationRules`, so every object-level rule (`script`, `state_machine`, `format`, `cross_field`, `json_schema`, `conditional`), field-level `requiredWhen`, and per-option `visibleWhen` check was a silent no-op there. The engine now reads the row-scoped match set (the same AST the write binds, one query shared with the `readonlyWhen` bulk strip) and evaluates the payload against each matched row's prior state; any error-severity violation rejects the whole batch with `ValidationError` (annotated with the failing record id) before anything is written. Schemas needing no prior state (`format`/`json_schema`-only) are evaluated once against the payload with no fetch, and rule-free schemas are unaffected. Behavior change: bulk writes that previously slipped past declared rules now throw. Doc comments in `rule-validator.ts` and `validation.zod.ts` no longer overstate coverage and name the remaining `events: ['delete']` gap (tracked separately). +- a3823b2: Collapse the hook event taxonomy from 18 declared events to the 8 the engine actually dispatches (#3195). The removed 10 (`beforeFindOne`/`afterFindOne`, `beforeCount`/`afterCount`, `beforeAggregate`/`afterAggregate`, `beforeUpdateMany`/`afterUpdateMany`, `beforeDeleteMany`/`afterDeleteMany`) were declared in `HookEvent` but never fired — the enum mirrored the engine method table instead of domain events, so a hook subscribing to them registered fine and then silently no-op'd. + + - `findOne` now fires the same `beforeFind`/`afterFind` hooks as `find` — the read event attaches to record materialization, not the engine method, so one subscription covers every read shape (no separate `beforeFindOne`/`afterFindOne`). + - Bulk (`multi: true`) updates/deletes already fire the singular `beforeUpdate`/`beforeDelete`/`afterUpdate`/`afterDelete` events with the row-scoping predicate in `ctx.input.ast`; this is now documented, and there is no `*Many` event. + - Read authorization / row filtering is the RLS/permission-rule layer's job and field masking is field-level metadata — neither is a hook every author must re-attach. + - `engine.registerHook` now warns when a hook subscribes to an event the engine never dispatches, so enum-vs-dispatch drift can't recur silently. + + No shipped hook or authored metadata used any of the removed events; authoring one now fails loudly at parse/validate time instead of registering a dead hook. Skills and docs updated to teach the 8 events and the declarative alternatives. + +- 5e3301d: Document two validation-rule facts surfaced by the 2026-06 liveness audit (follow-up to #3106 / #3184), and clean up a stale form-schema mirror — no runtime behavior change: + + - `label` / `description` / `tags` on validation rules are governance / editor metadata (surfaced to the Studio rule editor and rule listings), not evaluated on the write path. Documented as such on `BaseValidationSchema` rather than removed — they are set by nearly every example rule and feed the `/meta/types` editor form, so they are declared on purpose, not silent no-ops. + - `cross_field` evaluates identically to `script` (same CEL predicate path); only `fields[0]` is read, to target the violation at a field. Documented the overlap on the schema, its `fields` `.describe()`, and the validation docs so authors can choose between them; the variant is kept for the field-targeting affordance and backward compatibility. + - Removed dead form-field entries (`scope`, `caseSensitive`, `url`, `handler`) and the stale `type=unique` hint from the hand-written `HAND_CRAFTED_SCHEMAS['validation']` fallback in `@objectstack/metadata-protocol` — leftovers from the removed `unique`/`async`/`custom` variants. + - Added the missing `beforeDelete` lifecycle-hook pointer to the validation docs' "not a rule type" callout, so delete-time guards aren't stranded now that validation has no `delete` event (#3184). + +- 46e876c: fix(spec): declare `summaryOperations` sub-fields in the Field metadata form (#3257) + + `fieldForm` (the registered metadata form for editing a Field) previously + declared `summaryOperations` as a bare `composite` with no sub-fields, so a + protocol-driven renderer had to fall back to a raw JSON editor. It now declares + the inner shape explicitly — `object` (`ref:object`), `function` (select), + `field`, `relationshipField`, and `filter` (bound to `widget: 'filter-condition'`) + — mirroring the `summaryOperations` Zod schema and surfacing the roll-up `filter` + added in #1868. Also gates the block to `data.type == 'summary'`. + + Small step toward #3257 (making the Studio field designer metadata-driven rather + than hand-coded); the live objectui inspector already edits these fields. + +- 158aa14: feat(automation): mark the loop `collection` config field as an interpolate() template so designer forms render it correctly (#3304) + + The flow designer generates a node's config form from its published + `configSchema` (ADR-0018). A string property can now carry an `xExpression: +'expression' | 'template'` marker — riding the same Zod `.meta()` → JSON-Schema + channel as `xRef` / `xEnumDeprecated` — that declares whether the string is bare + CEL or an `interpolate()` single-brace `{var}` template. + + The `loop` node's `collection` (e.g. `{tasks}`) is a template, so it is now + marked `xExpression: 'template'` on both the canonical `LoopConfigSchema` and the + shipped descriptor's `configSchema` literal (service-automation loop-node). + Without the marker the designer rendered `collection` as plain text online while + the offline hardcoded form rendered it as a mono expression editor, and the CEL + brace-trap false-flagged `{tasks}` as a malformed condition. The marker closes + that divergence — objectui #2670 Phase 3 (#2699) already consumes it. + + Additive and backward-compatible: an unknown `xExpression` value is ignored by + the designer, and runtime behavior is unchanged. Filling the same marker in on + the remaining node types (map/decision/script and the node types that publish no + `configSchema` yet) is tracked as follow-up in #3304. + +- d2723e2: **`MetadataManager.register()` / `unregister()` now announce to `subscribe()` watchers.** Both updated the registry, persisted to writable loaders and published to realtime, but never fired the watch callbacks — so `subscribe()` looked like it covered every write while silently missing all of them. Only the `saveMetaItem` path (via the repository watch stream) and the filesystem watcher ever reached a subscriber. Runtime consumers that cache metadata — notably ObjectQL's SchemaRegistry bridge, the component that decides what is queryable — went stale on every other write until the process restarted. + + Announcing is now the **default**, so a new call site is correct without knowing this contract exists. This is a contract fix rather than a bug fix: the one live behavior change is that runtime datasource writes (`datasource-admin`) now reach the HMR SSE stream, which subscribes to every registered type. `unregisterPackage()` / `bulkUnregister()` also announce their deletes now — correct, but latent, since neither has a production caller today. + + Bulk ingest opts out explicitly with the new `MetadataWriteOptions` (`{ notify: false }`) — boot-time filesystem priming, artifact ingest, and ObjectQL's registry bridge, each of which either runs before consumers cache anything or announces the whole batch once (as the artifact reload path does via `metadata:reloaded`). The bridge in particular MUST stay silent: it copies objects out of the SchemaRegistry, and announcing would feed them back through a handler that re-registers under `_packageId ?? 'metadata-service'`, overwriting the true package provenance of every object whose body carries no `_packageId`. + + Additive only — `register(type, name, data)` and `unregister(type, name)` keep working unchanged. + + Fixes #3112. + +- beaf2de: fix(metadata-protocol): strip static `readonly` on INSERT at the data-write ingress (#3043) + + #2948/#3003 made static `readonly: true` fields server-enforced on UPDATE (a + non-system PATCH forging `approval_status: 'approved'` is silently stripped in + the engine), but INSERT was exempt. For approval/status/verdict columns that + exemption was the _shorter_ attack: instead of the #3003 draft-then-PATCH move, a + non-system caller could `POST` a record already `approval_status: 'approved'` in + one step — and the UPDATE-only strip never reached it. + + The strip now also runs on INSERT, but at the **external data-write ingress** + (`DataProtocol.createData` / `createManyData` / `batchData` / `cloneData`) rather + than in the engine. That seam is the single point every external programmatic + create funnels through — the REST CRUD route, the GraphQL/MCP dispatcher + (`bridge.create` → `callData` → `createData`), and bulk import — while **trusted + internal writers** (better-auth's adapter, the metadata repository, the seed + loader) call `engine.insert` directly and bypass it. Enforcing at the ingress + protects every caller/agent path at once without stripping the internal writers + that legitimately seed read-only columns on create (identity provisioning, + provenance stamps, event-log cursors) — the blast radius an engine-level insert + strip would have. + + - **Caller-forged only, at the ingress.** The payload here is raw caller input + (the security middleware stamps `owner_id` / `organization_id` later, inside + `engine.insert`), so only keys the caller actually sent are dropped; server + stamps are added afterwards and are unaffected. + - **Re-derives the default.** A stripped field falls back to its declared + `defaultValue` in the engine (a forged `approval_status` becomes `draft`, not + NULL). + - **System-context exempt.** `isSystem` writes still seed read-only columns. + - **Silent** (HTTP 2xx), per-row on batch/import. `readonlyWhen` stays + INSERT-exempt (a conditional lock needs a prior record). + - **Author-defined business objects only.** Platform objects (`managedBy` set, + or the `sys_` namespace) carry their own field-write governance that a silent + strip must not pre-empt — e.g. ADR-0086 REJECTS (403) a forged + `managed_by:'package'` on `sys_permission_set`, and #3004 rejects a forged + `owner_id`; several of those columns are `readonly`, so stripping them here + would swallow the payload the guard is meant to reject. The #3043 threat is app + approval/status fields, never `sys_` — the same boundary `applySystemFields` + uses for ownership. + + Behavior change: a non-system create through the data API (REST / GraphQL / MCP / + import) can no longer seed a `readonly` column from the payload. Flows that + legitimately write read-only columns at creation must run with a system context + (`isSystem`), the same requirement the UPDATE strip already imposes. + +- e0859b1: fix(formula): retire the `js` expression dialect and fix the `hasDialect` false-positive (#3278) + + The `js` **expression** dialect was declared in `ExpressionDialect` but never + shipped — it existed only as a registry stub with no engine and no author helper + (`cel`/`F`/`P` → CEL, `tmpl` → template, `cron` → cron; nothing ever emitted + `js`). Per ADR-0049 (enforce-or-remove) it is removed from the enum; the set is + now `{cel, cron, template}`. + + Procedural JavaScript is unaffected: it remains the **L2** authoring surface — + the sandboxed, capability-gated `ScriptBody { language: 'js' }` in hook/action + bodies — which is a separate enum (`hook-body.zod.ts`), not an expression + dialect. + + Also fixes a latent bug in `hasDialect`: it detected stubs via + `dialect.startsWith('stub:')`, but stubs were registered under their real name, + so the check was dead code and `hasDialect('js')` returned a false-positive + `true`. With the stub removed, `hasDialect` reports only registered real + engines, and the registry test now asserts the negative case (`hasDialect('js') +=== false`) so the gate can actually go red. + + No runtime behavior changes for any valid persisted artifact — no producer ever + emitted `dialect: 'js'`. See the ADR-0058 addendum. + +- 8923843: Reject view containers that define no views. A flat list-view object (`{ name, label, type, columns, ... }`) parses to an empty `ViewSchema` container because Zod strips unknown keys — zero views register and the Console silently renders nothing. `defineView()` now throws on a zero-view container, and `os validate` gains a `view-container-shape` check (`validateViewContainers` in `@objectstack/lint`) that reports flat or empty `views: []` entries pre-parse with a wrap-it fix hint. +- f16b492: Remove the dead `'delete'` member from the validation-rule `events` enum (#3184). The rule evaluator only runs on the insert/update write path — `engine.delete` never invokes it — so a rule declaring `events: ['delete']` was a silent no-op (flagged in #3106 and `docs/audits/2026-06-validationschema-property-liveness.md`). The enum now admits only `insert`/`update`; guard deletions with a `beforeDelete` lifecycle hook instead. No shipped metadata declares `events: ['delete']`; any off-spec metadata that did now fails loudly at `os validate` / registration rather than parsing and doing nothing. Also narrows the two hand-written mirrors (`rule-validator.ts` `BaseRule`, `metadata-protocol` JSON-schema form helper — whose stale `type` enum listing removed `unique`/`async`/`custom` variants is corrected in the same pass), updates the doc comments, the published data skill, and the hand-written validation doc. +- 4b6fde8: Trim the dead `undelete` and `api` webhook triggers (#3196). `WebhookTriggerType` declared five triggers but only three ever fired: + + - `undelete` had no event source — the engine has no soft-delete/restore capability (`delete` is a hard delete; no `deleted_at` convention, no restore operation, and `data.record.undeleted` is never emitted). The `undeleted` case in the auto-enqueuer's action mapper was dead code awaiting a producer that doesn't exist. + - `api` ("manually triggered") had no fire path — the only webhook HTTP surface re-queues already-failed deliveries; nothing originates a manual fire. + + Both are removed from the enum (contract-first, matching #3184/#3195): authoring a webhook on a removed trigger now fails loudly at `os validate` / registration instead of registering a webhook that silently never fires. No shipped webhook metadata used either. The auto-enqueuer now also warns when a persisted `sys_webhook` row carries a trigger it can't map to an emitted record event (a drift-guard, so a dead trigger can't silently no-op again). Reintroduce `undelete` only alongside a real restore subsystem, and `api` only alongside a real manual-fire endpoint. Updated the `sys_webhook` trigger options, field help (all locales), docs, and reference; added rejection tests. + +- 2018df9: **Unify the developer-facing org identifier in JS hooks — `organizationId` is now the blessed name; `session.tenantId` becomes a deprecated alias (#3280).** The caller's active organization was surfaced to hook authors as `ctx.session.tenantId`, while everything else on the developer surface — the `organization_id` column, `current_user.organizationId` in RLS/sharing, and seed rows — already said `organization`. A hook author had to internalize the hidden equation `tenantId === organizationId` to move between surfaces. This is additive and non-breaking: + + - **`ctx.session.organizationId`** is added as the blessed name; **`ctx.session.tenantId`** still carries the identical value but is marked `@deprecated` in its TSDoc. Both come from the same resolved `ExecutionContext.tenantId` (which the kernel derives from `session.activeOrganizationId`). + - **`ctx.user.organizationId`** is added to the ergonomic `user` shortcut, so a hook that needs "the current org to filter by" writes `ctx.user.organizationId` with zero relearning — matching `current_user.organizationId` (RLS) and the `organization_id` column. The engine now populates `ctx.user` (`{ id, email?, organizationId? }`) at every hook event that already carries a `session`; it stays `undefined` for system / unauthenticated writes. + + **No behavior change and no breaking rename.** The generic driver-layer tenancy abstraction (`ExecutionContext.tenantId`, `DriverOptions.tenantId`, `SqlDriver.applyTenantScope`, `TenancyConfig.tenantField`) is deliberately untouched — that layer's isolation column is configurable and legitimately carries an _environment_ id in per-environment (database-per-tenant) kernels. Hook-authoring docs now teach `organizationId` and distinguish the two isolation axes: **org row-scoping** (`organization_id`, shared DB) vs **environment / database-per-tenant** (`service-tenant`, `driver-turso`). Community edition never populates an org, so `organizationId` is `undefined` there. + +- fc5a3a2: **The `view` metadata type-schema now validates all three runtime `view` shapes instead of stripping two of them to `{}`.** `metadata-type-schemas.ts` mapped `view` to the aggregate container `ViewSchema` (`{ list, form, listViews, formViews }`, every slot optional). Zod strips unknown keys, so the two non-container shapes a `view` body actually carries at runtime — a standalone **ViewItem record** (`{ name, object, viewKind, config }`) and a **console personalization overlay** (raw view config + identity inherited by `normalizeViewMetadata`, #2555) — both strip-parsed to `{}`. That made the `422` check in `saveMetaItem` and read-time `computeMetadataDiagnostics` a **no-op** for those shapes: a broken `config` (e.g. a kanban missing `groupByField`) saved with a false `200` and badged valid, and the view create-seed test validated against nothing. + + `view` now maps to a new `ViewMetadataSchema` — a union over the three shapes, each validated genuinely: + + 1. **defineView container** — non-empty (`ViewSchema` refined to require at least one of `list`/`form`/`listViews`/`formViews`; an empty container is rejected, mirroring `defineView`). + 2. **ViewItem record** — `ViewItemSchema`; the nested `config` is validated against ListView/FormView. + 3. **Flattened personalization overlay** — inline ListView/FormView config plus optional identity fields. Structural guards pin `config`/`list`/`form`/`listViews`/`formViews` to `undefined` so a malformed record or container can never be rescued through this lenient branch with its real payload silently stripped. + + All members strip-parse (no `.strict()`), so auxiliary Studio round-trip keys (`isPinned`, `sortOrder`, …) still ride along without a false `422`, and `saveMetaItem` keeps persisting the body verbatim. `z.toJSONSchema()` emits the schema as an `anyOf` of the four members, which `/api/v1/meta/types/view` serves to Studio's SchemaForm. + + Fixes #3095. + +- 8ff9210: fix(spec): enforce the `ViewFilterRule` operator enum with legacy-alias + normalization (#3373) + + `ViewFilterRule.operator` was previously an open string, so views could persist + operators the runtime cannot evaluate. The Zod schema now constrains it to the + supported operator enum and normalizes the known legacy aliases to their + canonical form on parse. This is a public spec/api-surface change + (`packages/spec/api-surface.json`) that landed on `main` in #3373 without a + changeset; this backfills it so the fix ships in the next release instead of + being silently stranded. + ## 16.0.0-rc.1 ### Minor Changes diff --git a/packages/spec/package.json b/packages/spec/package.json index 9df62e44fd..09097fa2d9 100644 --- a/packages/spec/package.json +++ b/packages/spec/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/spec", - "version": "16.0.0-rc.1", + "version": "16.0.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 bc5c97a17c..d983fe5169 100644 --- a/packages/triggers/trigger-api/CHANGELOG.md +++ b/packages/triggers/trigger-api/CHANGELOG.md @@ -1,5 +1,54 @@ # @objectstack/trigger-api +## 16.0.0 + +### Patch Changes + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [e057f42] +- Updated dependencies [a3823b2] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [dd9f223] +- Updated dependencies [46e876c] +- Updated dependencies [5f05de2] +- Updated dependencies [021ba4c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [290e2f0] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + - @objectstack/core@16.0.0 + ## 16.0.0-rc.1 ### Patch Changes diff --git a/packages/triggers/trigger-api/package.json b/packages/triggers/trigger-api/package.json index cd694c0b32..ed9ea9fc7b 100644 --- a/packages/triggers/trigger-api/package.json +++ b/packages/triggers/trigger-api/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/trigger-api", - "version": "16.0.0-rc.1", + "version": "16.0.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 9f56416a57..d969a95125 100644 --- a/packages/triggers/trigger-record-change/CHANGELOG.md +++ b/packages/triggers/trigger-record-change/CHANGELOG.md @@ -1,5 +1,92 @@ # @objectstack/plugin-trigger-record-change +## 16.0.0 + +### Minor Changes + +- 2ea08ee: Flow trigger observability — kill the four-layer silence around record-change flows that never fire (2026-07-17 third-party eval). + + A misauthored auto-launched flow (wrong `objectName`, missing `requires: ['automation','triggers']`, failing start condition) produced ZERO output at every layer: the engine's own registration/binding logs land inside the CLI's boot-quiet stdout window (which swallows debug/info/warn — only error/fatal reach stderr), and each "didn't happen" path was itself silent. Fixes: + + - **Startup banner `Flows:` section** (`os serve`/`os dev`/`os start`): flow count, bound-to-trigger count, registered trigger types, draft count — plus loud `⚠` lines for flows declared with no automation engine enabled (`requires` missing), flows whose trigger type has no registered trigger, and bound record-change flows targeting an unknown object (dead binding). Printed after stdout is restored, so it is immune to the boot-quiet window. + - **Trigger-fired run failures now log at ERROR** (stderr — always visible): the automation engine no longer drops the AutomationResult of a trigger-fired execution; condition-evaluation faults and node failures surface with the flow name. Condition-not-met skips stay at debug (high-frequency, intentional). + - **`RecordChangeTrigger` probes object existence at bind time** and warns when a flow's `objectName` matches no registered object (exact-name matching), instead of silently arming a hook that can never fire. + - **`kernel:bootstrapped` binding audit** in the automation plugin: warns per enabled-but-unbound triggered flow with the reason, and reports registered/bound/draft counts (`AutomationEngine.getTriggerBindingAudit()`, extended `getFlowRuntimeStates()` with `status`/`triggerType`/`object`). + - **`os validate` flow-wiring advisories** (`@objectstack/lint` `validateFlowTriggerReadiness`): warns when a record-triggered flow targets an object the stack does not define, and when an auto-triggered flow's status is `draft` (authored or defaulted — draft flows still fire; declare `active` or `obsolete`). + - Removed leftover boot-debug writes (`registerApp`/`AppPlugin`/`StandaloneStack`/`AuditPlugin` stderr noise) that previous debugging of this same silence had left behind. + +### Patch Changes + +- 6c270a6: **BREAKING: remove the deprecated `ctx.session.tenantId` / `ctx.user.tenantId` alias from the hook & action authoring surface — converge on `organizationId` (#3290).** + + #3280 made `organizationId` the blessed developer-facing name for the caller's active org across the JS authoring surface and kept `tenantId` as a `@deprecated` alias carrying the identical value. That alias is now **removed** from the hook `ctx.session`, the action-body `ctx.session`, and the action-body `ctx.user`. Read the caller's active org under the single blessed name: + + ```diff + - const org = ctx.session.tenantId; // hook or action body + + const org = ctx.user?.organizationId ?? ctx.session?.organizationId; + ``` + + **FROM → TO migration** (in any `*.hook.ts` / `*.action.ts` body): + + - `ctx.session.tenantId` → `ctx.session.organizationId` + - `ctx.user.tenantId` (action body) → `ctx.user.organizationId` + + The value is unchanged — `organizationId` is the same active-org id, matching the `organization_id` column and `current_user.organizationId` in RLS/sharing. `ctx.user` is `undefined` for system / unauthenticated writes, so read `ctx.session?.organizationId` when a hook or action must work regardless of a resolved user. + + What changed internally: + + - **`@objectstack/spec`** — `HookContextSchema.session` drops the `tenantId` field (only `organizationId` remains). A stray `tenantId` on a constructed session is now stripped by the schema. + - **`@objectstack/objectql`** — the engine's `buildSession()` no longer emits `session.tenantId`; the audit-stamp plugin sources the `tenant_id` column from `session.organizationId`. + - **`@objectstack/runtime`** — `buildActionSession()` and the REST action `ctx.user` no longer emit `tenantId`. + - **`@objectstack/trigger-record-change`** — reads `session.organizationId` (was `session.tenantId`) when forwarding the writer's org to a `runAs:'user'` flow; behavior is identical. + + **Explicit non-goal (unchanged):** the generic **driver-layer** tenancy abstraction is _not_ touched — `ExecutionContext.tenantId`, `DriverOptions.tenantId`, `SqlDriver.applyTenantScope` / `TenancyConfig.tenantField`, and `ExecutionLog.tenantId`. That isolation column is configurable and legitimately carries an _environment_ id in database-per-tenant kernels; it is a distinct axis from the developer-facing org. The build-time `check:org-identifier` guard now also covers `packages/**` to keep reference bodies off the removed name. + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [e057f42] +- Updated dependencies [a3823b2] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [dd9f223] +- Updated dependencies [46e876c] +- Updated dependencies [5f05de2] +- Updated dependencies [021ba4c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [290e2f0] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + - @objectstack/core@16.0.0 + ## 16.0.0-rc.1 ### Patch Changes diff --git a/packages/triggers/trigger-record-change/package.json b/packages/triggers/trigger-record-change/package.json index a2291d4f37..e667230bd5 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": "16.0.0-rc.1", + "version": "16.0.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 892816292a..28d6a8d802 100644 --- a/packages/triggers/trigger-schedule/CHANGELOG.md +++ b/packages/triggers/trigger-schedule/CHANGELOG.md @@ -1,5 +1,98 @@ # @objectstack/plugin-trigger-schedule +## 16.0.0 + +### Minor Changes + +- a2795f6: feat(triggers): declarative time-relative trigger — daily sweep instead of fragile date-equality (#1874) + + Time-relative business rules ("alert 60 days before a contract's `end_date`") + could only be expressed as a `record_change` flow gated on a date-equality + condition like `end_date == daysFromNow(60)`. That predicate is only evaluated + when the record _happens to change_, so it fires only if a record is edited on + exactly the threshold day — i.e. almost never, unattended. The robust + alternative was a hand-written cron + range query that every author + re-implemented (contracts `renewal_alert`, hr `document_expiring_soon`, + procurement `po_overdue`, …). + + A flow's start node can now declare a `timeRelative` descriptor instead: + + ```ts + config: { + timeRelative: { + object: 'contracts', + dateField: 'end_date', + offsetDays: [60, 30, 7], // T-minus reminders — fires on each threshold day + // — or — withinDays: 30 // "expiring soon" range; negative = overdue lookback + filter: { status: 'active' }, // optional, ANDed with the date window + }, + schedule: { type: 'cron', expression: '0 8 * * *' }, // optional; defaults to daily 08:00 UTC + } + ``` + + The new `time_relative` trigger (shipped in `@objectstack/trigger-schedule` as + `TimeRelativeTriggerPlugin`) sweeps the object on that schedule and launches the + flow **once per matching record**, with the record on the automation context — + so the start-node `condition` gate and `{record.}` interpolation work + exactly as for a record-change flow. Because the window is evaluated every day, + a threshold is never missed regardless of when the record last changed. The + discovery query runs as a system operation (RLS-bypassing) and is capped + (`maxRecords`, default 1000) so a mis-scoped window can't fan out unboundedly; + per-record failures are isolated so one bad row never aborts the sweep. + + The automation engine routes a start node carrying `config.timeRelative` to the + `time_relative` trigger (ahead of the plain `schedule` trigger, whose behavior is + unchanged), and `os validate` gains readiness checks for the new descriptor + (unknown swept object, ambiguous draft status). New authorable spec key: + `TimeRelativeTriggerSchema` (`@objectstack/spec/automation`). + +### Patch Changes + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [e057f42] +- Updated dependencies [a3823b2] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [dd9f223] +- Updated dependencies [46e876c] +- Updated dependencies [5f05de2] +- Updated dependencies [021ba4c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [290e2f0] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + - @objectstack/core@16.0.0 + ## 16.0.0-rc.1 ### Patch Changes diff --git a/packages/triggers/trigger-schedule/package.json b/packages/triggers/trigger-schedule/package.json index b0425e08b5..5797a47366 100644 --- a/packages/triggers/trigger-schedule/package.json +++ b/packages/triggers/trigger-schedule/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/trigger-schedule", - "version": "16.0.0-rc.1", + "version": "16.0.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 cecdabc2ad..1867f0f4d3 100644 --- a/packages/types/CHANGELOG.md +++ b/packages/types/CHANGELOG.md @@ -1,5 +1,137 @@ # @objectstack/types +## 16.0.0 + +### Minor Changes + +- 83e8f7d: feat(mcp): decouple the stdio auto-start switch from the HTTP surface + surface the MCP endpoint on `os dev` boot (#3167) + + The MCP HTTP surface (`/api/v1/mcp`) and the long-lived stdio transport used to + share one env var: `OS_MCP_SERVER_ENABLED=true` turned the HTTP surface on **and** + silently auto-started the stdio transport — which bridges the raw metadata service + + - data engine with no per-request principal (unscoped). An operator setting it to + "make sure MCP is on" got an unscoped transport as a side effect. + + * **`@objectstack/types`** — new `resolveMcpStdioAutoStart()`. Stdio auto-start is + now its own switch, `OS_MCP_STDIO_ENABLED` (default off); `OS_MCP_SERVER_ENABLED` + governs only the HTTP surface. The legacy `OS_MCP_SERVER_ENABLED=true` trigger + still starts stdio for one release, flagged as deprecated. `=false` is unchanged + (it only ever gated HTTP). + * **`@objectstack/mcp`** — `MCPServerPlugin.start()` gates stdio on the new switch + and logs a one-time deprecation warning when started via the legacy alias. + * **`@objectstack/cli`** — `os dev` now prints the MCP endpoint, the agent-skill + URL, and a ready-to-paste `claude mcp add` command on boot (gated on the HTTP + surface being on), so the "an agent operates the app it's building" loop is + discoverable at dev time. + * **`create-objectstack`** — the blank scaffold README documents that the app is + itself an MCP server (the serve side), distinct from the consume-side connector. + +- 92f5f19: feat(runtime): sandbox budget is script CPU-time, not wall clock (ADR-0102 D1, #3295) + + The QuickJS sandbox now meters each hook/action invocation against how much + **VM-active (CPU) time** the body burns, not wall clock. Idle host-await time and + a nested hook's own execution (which runs host-side while the caller's VM is + parked) are no longer charged to the caller — so a slow/loaded host or a deep + nested-write chain can't trip the budget while a script is merely waiting (the + root cause of the #3259 CI flake). A separate, generous **wall-clock ceiling** + (default 30s, `max(ceiling, cpuBudget)`) remains as the backstop for a body stuck + on a host call that never settles. + + What changes for consumers (behaviour, not API signatures): + + - **Meaning of the timeout knobs.** `body.timeoutMs`, the `hookTimeoutMs` / + `actionTimeoutMs` runner options, and `OS_SANDBOX_HOOK_TIMEOUT_MS` / + `OS_SANDBOX_ACTION_TIMEOUT_MS` keep their **names, defaults (250ms / 5000ms), + and precedence** — but now bound CPU-time instead of wall-clock. In practice + this only _loosens_ legitimate slow/nested work; a runaway synchronous script + is still cut at the same budget. + - **Error messages.** `exceeded timeout of Nms` → either `exceeded CPU budget of +Nms` (script burned its CPU budget) or `exceeded wall-clock ceiling of Nms +while awaiting host calls` (stuck on a never-settling host call). Update any + code/tests matching the old string. + + New knobs (additive): + + - `QuickJSScriptRunner` option `wallCeilingMs` and env `OS_SANDBOX_WALL_CEILING_MS` + — tune the wall ceiling (explicit option › env › 30s). + - `resolveSandboxTimeoutMs` (`@objectstack/types`) gains a `'wallCeiling'` kind. + + Also fixes a latent init bug in the new accounting where the interrupt handler + could fire during `installCtx` and corrupt ctx marshalling. The nested-write + integration suites now run at the stock 250ms budget (previously forced to 10s), + which is itself the regression guard for the nested-charging fix. + +- 32899e6: feat(runtime): env-overridable sandbox hook/action timeout default (#3259) + + The QuickJS sandbox enforces a wall-clock deadline on every hook/action + invocation (250ms hooks / 5000ms actions). Each invocation compiles a fresh + WASM module, and a nested hook compiles ANOTHER one inside the parent's budget, + so on a heavily loaded or slow host — an oversubscribed CI runner, constrained + production hardware — that fixed VM-creation cost alone can trip the hook + default even while the VM is still making progress. On CI this surfaced as an + intermittent `hook '…' exceeded timeout of 250ms` flake on PRs that never + touched the sandbox path. + + The per-invocation timeout DEFAULT is now resolvable from the environment via + `resolveSandboxTimeoutMs` (`@objectstack/types`), which `QuickJSScriptRunner` + consults, so an operator can raise the floor once, deployment-wide, instead of + re-tuning every call site: + + - `OS_SANDBOX_HOOK_TIMEOUT_MS` — default hook budget (ms) + - `OS_SANDBOX_ACTION_TIMEOUT_MS` — default action budget (ms) + + Precedence is unchanged: an explicit `hookTimeoutMs` / `actionTimeoutMs` passed + to the runner still wins over the env var, and a body's own declared `timeoutMs` + still wins over the resolved default (the smaller of the explicit values). Only + a positive integer is honored; unset / empty / non-numeric / non-positive keeps + the built-in 250ms / 5000ms defaults, so behaviour is byte-for-byte unchanged + when the vars are absent — production is unaffected unless it opts in. + + CI's Test Core now sets `OS_SANDBOX_HOOK_TIMEOUT_MS=10000` so the shared-runner + load flake can't recur; genuine hangs stay bounded by each test's own timeout. + +### Patch Changes + +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [22013aa] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [a8aa34c] +- Updated dependencies [a3823b2] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [46e876c] +- Updated dependencies [158aa14] +- Updated dependencies [62a2117] +- Updated dependencies [d2723e2] +- Updated dependencies [fefcd54] +- Updated dependencies [beaf2de] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/spec@16.0.0 + ## 16.0.0-rc.1 ### Patch Changes diff --git a/packages/types/package.json b/packages/types/package.json index 202ed89421..d94423faf0 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/types", - "version": "16.0.0-rc.1", + "version": "16.0.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 fe93f7c963..be90a15000 100644 --- a/packages/verify/CHANGELOG.md +++ b/packages/verify/CHANGELOG.md @@ -1,5 +1,93 @@ # @objectstack/verify +## 16.0.0 + +### Patch Changes + +- Updated dependencies [b39c65d] +- Updated dependencies [f972574] +- Updated dependencies [6289ec3] +- Updated dependencies [2f3c641] +- Updated dependencies [e38da5b] +- Updated dependencies [f9b118d] +- Updated dependencies [22013aa] +- Updated dependencies [a9459e6] +- Updated dependencies [3ad3dd5] +- Updated dependencies [8efa395] +- Updated dependencies [3a18b60] +- Updated dependencies [02eafa5] +- Updated dependencies [deb7e7e] +- Updated dependencies [a8aa34c] +- Updated dependencies [e057f42] +- Updated dependencies [a3823b2] +- Updated dependencies [43a3efb] +- Updated dependencies [524696a] +- Updated dependencies [fdc244e] +- Updated dependencies [bfa3c3f] +- Updated dependencies [5e3301d] +- Updated dependencies [dd9f223] +- Updated dependencies [46e876c] +- Updated dependencies [780b4b5] +- Updated dependencies [2ea08ee] +- Updated dependencies [d1d1c40] +- Updated dependencies [616e839] +- Updated dependencies [b320158] +- Updated dependencies [ee0a499] +- Updated dependencies [5f05de2] +- Updated dependencies [021ba4c] +- Updated dependencies [158aa14] +- Updated dependencies [9d897b3] +- Updated dependencies [62a2117] +- Updated dependencies [f8c1b69] +- Updated dependencies [d2723e2] +- Updated dependencies [674457a] +- Updated dependencies [fefcd54] +- Updated dependencies [efbcfe1] +- Updated dependencies [2049b6a] +- Updated dependencies [beaf2de] +- Updated dependencies [1e145eb] +- Updated dependencies [369eb6e] +- Updated dependencies [06ff734] +- Updated dependencies [b659111] +- Updated dependencies [5754a23] +- Updated dependencies [6c270a6] +- Updated dependencies [290e2f0] +- Updated dependencies [668dd17] +- Updated dependencies [8abf133] +- Updated dependencies [e0859b1] +- Updated dependencies [92f5f19] +- Updated dependencies [a2d6555] +- Updated dependencies [3a6310c] +- Updated dependencies [32899e6] +- Updated dependencies [515f11a] +- Updated dependencies [4174a07] +- Updated dependencies [ce468c8] +- Updated dependencies [04ecd4e] +- Updated dependencies [4d5a892] +- Updated dependencies [16cebeb] +- Updated dependencies [86d30af] +- Updated dependencies [8923843] +- Updated dependencies [a2795f6] +- Updated dependencies [f16b492] +- Updated dependencies [4b6fde8] +- Updated dependencies [2018df9] +- Updated dependencies [fc5a3a2] +- Updated dependencies [8ff9210] + - @objectstack/runtime@16.0.0 + - @objectstack/spec@16.0.0 + - @objectstack/plugin-security@16.0.0 + - @objectstack/objectql@16.0.0 + - @objectstack/plugin-hono-server@16.0.0 + - @objectstack/service-automation@16.0.0 + - @objectstack/plugin-sharing@16.0.0 + - @objectstack/rest@16.0.0 + - @objectstack/service-analytics@16.0.0 + - @objectstack/plugin-auth@16.0.0 + - @objectstack/core@16.0.0 + - @objectstack/driver-sqlite-wasm@16.0.0 + - @objectstack/service-datasource@16.0.0 + - @objectstack/service-settings@16.0.0 + ## 16.0.0-rc.1 ### Patch Changes diff --git a/packages/verify/package.json b/packages/verify/package.json index 2ac6e71b55..2682cb77f2 100644 --- a/packages/verify/package.json +++ b/packages/verify/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/verify", - "version": "16.0.0-rc.1", + "version": "16.0.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 664a6e8721..407a59fc0b 100644 --- a/packages/vscode-objectstack/CHANGELOG.md +++ b/packages/vscode-objectstack/CHANGELOG.md @@ -1,5 +1,7 @@ # objectstack-vscode +## 16.0.0 + ## 16.0.0-rc.1 ## 16.0.0-rc.0 diff --git a/packages/vscode-objectstack/package.json b/packages/vscode-objectstack/package.json index 7579a932f1..3369d47fdb 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": "16.0.0-rc.1", + "version": "16.0.0", "publisher": "objectstack", "license": "Apache-2.0", "repository": {