diff --git a/.changeset/22161-s18-data-model-fix-lines.md b/.changeset/pre/22161-s18-data-model-fix-lines.md similarity index 100% rename from .changeset/22161-s18-data-model-fix-lines.md rename to .changeset/pre/22161-s18-data-model-fix-lines.md diff --git a/.changeset/22161-s19-five-file-fix-lines.md b/.changeset/pre/22161-s19-five-file-fix-lines.md similarity index 100% rename from .changeset/22161-s19-five-file-fix-lines.md rename to .changeset/pre/22161-s19-five-file-fix-lines.md diff --git a/.changeset/22161-s20-flow-patterns-fix-lines.md b/.changeset/pre/22161-s20-flow-patterns-fix-lines.md similarity index 100% rename from .changeset/22161-s20-flow-patterns-fix-lines.md rename to .changeset/pre/22161-s20-flow-patterns-fix-lines.md diff --git a/.changeset/22269-print-render-service.md b/.changeset/pre/22269-print-render-service.md similarity index 100% rename from .changeset/22269-print-render-service.md rename to .changeset/pre/22269-print-render-service.md diff --git a/.changeset/23012-lookup-snake-tolerance-text.md b/.changeset/pre/23012-lookup-snake-tolerance-text.md similarity index 100% rename from .changeset/23012-lookup-snake-tolerance-text.md rename to .changeset/pre/23012-lookup-snake-tolerance-text.md diff --git a/.changeset/23050-s11-form-liveness-citations.md b/.changeset/pre/23050-s11-form-liveness-citations.md similarity index 100% rename from .changeset/23050-s11-form-liveness-citations.md rename to .changeset/pre/23050-s11-form-liveness-citations.md diff --git a/.changeset/23216-routes-retire-text.md b/.changeset/pre/23216-routes-retire-text.md similarity index 100% rename from .changeset/23216-routes-retire-text.md rename to .changeset/pre/23216-routes-retire-text.md diff --git a/.changeset/23385-walled-accepted-defaults.md b/.changeset/pre/23385-walled-accepted-defaults.md similarity index 100% rename from .changeset/23385-walled-accepted-defaults.md rename to .changeset/pre/23385-walled-accepted-defaults.md diff --git a/.changeset/23394-branding-logo-live.md b/.changeset/pre/23394-branding-logo-live.md similarity index 100% rename from .changeset/23394-branding-logo-live.md rename to .changeset/pre/23394-branding-logo-live.md diff --git a/.changeset/23447-page-component-view-data-judge-deleted.md b/.changeset/pre/23447-page-component-view-data-judge-deleted.md similarity index 100% rename from .changeset/23447-page-component-view-data-judge-deleted.md rename to .changeset/pre/23447-page-component-view-data-judge-deleted.md diff --git a/.changeset/23497-public-form-section-i18n.md b/.changeset/pre/23497-public-form-section-i18n.md similarity index 100% rename from .changeset/23497-public-form-section-i18n.md rename to .changeset/pre/23497-public-form-section-i18n.md diff --git a/.changeset/23528-exposure-refusal-code.md b/.changeset/pre/23528-exposure-refusal-code.md similarity index 100% rename from .changeset/23528-exposure-refusal-code.md rename to .changeset/pre/23528-exposure-refusal-code.md diff --git a/.changeset/23543-f2-job-retry-policy-without-attempts-refused.md b/.changeset/pre/23543-f2-job-retry-policy-without-attempts-refused.md similarity index 100% rename from .changeset/23543-f2-job-retry-policy-without-attempts-refused.md rename to .changeset/pre/23543-f2-job-retry-policy-without-attempts-refused.md diff --git a/.changeset/23543-f4-visibility-alias-keys-retired.md b/.changeset/pre/23543-f4-visibility-alias-keys-retired.md similarity index 100% rename from .changeset/23543-f4-visibility-alias-keys-retired.md rename to .changeset/pre/23543-f4-visibility-alias-keys-retired.md diff --git a/.changeset/23543-f5-http-callout-node-types-reserved.md b/.changeset/pre/23543-f5-http-callout-node-types-reserved.md similarity index 100% rename from .changeset/23543-f5-http-callout-node-types-reserved.md rename to .changeset/pre/23543-f5-http-callout-node-types-reserved.md diff --git a/.changeset/23553-default-record-page-remedy.md b/.changeset/pre/23553-default-record-page-remedy.md similarity index 100% rename from .changeset/23553-default-record-page-remedy.md rename to .changeset/pre/23553-default-record-page-remedy.md diff --git a/.changeset/23609-legacy-org-read-seam.md b/.changeset/pre/23609-legacy-org-read-seam.md similarity index 100% rename from .changeset/23609-legacy-org-read-seam.md rename to .changeset/pre/23609-legacy-org-read-seam.md diff --git a/.changeset/23610-page-isdefault-template-slots.md b/.changeset/pre/23610-page-isdefault-template-slots.md similarity index 100% rename from .changeset/23610-page-isdefault-template-slots.md rename to .changeset/pre/23610-page-isdefault-template-slots.md diff --git a/.changeset/23610-skills-pages-contract.md b/.changeset/pre/23610-skills-pages-contract.md similarity index 100% rename from .changeset/23610-skills-pages-contract.md rename to .changeset/pre/23610-skills-pages-contract.md diff --git a/.changeset/23633-unrecognised-database-url-scheme.md b/.changeset/pre/23633-unrecognised-database-url-scheme.md similarity index 100% rename from .changeset/23633-unrecognised-database-url-scheme.md rename to .changeset/pre/23633-unrecognised-database-url-scheme.md diff --git a/.changeset/23634-stack-datasource-default-refused.md b/.changeset/pre/23634-stack-datasource-default-refused.md similarity index 100% rename from .changeset/23634-stack-datasource-default-refused.md rename to .changeset/pre/23634-stack-datasource-default-refused.md diff --git a/.changeset/23650-page-header-show-star-retired.md b/.changeset/pre/23650-page-header-show-star-retired.md similarity index 100% rename from .changeset/23650-page-header-show-star-retired.md rename to .changeset/pre/23650-page-header-show-star-retired.md diff --git a/.changeset/23710-liveness-print-flow-messages.md b/.changeset/pre/23710-liveness-print-flow-messages.md similarity index 100% rename from .changeset/23710-liveness-print-flow-messages.md rename to .changeset/pre/23710-liveness-print-flow-messages.md diff --git a/.changeset/23712-draft-save-d99-address.md b/.changeset/pre/23712-draft-save-d99-address.md similarity index 100% rename from .changeset/23712-draft-save-d99-address.md rename to .changeset/pre/23712-draft-save-d99-address.md diff --git a/.changeset/23714-unique-violation-arm-relay.md b/.changeset/pre/23714-unique-violation-arm-relay.md similarity index 100% rename from .changeset/23714-unique-violation-arm-relay.md rename to .changeset/pre/23714-unique-violation-arm-relay.md diff --git a/.changeset/23723-sandbox-unreadable-rejection.md b/.changeset/pre/23723-sandbox-unreadable-rejection.md similarity index 100% rename from .changeset/23723-sandbox-unreadable-rejection.md rename to .changeset/pre/23723-sandbox-unreadable-rejection.md diff --git a/.changeset/23726-read-only-arms-remainder.md b/.changeset/pre/23726-read-only-arms-remainder.md similarity index 100% rename from .changeset/23726-read-only-arms-remainder.md rename to .changeset/pre/23726-read-only-arms-remainder.md diff --git a/.changeset/23731-activity-changed-fields.md b/.changeset/pre/23731-activity-changed-fields.md similarity index 100% rename from .changeset/23731-activity-changed-fields.md rename to .changeset/pre/23731-activity-changed-fields.md diff --git a/.changeset/23732-attachment-readable-ids.md b/.changeset/pre/23732-attachment-readable-ids.md similarity index 100% rename from .changeset/23732-attachment-readable-ids.md rename to .changeset/pre/23732-attachment-readable-ids.md diff --git a/.changeset/23735-reference-deleted-when.md b/.changeset/pre/23735-reference-deleted-when.md similarity index 100% rename from .changeset/23735-reference-deleted-when.md rename to .changeset/pre/23735-reference-deleted-when.md diff --git a/.changeset/23736-record-id-deleted-when.md b/.changeset/pre/23736-record-id-deleted-when.md similarity index 100% rename from .changeset/23736-record-id-deleted-when.md rename to .changeset/pre/23736-record-id-deleted-when.md diff --git a/.changeset/23737-uncreatable-db-dir-refusal.md b/.changeset/pre/23737-uncreatable-db-dir-refusal.md similarity index 100% rename from .changeset/23737-uncreatable-db-dir-refusal.md rename to .changeset/pre/23737-uncreatable-db-dir-refusal.md diff --git a/.changeset/23740-form-section-columns-no-default.md b/.changeset/pre/23740-form-section-columns-no-default.md similarity index 100% rename from .changeset/23740-form-section-columns-no-default.md rename to .changeset/pre/23740-form-section-columns-no-default.md diff --git a/.changeset/23743-no-server-announcements.md b/.changeset/pre/23743-no-server-announcements.md similarity index 100% rename from .changeset/23743-no-server-announcements.md rename to .changeset/pre/23743-no-server-announcements.md diff --git a/.changeset/23744-kernel-only-lifetime.md b/.changeset/pre/23744-kernel-only-lifetime.md similarity index 100% rename from .changeset/23744-kernel-only-lifetime.md rename to .changeset/pre/23744-kernel-only-lifetime.md diff --git a/.changeset/23745-record-json-block.md b/.changeset/pre/23745-record-json-block.md similarity index 100% rename from .changeset/23745-record-json-block.md rename to .changeset/pre/23745-record-json-block.md diff --git a/.changeset/23753-navigation-record-surface.md b/.changeset/pre/23753-navigation-record-surface.md similarity index 100% rename from .changeset/23753-navigation-record-surface.md rename to .changeset/pre/23753-navigation-record-surface.md diff --git a/.changeset/23756-section-translation-tsdoc.md b/.changeset/pre/23756-section-translation-tsdoc.md similarity index 100% rename from .changeset/23756-section-translation-tsdoc.md rename to .changeset/pre/23756-section-translation-tsdoc.md diff --git a/.changeset/23764-unsupported-scheme-no-url.md b/.changeset/pre/23764-unsupported-scheme-no-url.md similarity index 100% rename from .changeset/23764-unsupported-scheme-no-url.md rename to .changeset/pre/23764-unsupported-scheme-no-url.md diff --git a/.changeset/23765-opaque-scheme-refusal.md b/.changeset/pre/23765-opaque-scheme-refusal.md similarity index 100% rename from .changeset/23765-opaque-scheme-refusal.md rename to .changeset/pre/23765-opaque-scheme-refusal.md diff --git a/.changeset/23768-datasources-default-host-reserved.md b/.changeset/pre/23768-datasources-default-host-reserved.md similarity index 100% rename from .changeset/23768-datasources-default-host-reserved.md rename to .changeset/pre/23768-datasources-default-host-reserved.md diff --git a/.changeset/23772-principal-session-mint.md b/.changeset/pre/23772-principal-session-mint.md similarity index 100% rename from .changeset/23772-principal-session-mint.md rename to .changeset/pre/23772-principal-session-mint.md diff --git a/.changeset/23775-flow-save-door-new-refusal.md b/.changeset/pre/23775-flow-save-door-new-refusal.md similarity index 100% rename from .changeset/23775-flow-save-door-new-refusal.md rename to .changeset/pre/23775-flow-save-door-new-refusal.md diff --git a/.changeset/23787-api-skill-retired-route-door.md b/.changeset/pre/23787-api-skill-retired-route-door.md similarity index 100% rename from .changeset/23787-api-skill-retired-route-door.md rename to .changeset/pre/23787-api-skill-retired-route-door.md diff --git a/.changeset/23788-blank-scaffold-npm10-vitest-override.md b/.changeset/pre/23788-blank-scaffold-npm10-vitest-override.md similarity index 100% rename from .changeset/23788-blank-scaffold-npm10-vitest-override.md rename to .changeset/pre/23788-blank-scaffold-npm10-vitest-override.md diff --git a/.changeset/23789-page-props-save-door-sentences.md b/.changeset/pre/23789-page-props-save-door-sentences.md similarity index 100% rename from .changeset/23789-page-props-save-door-sentences.md rename to .changeset/pre/23789-page-props-save-door-sentences.md diff --git a/.changeset/23792-memory-url-refusal-no-url.md b/.changeset/pre/23792-memory-url-refusal-no-url.md similarity index 100% rename from .changeset/23792-memory-url-refusal-no-url.md rename to .changeset/pre/23792-memory-url-refusal-no-url.md diff --git a/.changeset/23794-file-url-filename-rule.md b/.changeset/pre/23794-file-url-filename-rule.md similarity index 100% rename from .changeset/23794-file-url-filename-rule.md rename to .changeset/pre/23794-file-url-filename-rule.md diff --git a/.changeset/23802-setup-copy.md b/.changeset/pre/23802-setup-copy.md similarity index 100% rename from .changeset/23802-setup-copy.md rename to .changeset/pre/23802-setup-copy.md diff --git a/.changeset/23803-setup-nav-user-entries.md b/.changeset/pre/23803-setup-nav-user-entries.md similarity index 100% rename from .changeset/23803-setup-nav-user-entries.md rename to .changeset/pre/23803-setup-nav-user-entries.md diff --git a/.changeset/23804-own-ui-state-no-ledger-row.md b/.changeset/pre/23804-own-ui-state-no-ledger-row.md similarity index 100% rename from .changeset/23804-own-ui-state-no-ledger-row.md rename to .changeset/pre/23804-own-ui-state-no-ledger-row.md diff --git a/.changeset/23805-scaffold-vitest-override.md b/.changeset/pre/23805-scaffold-vitest-override.md similarity index 100% rename from .changeset/23805-scaffold-vitest-override.md rename to .changeset/pre/23805-scaffold-vitest-override.md diff --git a/.changeset/23806-metadata-core-vitest-peer.md b/.changeset/pre/23806-metadata-core-vitest-peer.md similarity index 100% rename from .changeset/23806-metadata-core-vitest-peer.md rename to .changeset/pre/23806-metadata-core-vitest-peer.md diff --git a/.changeset/23808-type-descriptions-plain.md b/.changeset/pre/23808-type-descriptions-plain.md similarity index 100% rename from .changeset/23808-type-descriptions-plain.md rename to .changeset/pre/23808-type-descriptions-plain.md diff --git a/.changeset/23809-select-option-kind.md b/.changeset/pre/23809-select-option-kind.md similarity index 100% rename from .changeset/23809-select-option-kind.md rename to .changeset/pre/23809-select-option-kind.md diff --git a/.changeset/23813-minted-session-no-refresh.md b/.changeset/pre/23813-minted-session-no-refresh.md similarity index 100% rename from .changeset/23813-minted-session-no-refresh.md rename to .changeset/pre/23813-minted-session-no-refresh.md diff --git a/.changeset/23816-turso-remote-ownership-ceremony.md b/.changeset/pre/23816-turso-remote-ownership-ceremony.md similarity index 100% rename from .changeset/23816-turso-remote-ownership-ceremony.md rename to .changeset/pre/23816-turso-remote-ownership-ceremony.md diff --git a/.changeset/23820-dev-watch-compile-refusal.md b/.changeset/pre/23820-dev-watch-compile-refusal.md similarity index 100% rename from .changeset/23820-dev-watch-compile-refusal.md rename to .changeset/pre/23820-dev-watch-compile-refusal.md diff --git a/.changeset/23826-ceremony-report-file-reserved.md b/.changeset/pre/23826-ceremony-report-file-reserved.md similarity index 100% rename from .changeset/23826-ceremony-report-file-reserved.md rename to .changeset/pre/23826-ceremony-report-file-reserved.md diff --git a/.changeset/23829-setup-copy-services.md b/.changeset/pre/23829-setup-copy-services.md similarity index 100% rename from .changeset/23829-setup-copy-services.md rename to .changeset/pre/23829-setup-copy-services.md diff --git a/.changeset/23838-identity-feeds-off.md b/.changeset/pre/23838-identity-feeds-off.md similarity index 100% rename from .changeset/23838-identity-feeds-off.md rename to .changeset/pre/23838-identity-feeds-off.md diff --git a/.changeset/23839-settings-warning-banners.md b/.changeset/pre/23839-settings-warning-banners.md similarity index 100% rename from .changeset/23839-settings-warning-banners.md rename to .changeset/pre/23839-settings-warning-banners.md diff --git a/.changeset/23842-automation-stamps-unbound-cause.md b/.changeset/pre/23842-automation-stamps-unbound-cause.md similarity index 100% rename from .changeset/23842-automation-stamps-unbound-cause.md rename to .changeset/pre/23842-automation-stamps-unbound-cause.md diff --git a/.changeset/23842-flow-runtime-state-unbound-cause.md b/.changeset/pre/23842-flow-runtime-state-unbound-cause.md similarity index 100% rename from .changeset/23842-flow-runtime-state-unbound-cause.md rename to .changeset/pre/23842-flow-runtime-state-unbound-cause.md diff --git a/.changeset/23847-turso-remote-not-null.md b/.changeset/pre/23847-turso-remote-not-null.md similarity index 100% rename from .changeset/23847-turso-remote-not-null.md rename to .changeset/pre/23847-turso-remote-not-null.md diff --git a/.changeset/23848-migrate-apply-turso-remote.md b/.changeset/pre/23848-migrate-apply-turso-remote.md similarity index 100% rename from .changeset/23848-migrate-apply-turso-remote.md rename to .changeset/pre/23848-migrate-apply-turso-remote.md diff --git a/.changeset/23852-setup-empty-states.md b/.changeset/pre/23852-setup-empty-states.md similarity index 100% rename from .changeset/23852-setup-empty-states.md rename to .changeset/pre/23852-setup-empty-states.md diff --git a/.changeset/23853-form-help-texts-plain.md b/.changeset/pre/23853-form-help-texts-plain.md similarity index 100% rename from .changeset/23853-form-help-texts-plain.md rename to .changeset/pre/23853-form-help-texts-plain.md diff --git a/.changeset/23855-settings-banner-translation-slot.md b/.changeset/pre/23855-settings-banner-translation-slot.md similarity index 100% rename from .changeset/23855-settings-banner-translation-slot.md rename to .changeset/pre/23855-settings-banner-translation-slot.md diff --git a/.changeset/23857-comment-parent-pair.md b/.changeset/pre/23857-comment-parent-pair.md similarity index 100% rename from .changeset/23857-comment-parent-pair.md rename to .changeset/pre/23857-comment-parent-pair.md diff --git a/.changeset/23858-serve-state-file-directory.md b/.changeset/pre/23858-serve-state-file-directory.md similarity index 100% rename from .changeset/23858-serve-state-file-directory.md rename to .changeset/pre/23858-serve-state-file-directory.md diff --git a/.changeset/23858-uncreatable-directory-refusal.md b/.changeset/pre/23858-uncreatable-directory-refusal.md similarity index 100% rename from .changeset/23858-uncreatable-directory-refusal.md rename to .changeset/pre/23858-uncreatable-directory-refusal.md diff --git a/.changeset/23859-client-runtime-status-type.md b/.changeset/pre/23859-client-runtime-status-type.md similarity index 100% rename from .changeset/23859-client-runtime-status-type.md rename to .changeset/pre/23859-client-runtime-status-type.md diff --git a/.changeset/23863-explicit-sqlite-non-file-refusal.md b/.changeset/pre/23863-explicit-sqlite-non-file-refusal.md similarity index 100% rename from .changeset/23863-explicit-sqlite-non-file-refusal.md rename to .changeset/pre/23863-explicit-sqlite-non-file-refusal.md diff --git a/.changeset/23865-next-steps-after-failed-install.md b/.changeset/pre/23865-next-steps-after-failed-install.md similarity index 100% rename from .changeset/23865-next-steps-after-failed-install.md rename to .changeset/pre/23865-next-steps-after-failed-install.md diff --git a/.changeset/23877-visibility-alias-fold-unexported.md b/.changeset/pre/23877-visibility-alias-fold-unexported.md similarity index 100% rename from .changeset/23877-visibility-alias-fold-unexported.md rename to .changeset/pre/23877-visibility-alias-fold-unexported.md diff --git a/.changeset/23887-anchor-docblock-guest-no-door.md b/.changeset/pre/23887-anchor-docblock-guest-no-door.md similarity index 100% rename from .changeset/23887-anchor-docblock-guest-no-door.md rename to .changeset/pre/23887-anchor-docblock-guest-no-door.md diff --git a/.changeset/23889-approval-list-display-labels.md b/.changeset/pre/23889-approval-list-display-labels.md similarity index 100% rename from .changeset/23889-approval-list-display-labels.md rename to .changeset/pre/23889-approval-list-display-labels.md diff --git a/.changeset/23891-permission-set-managed-by-retired.md b/.changeset/pre/23891-permission-set-managed-by-retired.md similarity index 100% rename from .changeset/23891-permission-set-managed-by-retired.md rename to .changeset/pre/23891-permission-set-managed-by-retired.md diff --git a/.changeset/23898-sqlite-refusal-remedy.md b/.changeset/pre/23898-sqlite-refusal-remedy.md similarity index 100% rename from .changeset/23898-sqlite-refusal-remedy.md rename to .changeset/pre/23898-sqlite-refusal-remedy.md diff --git a/.changeset/23899-read-only-arms-four-more.md b/.changeset/pre/23899-read-only-arms-four-more.md similarity index 100% rename from .changeset/23899-read-only-arms-four-more.md rename to .changeset/pre/23899-read-only-arms-four-more.md diff --git a/.changeset/23903-flow-save-reintroduced-retired.md b/.changeset/pre/23903-flow-save-reintroduced-retired.md similarity index 100% rename from .changeset/23903-flow-save-reintroduced-retired.md rename to .changeset/pre/23903-flow-save-reintroduced-retired.md diff --git a/.changeset/23904-installed-page-retired-declarations-convert-at-load.md b/.changeset/pre/23904-installed-page-retired-declarations-convert-at-load.md similarity index 100% rename from .changeset/23904-installed-page-retired-declarations-convert-at-load.md rename to .changeset/pre/23904-installed-page-retired-declarations-convert-at-load.md diff --git a/.changeset/23909-keys-every-verb-anonymous-floor.md b/.changeset/pre/23909-keys-every-verb-anonymous-floor.md similarity index 100% rename from .changeset/23909-keys-every-verb-anonymous-floor.md rename to .changeset/pre/23909-keys-every-verb-anonymous-floor.md diff --git a/.changeset/23910-validation-previous-insert.md b/.changeset/pre/23910-validation-previous-insert.md similarity index 100% rename from .changeset/23910-validation-previous-insert.md rename to .changeset/pre/23910-validation-previous-insert.md diff --git a/.changeset/23914-cloud-connection-body-refusal.md b/.changeset/pre/23914-cloud-connection-body-refusal.md similarity index 100% rename from .changeset/23914-cloud-connection-body-refusal.md rename to .changeset/pre/23914-cloud-connection-body-refusal.md diff --git a/.changeset/23914-hono-body-refusal.md b/.changeset/pre/23914-hono-body-refusal.md similarity index 100% rename from .changeset/23914-hono-body-refusal.md rename to .changeset/pre/23914-hono-body-refusal.md diff --git a/.changeset/23914-hono-server-body-refusal.md b/.changeset/pre/23914-hono-server-body-refusal.md similarity index 100% rename from .changeset/23914-hono-server-body-refusal.md rename to .changeset/pre/23914-hono-server-body-refusal.md diff --git a/.changeset/23919-builtin-position-copy.md b/.changeset/pre/23919-builtin-position-copy.md similarity index 100% rename from .changeset/23919-builtin-position-copy.md rename to .changeset/pre/23919-builtin-position-copy.md diff --git a/.changeset/23925-remote-plan-remedy.md b/.changeset/pre/23925-remote-plan-remedy.md similarity index 100% rename from .changeset/23925-remote-plan-remedy.md rename to .changeset/pre/23925-remote-plan-remedy.md diff --git a/.changeset/23928-parallel-failed-branch-steps.md b/.changeset/pre/23928-parallel-failed-branch-steps.md similarity index 100% rename from .changeset/23928-parallel-failed-branch-steps.md rename to .changeset/pre/23928-parallel-failed-branch-steps.md diff --git a/.changeset/23932-declared-client-refusal-log.md b/.changeset/pre/23932-declared-client-refusal-log.md similarity index 100% rename from .changeset/23932-declared-client-refusal-log.md rename to .changeset/pre/23932-declared-client-refusal-log.md diff --git a/.changeset/23944-revoked-key-refusal-text.md b/.changeset/pre/23944-revoked-key-refusal-text.md similarity index 100% rename from .changeset/23944-revoked-key-refusal-text.md rename to .changeset/pre/23944-revoked-key-refusal-text.md diff --git a/.changeset/23946-email-template-loader-boot-order.md b/.changeset/pre/23946-email-template-loader-boot-order.md similarity index 100% rename from .changeset/23946-email-template-loader-boot-order.md rename to .changeset/pre/23946-email-template-loader-boot-order.md diff --git a/.changeset/23948-declared-rule-mirrors-advisory.md b/.changeset/pre/23948-declared-rule-mirrors-advisory.md similarity index 100% rename from .changeset/23948-declared-rule-mirrors-advisory.md rename to .changeset/pre/23948-declared-rule-mirrors-advisory.md diff --git a/.changeset/23957-invite-role-options-per-caller.md b/.changeset/pre/23957-invite-role-options-per-caller.md similarity index 100% rename from .changeset/23957-invite-role-options-per-caller.md rename to .changeset/pre/23957-invite-role-options-per-caller.md diff --git a/.changeset/23963-resume-gone-run-settled.md b/.changeset/pre/23963-resume-gone-run-settled.md similarity index 100% rename from .changeset/23963-resume-gone-run-settled.md rename to .changeset/pre/23963-resume-gone-run-settled.md diff --git a/.changeset/23964-record-surface-docblock.md b/.changeset/pre/23964-record-surface-docblock.md similarity index 100% rename from .changeset/23964-record-surface-docblock.md rename to .changeset/pre/23964-record-surface-docblock.md diff --git a/.changeset/23967-view-label-not-invented.md b/.changeset/pre/23967-view-label-not-invented.md similarity index 100% rename from .changeset/23967-view-label-not-invented.md rename to .changeset/pre/23967-view-label-not-invented.md diff --git a/.changeset/23972-installed-package-source.md b/.changeset/pre/23972-installed-package-source.md similarity index 100% rename from .changeset/23972-installed-package-source.md rename to .changeset/pre/23972-installed-package-source.md diff --git a/.changeset/23974-package-doors-convert-forward.md b/.changeset/pre/23974-package-doors-convert-forward.md similarity index 100% rename from .changeset/23974-package-doors-convert-forward.md rename to .changeset/pre/23974-package-doors-convert-forward.md diff --git a/.changeset/23978-approval-notification-subject-name.md b/.changeset/pre/23978-approval-notification-subject-name.md similarity index 100% rename from .changeset/23978-approval-notification-subject-name.md rename to .changeset/pre/23978-approval-notification-subject-name.md diff --git a/.changeset/23993-reset-revoked-key-restore.md b/.changeset/pre/23993-reset-revoked-key-restore.md similarity index 100% rename from .changeset/23993-reset-revoked-key-restore.md rename to .changeset/pre/23993-reset-revoked-key-restore.md diff --git a/.changeset/24001-conversion-conflict-channel-retired.md b/.changeset/pre/24001-conversion-conflict-channel-retired.md similarity index 100% rename from .changeset/24001-conversion-conflict-channel-retired.md rename to .changeset/pre/24001-conversion-conflict-channel-retired.md diff --git a/.changeset/24007-approval-request-title-label.md b/.changeset/pre/24007-approval-request-title-label.md similarity index 100% rename from .changeset/24007-approval-request-title-label.md rename to .changeset/pre/24007-approval-request-title-label.md diff --git a/.changeset/24022-datasource-admin-no-server.md b/.changeset/pre/24022-datasource-admin-no-server.md similarity index 100% rename from .changeset/24022-datasource-admin-no-server.md rename to .changeset/pre/24022-datasource-admin-no-server.md diff --git a/.changeset/24024-notifications-share-links-anonymous-floor.md b/.changeset/pre/24024-notifications-share-links-anonymous-floor.md similarity index 100% rename from .changeset/24024-notifications-share-links-anonymous-floor.md rename to .changeset/pre/24024-notifications-share-links-anonymous-floor.md diff --git a/.changeset/24029-protocol-range-union.md b/.changeset/pre/24029-protocol-range-union.md similarity index 100% rename from .changeset/24029-protocol-range-union.md rename to .changeset/pre/24029-protocol-range-union.md diff --git a/.changeset/24030-install-overwrite-readonly-package.md b/.changeset/pre/24030-install-overwrite-readonly-package.md similarity index 100% rename from .changeset/24030-install-overwrite-readonly-package.md rename to .changeset/pre/24030-install-overwrite-readonly-package.md diff --git a/.changeset/24031-exposure-gate-one-object-read.md b/.changeset/pre/24031-exposure-gate-one-object-read.md similarity index 100% rename from .changeset/24031-exposure-gate-one-object-read.md rename to .changeset/pre/24031-exposure-gate-one-object-read.md diff --git a/.changeset/24032-plugin-auth-body-refusal.md b/.changeset/pre/24032-plugin-auth-body-refusal.md similarity index 100% rename from .changeset/24032-plugin-auth-body-refusal.md rename to .changeset/pre/24032-plugin-auth-body-refusal.md diff --git a/.changeset/24041-duplicate-install-refusal.md b/.changeset/pre/24041-duplicate-install-refusal.md similarity index 100% rename from .changeset/24041-duplicate-install-refusal.md rename to .changeset/pre/24041-duplicate-install-refusal.md diff --git a/.changeset/24042-hono-undeclared-body-refusal.md b/.changeset/pre/24042-hono-undeclared-body-refusal.md similarity index 100% rename from .changeset/24042-hono-undeclared-body-refusal.md rename to .changeset/pre/24042-hono-undeclared-body-refusal.md diff --git a/.changeset/24043-http-request-body-tsdoc.md b/.changeset/pre/24043-http-request-body-tsdoc.md similarity index 100% rename from .changeset/24043-http-request-body-tsdoc.md rename to .changeset/pre/24043-http-request-body-tsdoc.md diff --git a/.changeset/24047-action-param-options-comments.md b/.changeset/pre/24047-action-param-options-comments.md similarity index 100% rename from .changeset/24047-action-param-options-comments.md rename to .changeset/pre/24047-action-param-options-comments.md diff --git a/.changeset/24065-ooo-notification-person-names.md b/.changeset/pre/24065-ooo-notification-person-names.md similarity index 100% rename from .changeset/24065-ooo-notification-person-names.md rename to .changeset/pre/24065-ooo-notification-person-names.md diff --git a/.changeset/24082-invite-role-cap-codes.md b/.changeset/pre/24082-invite-role-cap-codes.md similarity index 100% rename from .changeset/24082-invite-role-cap-codes.md rename to .changeset/pre/24082-invite-role-cap-codes.md diff --git a/.changeset/24129-blueprint-view-filters-sort.md b/.changeset/pre/24129-blueprint-view-filters-sort.md similarity index 100% rename from .changeset/24129-blueprint-view-filters-sort.md rename to .changeset/pre/24129-blueprint-view-filters-sort.md diff --git a/.changeset/24134-console-pin-5e15928b4631.md b/.changeset/pre/24134-console-pin-5e15928b4631.md similarity index 100% rename from .changeset/24134-console-pin-5e15928b4631.md rename to .changeset/pre/24134-console-pin-5e15928b4631.md diff --git a/.changeset/console-daeb77fc84dd.md b/.changeset/pre/console-daeb77fc84dd.md similarity index 100% rename from .changeset/console-daeb77fc84dd.md rename to .changeset/pre/console-daeb77fc84dd.md diff --git a/.changeset/console-f8267df93aba.md b/.changeset/pre/console-f8267df93aba.md similarity index 100% rename from .changeset/console-f8267df93aba.md rename to .changeset/pre/console-f8267df93aba.md diff --git a/.changeset/org-ownership-scim-provider-inventory.md b/.changeset/pre/org-ownership-scim-provider-inventory.md similarity index 100% rename from .changeset/org-ownership-scim-provider-inventory.md rename to .changeset/pre/org-ownership-scim-provider-inventory.md diff --git a/.changeset/v18-ceremony-dev-databases.md b/.changeset/pre/v18-ceremony-dev-databases.md similarity index 100% rename from .changeset/v18-ceremony-dev-databases.md rename to .changeset/pre/v18-ceremony-dev-databases.md diff --git a/examples/app-crm/CHANGELOG.md b/examples/app-crm/CHANGELOG.md index 0ad575f394a..6a9e470c9a3 100644 --- a/examples/app-crm/CHANGELOG.md +++ b/examples/app-crm/CHANGELOG.md @@ -1,5 +1,66 @@ # @objectstack/example-crm +## 4.0.100-next.3 + +### Patch Changes + +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [9fcea20] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [7428da0] +- Updated dependencies [b55be42] +- Updated dependencies [8776119] +- Updated dependencies [f7a5ebe] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9d4fb3c] +- Updated dependencies [c5cf546] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [34b740f] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [ae4dfaf] +- Updated dependencies [6422da5] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2e15620] +- Updated dependencies [0cb45e9] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [605aa01] +- Updated dependencies [d050a8f] +- Updated dependencies [9e797a8] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [a65a59b] +- Updated dependencies [6dede7a] +- Updated dependencies [62f959d] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] +- Updated dependencies [cdf3777] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/runtime@18.0.0-next.3 + - @objectstack/service-i18n@18.0.0-next.3 + ## 4.0.100-next.2 ### Patch Changes diff --git a/examples/app-crm/package.json b/examples/app-crm/package.json index 8f3cf45cc34..a7831dfb4f9 100644 --- a/examples/app-crm/package.json +++ b/examples/app-crm/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/example-crm", - "version": "4.0.100-next.2", + "version": "4.0.100-next.3", "description": "Minimal CRM example \u2014 a smoke-test workspace that exercises the metadata loading pipeline (objects \u2192 views \u2192 app \u2192 dashboard \u2192 hook \u2192 flow \u2192 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-multi-package/CHANGELOG.md b/examples/app-multi-package/CHANGELOG.md index 405cd280a06..4923470e0af 100644 --- a/examples/app-multi-package/CHANGELOG.md +++ b/examples/app-multi-package/CHANGELOG.md @@ -1,5 +1,49 @@ # @objectstack/example-multi-package +## 0.0.7-next.3 + +### Patch Changes + +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [6422da5] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [a65a59b] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + ## 0.0.7-next.2 ### Patch Changes diff --git a/examples/app-multi-package/package.json b/examples/app-multi-package/package.json index ac63f6882b8..1c6a9df8da7 100644 --- a/examples/app-multi-package/package.json +++ b/examples/app-multi-package/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/example-multi-package", - "version": "0.0.7-next.2", + "version": "0.0.7-next.3", "description": "One release artifact carrying TWO packages that share a namespace (ADR-0130 D4) — the producer-side fixture for `packages[]`", "license": "Apache-2.0", "private": true, diff --git a/examples/app-showcase/CHANGELOG.md b/examples/app-showcase/CHANGELOG.md index 9e6ccbf2820..412a511098a 100644 --- a/examples/app-showcase/CHANGELOG.md +++ b/examples/app-showcase/CHANGELOG.md @@ -1,5 +1,76 @@ # @objectstack/example-showcase +## 0.3.22-next.3 + +### Patch Changes + +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [9fcea20] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [7428da0] +- Updated dependencies [b55be42] +- Updated dependencies [8776119] +- Updated dependencies [f7a5ebe] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9d4fb3c] +- Updated dependencies [c5cf546] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [34b740f] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [ae4dfaf] +- Updated dependencies [6422da5] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2e15620] +- Updated dependencies [0cb45e9] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [605aa01] +- Updated dependencies [d050a8f] +- Updated dependencies [9e797a8] +- Updated dependencies [75ced06] +- Updated dependencies [c4111c6] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [c7168ac] +- Updated dependencies [a65a59b] +- Updated dependencies [6dede7a] +- Updated dependencies [62f959d] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] +- Updated dependencies [cdf3777] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/runtime@18.0.0-next.3 + - @objectstack/driver-sql@18.0.0-next.3 + - @objectstack/cloud-connection@18.0.0-next.3 + - @objectstack/connector-mcp@18.0.0-next.3 + - @objectstack/connector-openapi@18.0.0-next.3 + - @objectstack/connector-rest@18.0.0-next.3 + - @objectstack/connector-slack@18.0.0-next.3 + - @objectstack/service-datasource@18.0.0-next.3 + - @objectstack/service-i18n@18.0.0-next.3 + ## 0.3.22-next.2 ### Patch Changes diff --git a/examples/app-showcase/package.json b/examples/app-showcase/package.json index 2e729ce51bf..e68be4c46f1 100644 --- a/examples/app-showcase/package.json +++ b/examples/app-showcase/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/example-showcase", - "version": "0.3.22-next.2", + "version": "0.3.22-next.3", "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 c2e3728ed57..00d89887eb8 100644 --- a/examples/app-todo/CHANGELOG.md +++ b/examples/app-todo/CHANGELOG.md @@ -1,5 +1,77 @@ # @objectstack/example-todo +## 4.0.100-next.3 + +### Patch Changes + +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [9fcea20] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [7428da0] +- Updated dependencies [b55be42] +- Updated dependencies [abb6df0] +- Updated dependencies [8776119] +- Updated dependencies [f7a5ebe] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9d4fb3c] +- Updated dependencies [c5cf546] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [34b740f] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [ae4dfaf] +- Updated dependencies [6422da5] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2e15620] +- Updated dependencies [6d1660d] +- Updated dependencies [0cb45e9] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [605aa01] +- Updated dependencies [a33f3d5] +- Updated dependencies [d050a8f] +- Updated dependencies [9e797a8] +- Updated dependencies [b25d98a] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [a65a59b] +- Updated dependencies [6dede7a] +- Updated dependencies [62f959d] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] +- Updated dependencies [cdf3777] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/runtime@18.0.0-next.3 + - @objectstack/metadata@18.0.0-next.3 + - @objectstack/objectql@18.0.0-next.3 + - @objectstack/client@18.0.0-next.3 + - @objectstack/mcp@18.0.0-next.3 + - @objectstack/driver-sqlite-wasm@18.0.0-next.3 + - @objectstack/knowledge-memory@18.0.0-next.3 + - @objectstack/service-i18n@18.0.0-next.3 + - @objectstack/service-knowledge@18.0.0-next.3 + ## 4.0.100-next.2 ### Patch Changes diff --git a/examples/app-todo/package.json b/examples/app-todo/package.json index 9bd21e2ed00..6fc799449c0 100644 --- a/examples/app-todo/package.json +++ b/examples/app-todo/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/example-todo", - "version": "4.0.100-next.2", + "version": "4.0.100-next.3", "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 4a3d3a30463..c24159702e5 100644 --- a/examples/embed-objectql/CHANGELOG.md +++ b/examples/embed-objectql/CHANGELOG.md @@ -1,5 +1,54 @@ # @objectstack/example-embed-objectql +## 0.0.40-next.3 + +### Patch Changes + +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [abb6df0] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [6422da5] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [a33f3d5] +- Updated dependencies [d050a8f] +- Updated dependencies [b25d98a] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [a65a59b] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/objectql@18.0.0-next.3 + - @objectstack/driver-memory@18.0.0-next.3 + ## 0.0.40-next.2 ### Patch Changes diff --git a/examples/embed-objectql/package.json b/examples/embed-objectql/package.json index 37f22cb7983..91d4b3164c1 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.40-next.2", + "version": "0.0.40-next.3", "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 bf0b5193f39..8d37f72b3f6 100644 --- a/packages/adapters/hono/CHANGELOG.md +++ b/packages/adapters/hono/CHANGELOG.md @@ -1,5 +1,83 @@ # @objectstack/hono +## 18.0.0-next.3 + +### Minor Changes + +- 75ced06: fix(hono)!: `createHonoApp` refuses a declared JSON body that does not parse with `400 INVALID_REQUEST` when the dispatched domain reads it, instead of dispatching it as `{}` + + Clause-②: no (narrowing) + + + + **BREAKING** (an accept-set narrowing), shipped as `minor` under the launch-window convention for breaking changes. + + The `${prefix}/*` catch-all handed `dispatch()` `{}` for a body that did not parse, and the data domain read that as an empty query or an empty update: a truncated `POST /data/OBJECT/query` answered every row the caller could read, and a truncated `PATCH` answered `200` for an update that changed nothing. + + What changes for an integrator, on a host that serves `createHonoApp`: + + - A `POST`, `PUT` or `PATCH` body sent as `application/json` (matched case-insensitively) that is not blank and does not parse, to a domain that reads the parsed body (the data, metadata and every other domain that takes `body`): FROM that domain's answer for `{}`, TO `400` with `{ "success": false, "error": { "code": "INVALID_REQUEST", "message": "The request body could not be parsed as JSON (it was sent as application/json). It was refused rather than read as an empty body: send a well-formed JSON document, or no body at all." } }`. The domain acts on nothing past its read of the body, and the dispatcher's own gates still answer first. This is the same status and body `@objectstack/plugin-hono-server` answers for the same request. + - The same applies to the `/auth/*` legacy fallback that runs when no auth service is registered, for every method it hands a body (all but `GET` and `HEAD`). + + **Remedy, in one line.** Send a JSON body as well-formed JSON with `Content-Type: application/json`, or send no body. + + **Unchanged.** + + - An empty or whitespace-only JSON body is dispatched as `{}` (an empty query), as before. + - A body that does not declare JSON is read as before: parsed when it is JSON, `{}` when it is not. + - The request handed on beside the body still arrives unread, in every case. A domain that forwards it and reads its own bytes answers exactly as before, a malformed JSON body included: `/webhooks/redeliver` and the inbound automation hooks give their own refusal after their own sign-in or signature check, and the `/approvals/act` form `POST` reads its form. + - The catch-all dispatches `GET`, `HEAD` and `DELETE` with no body, and the `/auth/*` fallback hands `GET` and `HEAD` `{}`, as before. +- 76d9dca: fix(hono)!: `createHonoApp` reads a dispatched body by `@objectstack/plugin-hono-server`'s rule, so a JSON body sent as `text/plain`, or any other body that declares neither JSON nor a form, is refused `400 INVALID_REQUEST` when the dispatched domain reads it, instead of being parsed or dispatched as `{}` + + Clause-②: no (narrowing) + + + + **BREAKING** (an accept-set narrowing), shipped as `minor` under the launch-window convention for breaking changes. + + The `${prefix}/*` catch-all and the `/auth/*` legacy fallback parsed every body as JSON whatever its `Content-Type`, and handed `dispatch()` `{}` for any body that did not parse. So the two Hono doors answered one request two ways: `@objectstack/plugin-hono-server` (`objectstack dev`, `os serve`) refused a valid query body sent as `text/plain`, and this door honoured it; a garbled body that did not declare JSON reached the data domain here as `{}`, the empty query, and read every row the caller could read. + + What changes for an integrator, on a host that serves `createHonoApp`, for a `POST`, `PUT` or `PATCH` body read by a domain that reads the parsed body (the data, metadata and every other domain that takes `body`), and on the `/auth/*` legacy fallback for every method it hands a body (all but `GET` and `HEAD`): + + - A JSON body sent as `text/plain`, or as any media type other than JSON or a form: FROM parsed and honoured, TO `400` with `{ "success": false, "error": { "code": "INVALID_REQUEST", "message": "The request body was sent as text/plain, which this server does not read into a request body. It was refused rather than read as an empty body: send it as application/json." } }`, the message naming the media type that was sent. + - Any other body that declares neither JSON nor a form (`text/plain`, `application/octet-stream`, a `+json` suffix type such as `application/merge-patch+json`, or no `Content-Type` at all), when the request declares a body (`Content-Length` other than `0`, or `Transfer-Encoding`): FROM `{}`, TO the same refusal ("sent with no Content-Type" when there is none). + - A `multipart/form-data` or `application/x-www-form-urlencoded` body: FROM `{}`, TO its parsed fields, as `plugin-hono-server` hands them (Hono's `parseBody`: the last value of a repeated name). A form that does not parse: TO the refusal "The request body could not be parsed as form data (it was sent as multipart/form-data). It was refused rather than read as an empty body." + + The domain acts on nothing past its read of the body, and the dispatcher's own gates still answer first. Each answer is the status and body `@objectstack/plugin-hono-server` gives the same request. This replaces the earlier `createHonoApp` entry's statement that a body that does not declare JSON is read as before. + + **Remedy, in one line.** Send a JSON body with `Content-Type: application/json`, or send no body. + + **Unchanged.** + + - A body sent as `application/json` (matched case-insensitively) is parsed when well-formed, `{}` when blank or whitespace-only, and refused when it does not parse, as before. + - A request that declares no body is dispatched as `{}`, whatever its media type. + - The request handed on beside the body still arrives unread, in every case. A domain that forwards it and reads its own bytes answers exactly as before, whatever the body declares: `/webhooks/redeliver` and the inbound automation hooks give their own refusal after their own sign-in or signature check, and the `/approvals/act` form `POST` reads its form. + - The catch-all dispatches `GET`, `HEAD` and `DELETE` with no body, and the `/auth/*` fallback hands `GET` and `HEAD` `{}`, as before. + +### Patch Changes + +- Updated dependencies [9fcea20] +- Updated dependencies [ee0e115] +- Updated dependencies [7428da0] +- Updated dependencies [b55be42] +- Updated dependencies [f7a5ebe] +- Updated dependencies [9d4fb3c] +- Updated dependencies [c5cf546] +- Updated dependencies [34b740f] +- Updated dependencies [ae4dfaf] +- Updated dependencies [2e15620] +- Updated dependencies [0cb45e9] +- Updated dependencies [605aa01] +- Updated dependencies [9e797a8] +- Updated dependencies [75ced06] +- Updated dependencies [66d2757] +- Updated dependencies [6dede7a] +- Updated dependencies [62f959d] +- Updated dependencies [cdf3777] + - @objectstack/runtime@18.0.0-next.3 + - @objectstack/plugin-hono-server@18.0.0-next.3 + - @objectstack/types@18.0.0-next.3 + ## 18.0.0-next.2 ### Minor Changes diff --git a/packages/adapters/hono/package.json b/packages/adapters/hono/package.json index 9e6dbaae58d..9dfb35bed50 100644 --- a/packages/adapters/hono/package.json +++ b/packages/adapters/hono/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/hono", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "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 5a680af1a8a..3ceb559a377 100644 --- a/packages/apps/account/CHANGELOG.md +++ b/packages/apps/account/CHANGELOG.md @@ -1,5 +1,55 @@ # @objectstack/account +## 18.0.0-next.3 + +### Patch Changes + +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [30ce56e] +- Updated dependencies [2e0e451] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [77efc66] +- Updated dependencies [6422da5] +- Updated dependencies [8f4260f] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [e246f06] +- Updated dependencies [a65a59b] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/platform-objects@18.0.0-next.3 + ## 18.0.0-next.2 ### Patch Changes diff --git a/packages/apps/account/package.json b/packages/apps/account/package.json index f457ae3ed1c..814f28a877f 100644 --- a/packages/apps/account/package.json +++ b/packages/apps/account/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/account", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "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 22943d44a81..5d28f74128d 100644 --- a/packages/apps/setup/CHANGELOG.md +++ b/packages/apps/setup/CHANGELOG.md @@ -1,5 +1,55 @@ # @objectstack/setup +## 18.0.0-next.3 + +### Patch Changes + +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [30ce56e] +- Updated dependencies [2e0e451] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [77efc66] +- Updated dependencies [6422da5] +- Updated dependencies [8f4260f] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [e246f06] +- Updated dependencies [a65a59b] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/platform-objects@18.0.0-next.3 + ## 18.0.0-next.2 ### Patch Changes diff --git a/packages/apps/setup/package.json b/packages/apps/setup/package.json index 7ed6fc81ba0..b1509f799b1 100644 --- a/packages/apps/setup/package.json +++ b/packages/apps/setup/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/setup", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "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 d082aad65c2..5768b6def85 100644 --- a/packages/apps/studio/CHANGELOG.md +++ b/packages/apps/studio/CHANGELOG.md @@ -1,5 +1,55 @@ # @objectstack/studio +## 18.0.0-next.3 + +### Patch Changes + +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [30ce56e] +- Updated dependencies [2e0e451] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [77efc66] +- Updated dependencies [6422da5] +- Updated dependencies [8f4260f] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [e246f06] +- Updated dependencies [a65a59b] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/platform-objects@18.0.0-next.3 + ## 18.0.0-next.2 ### Patch Changes diff --git a/packages/apps/studio/package.json b/packages/apps/studio/package.json index b62f93246d3..f127a50e1ba 100644 --- a/packages/apps/studio/package.json +++ b/packages/apps/studio/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/studio", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "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 833880444ba..f4c7dbee6c3 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,471 @@ # @objectstack/cli +## 18.0.0-next.3 + +### Minor Changes + +- 5b6c257: feat(plugin-audit): `sys_activity` rows carry two writer-stamped read-rule columns, and the withheld-row rule is a predicate over them, with no SYSTEM pre-scan (ADR-0139 E6) + + **Run `os migrate activity-changed-fields --apply` at once after upgrading.** After the upgrade, and until it runs, the `sys_activity` rows written before the upgrade carry no stamp and are withheld from every non-system reader: activity feeds, the header bell and record timelines show only the rows written since the upgrade. The door is `os migrate activity-changed-fields`, dry run by default, `--apply` to write, idempotent, and it stamps both columns in one run. Its flags are `os migrate audit-metadata-bodies`'s, one for one (`--database-url`, `--apply`, `--yes`, `--force`, `--json`). The columns arrive with the upgraded deployment's schema sync, which an `--apply` run performs itself; a dry run against a database no upgraded process has opened cannot read them and exits `1`. + + Clause-②: yes + + `sys_activity` gains two columns, stamped at write time by one derivation (`planActivityStamp`): the CRUD mirror stamps its own rows, and a `sys_activity` before-insert hook stamps every row any other writer inserts (an app's own server action, the seed loader), over whatever value that writer sent. + + - `changed_fields` (`tags`) — the key set of the recorded change: for an update, every field its recorded `old` / `new` values name; `[]` for a create, a delete, a note, or an update that touched only `internal` fields. It is served narrowed to the fields the reader is served. + - `milestone_key` (`text`) — for a fired activity milestone, one sorted, comma-joined key of every field the object's `activityMilestones` declaration watched when it fired; `null` for every other row. It is never served. + + The withheld-row rule is now a WHERE over those two columns, one branch per parent object, on `find`, `findOne`, `count` and `aggregate`. It reads no row, so the 2,000-row SYSTEM pre-scan and its window are gone: a broad read of the stream is served every row the caller may read, with a true `total`. What the rule withholds: + + - an update row whose `changed_fields` is non-empty and names no field the reader is served (unchanged); + - a fired milestone row, unless its key equals the key of the object's CURRENT `activityMilestones` declaration and the reader is served every field that declaration watches. **The cost:** a milestone row written under an earlier declaration, or naming a since-removed field, is withheld from every non-exempt reader until retention ages it out (14 days declared; `lifecycle.retention_overrides` may lengthen it); + - an unstamped row, from every non-system reader: one at rest from before the upgrade until the backfill stamps it, and one the planner cannot judge (a milestone row naming no readable watched-field list), which stays unstamped. + + The parent-field query guard covers both columns as it covers `metadata`: a reader withheld any field of the parent is refused a filter, sort, group or aggregate over `changed_fields` or `milestone_key` (`PERMISSION_DENIED` / 403). + + `@objectstack/plugin-audit` exports the backfill, `backfillActivityChangedFields` (with `ACTIVITY_BACKFILL_PAGE_SIZE` and its report and engine types), and the planner, `planActivityStamp` (with `ACTIVITY_CHANGED_FIELDS_COLUMN`, `ACTIVITY_MILESTONE_KEY_COLUMN` and `ActivityStamp`). Inside the module (none of it is exported from the package), `computeWithheldUpdateFilter` keeps its name and now builds the predicate, `isWithheldOnlyUpdate` keeps its name as the update half over `changed_fields`, and `isWithheldMilestone` / `isWithheldRow` are gone with the pre-scan they judged for. +- ae4dfaf: feat(driver-turso,cli): the v18 organization-ownership ceremony runs on a Turso remote database + + Clause-②: yes (widening) + + `os migrate organization-ownership`, the plan and `--apply --backup-taken`, now completes against a Turso database reached over the remote transport (`libsql://…`), the transport hosted environments use. Before, the plan stopped at its boot with `Deferred schema DDL is not supported by the Turso REMOTE transport`, and the apply's column drops had no remote path either. + + - **`@objectstack/driver-turso`**, remote face: + - `detectManagedDrift()` answers instead of refusing with `NOT_IMPLEMENTED` / 501. The verdict is the shared differ's, the one the local face runs; the physical schema it diffs is read from the remote catalog (`pragma_table_info`, `sqlite_master`, `pragma_index_list` / `pragma_index_info`, and the ADR-0120 D4 duplicate probe). The declarations it diffs against are recorded by every remote schema door and by the DDL-free `registerObjectMetadata`, so a boot that skipped schema sync is covered too. + - `applyMigrationEntries()` applies `drop_index`, `create_index`, `recreate_index`, `replace_unique_index` and `drop_column` instead of refusing. Each entry runs as one libSQL `write` batch, so it lands whole or not at all: a `drop_column` drops the column's indexes with it in the same transaction, and a re-created index that fails to build leaves the old one in place. An entry is reported `applied` only once a read-back shows its target state. Every other column entry (nullability, width, a dropped DEFAULT) needs the SQLite table rebuild, which this transport does not run, so it is reported `skipped` and logged, as the in-place dialects report an op they cannot perform. + - The remote face declares `driftReportCaveat`: a sentence saying its drift report is for reading only and naming the reason. The OS_ARTIFACT_URL boot gate reads it (below). The local and embedded-replica faces declare `null`. + - The remote driver's `config` now carries its `url`, so the CLI labels a remote database by its URL (credential-free) instead of `better-sqlite3`. The ceremony writes that label into its plan and refuses an apply whose plan names another database; that refusal can now tell two remote databases apart. + - **`@objectstack/cli`**: the ceremony's plan and apply boots skip schema sync, instead of arming deferred DDL, when the primary datasource is a Turso remote one. No DDL is attempted, so the plan stays read-only, and every other datasource the boot connects is still deferred. The environment's own boot has already run the additive sync the ceremony reads. Every other `os migrate` command keeps refusing on that transport: their dry runs list held-back schema work, which a boot that holds nothing back cannot list. + - **`@objectstack/runtime`**: `createStandaloneStack` takes `skipSchemaSync`, passed to `ObjectQLPlugin` as the option `OS_SKIP_SCHEMA_SYNC=1` names. Unset, the plugin reads the variable as before. + + On a Turso remote primary, the `OS_ARTIFACT_URL` boot migration gate on `os serve` keeps its earlier answer: cannot judge, warn and boot. It applies no drift and refuses no boot. Only its warning's wording changes, from `Could not check the physical schema` to `Did not check the physical schema against the artifact: …`, followed by the driver's reason. The gate now decides this from a capability the driver declares (`driftReportCaveat`, a string on the remote face and `null` on the local and embedded-replica faces), not from a refusal it catches. Two follow-ups, filed separately and not closed by this change, have to land before that gate may act on a remote report: + - #23847: the remote transport's schema sync honours `storage.notNull` on create and add column, so the report stops naming a tightening its own sync produced; + - #23848: `os migrate apply` works on a Turso remote database, so the destructive drift the gate would refuse on has a remedy there. +- 61e5ee9: fix(cli,driver-turso): `os migrate plan` and `os migrate apply` run on a Turso REMOTE database, and the apply names every drift entry it could not apply + + Clause-②: yes + + On a primary reached over the Turso REMOTE transport (`libsql://…`, every hosted tenant), `os migrate plan` and `os migrate apply` used to stop at their boot with `Deferred schema DDL is not supported by the Turso REMOTE transport`, so the remedy the drift report and the `OS_ARTIFACT_URL` boot gate print for destructive drift, `os migrate apply --allow-destructive`, could not run there at all. Both commands now boot the way `os migrate organization-ownership` does on that transport: with schema sync skipped instead of deferred. What an operator on a remote primary now sees: + + - **The drift half runs.** `os migrate plan` lists the drift; `os migrate apply` applies what the transport can express, after the same confirmation: a column drop (with the indexes keyed on it, in one transaction) and every index entry. Nothing is written before the prompt, as before. + - **The additive half is reported as NOT examined, never as "nothing to create".** Both commands print a warning under the database line saying that tables and columns the metadata declares and the database lacks are neither listed nor created by this run, and that the serving boot (`os serve` / `os start`) creates them on this transport. The `--json` documents carry `schemaSync: "skipped"`, so their `pending: []` reads as not examined; an apply with no drift answers `message: "in_sync_partial"`, not `"in_sync"`. + - **Every entry the driver did not apply is named,** by table, column and kind, in both faces (`--json`: a new `notApplied` array on the post-apply document). The REMOTE transport runs no SQLite table rebuild, so a tightening, a relaxed NOT NULL, a width change or a dropped DEFAULT is not applied there. + - **A destructive entry confirmed with `--allow-destructive` that was not applied makes the run PARTIAL:** the human face ends `PARTIAL apply: …` and the command exits `1`; `--json` carries `message: "partial"` and exits `1`. A non-destructive entry that was not applied is named and the exit stays `0`. + + On every other database the apply behaves as before, with one difference in the same direction: a destructive change confirmed with `--allow-destructive` that the driver could not apply (its DDL failed, or the dialect cannot perform it) now exits `1` and is named, where the run used to exit `0` with a count. Its human line for the entries held back without `--allow-destructive` now reads `Skipped N destructive change(s) (re-run with --allow-destructive to include them)`. + + `@objectstack/driver-turso`: the REMOTE face's `driftReportCaveat` no longer says a destructive entry has no remedy on this transport; it says `os migrate apply --allow-destructive` applies a column drop or an index change there and names the rest. + + The other `os migrate` commands whose dry run arms deferred DDL (`duplicates`, `account-issuer`, `unmapped-columns`, `multi-value-columns`, and the dry runs of the data commands) still refuse on the REMOTE transport, unchanged. +- 67e8792: feat(plugin-audit): `sys_comment` rows carry a writer-stamped parent pair, `object_name` + `record_id`, and the comment-thread read gate narrows by it with one `ReadableIds` leaf per parent object, with no pre-scan (ADR-0139 E7) + + **Run `os migrate comment-parent-pair --apply` at once after upgrading.** After the upgrade, and until it runs, the `sys_comment` rows written before the upgrade carry no pair, and the read gate answers them as it answered every comment before: by a SYSTEM pre-scan of at most 2,000 candidate rows per read, fail closed. So a broad comment list — one not scoped to a single record's thread — can omit comments the reader may see, and its `total` agrees with the short list; a record's own comment thread is unaffected. The door is `os migrate comment-parent-pair`, dry run by default, `--apply` to write, idempotent, and it stamps both columns in one run, keeping each row's `updated_at`. Its flags are `os migrate activity-changed-fields`'s, one for one (`--database-url`, `--apply`, `--yes`, `--force`, `--json`). The columns arrive with the upgraded deployment's schema sync, which an `--apply` run performs itself; a dry run against a database no upgraded process has opened cannot read them and exits `1`. + + Clause-②: yes + + `sys_comment` gains two `readonly` text columns and an index over them. They are derived from `thread_id` (`{object}:{record_id}`, split on the first colon) by one planner, `planCommentParentPair`, and `thread_id` stays the key authors write: nothing changes for a writer. A `sys_comment` before-insert hook stamps every inserted row, whoever writes it, over whatever value the writer sent; an update that names `thread_id` (a re-point) is re-stamped from it; and an update that names either column without a thread has the pair re-derived from the row's stored thread, for every caller — a historical import (`preserveAudit`) included, where the engine's update-side `readonly` strip keeps a value — so no writer's value for either column lands. A multi-row update that names either column without a thread is refused whole (`VALIDATION_ERROR` / 400, nothing written) when its rows' threads name different records, since one write cannot store each row's own pair; over rows sharing one thread it stores that thread's pair. A thread that names no record stamps `null` / `null`, and such a comment stays hidden from every non-system reader, as before. + + The comment-thread read gate (`installCommentReadVisibility`) is now the parent-record gate the activity stream and the compliance ledger use, over the comment's own pair: one branch `{ object_name: P, record_id: { $in: ReadableIds.of(P) } }` per registered object other than `sys_comment`, ORed and ANDed into the read's `where`, on `find`, `findOne`, `count` and `aggregate`. A broad comment read is served every comment the reader may read, on any table size, with a true `total`. What keeps the bounded 2,000-row fail-closed probe, chosen up front by name, never by catching a refusal: + + - ADR-0139 D5's three classes: a delegated (on-behalf-of) caller (the whole read); a parent narrowed by an object-scoped read middleware (`sys_attachment`, `sys_comment_reaction`, `sys_approval_request`, …); a parent bound to another datasource; + - a security service that answers no `canReadObject`, or none at all (no leaf is written without it), and an engine that cannot classify a parent — each sends the whole read to the probe and is said once; + - a row with no stamped pair (until the backfill reaches it): probed by its `thread_id`, restricted to rows with no pair, never leafed; + - a composition whose registered `sys_comment` declares no pair: the thread probe over every row, said once. + + **The cost:** a deployment whose driver compiles no `ReadableIds` leaf — `driver-mongodb`, until ADR-0139's O3 — now fails a non-system comment read with `INTERNAL_ERROR` / 500 naming the driver in the log, as the activity, ledger and attachment reads already do (ADR-0139 D3); the gate does not catch that refusal into an empty list. + + Comment reactions are unchanged: their only parent is `sys_comment`, which this gate narrows by an object-scoped middleware, so the reaction gate keeps the bounded probe (ADR-0139 D5.2). + + `installCommentReadVisibility` takes an optional third argument, the security source whose `canReadObject` admits a leaf (`ParentObjectReadAdmissionSource`); without it every comment read is the bounded probe, as before. Its first parameter widens from `CommentAccessEngine` to `ParentRecordGateEngine` (`CommentAccessEngine` plus three optional members: a registry view, `hasObjectMiddleware` and `resolveEffectiveDatasource`); every existing caller's engine still satisfies it. Both types reach the published declaration through that signature. `CommentAccessEngine` gains an optional `getSchema`. `@objectstack/plugin-audit` exports the backfill, `backfillCommentParentPair` (with `COMMENT_PARENT_PAIR_BACKFILL_PAGE_SIZE` and its report and engine types), the planner `planCommentParentPair` (with `COMMENT_PARENT_OBJECT_COLUMN`, `COMMENT_PARENT_RECORD_COLUMN` and the `CommentParentPair` type). +- adce192: feat(cli): `os migrate organization-ownership --apply` certifies a database whose packages declare sharing rules — the declared sharing-rule mirrors this release's boot still seeds are reported as advisory, not as discrepancies + + Clause-②: yes (widening — the apply report file, the plan file and both `--json` payloads gain an `advisory` field on a departure entry, and the run's `advisory` count includes that departure's rows) + + - **What an operator saw.** On a v17 database whose packages declare sharing rules (HotCRM declares eleven), `--apply` ended with `complete: true` and `blocking: 11`, and never certified: the `adr-0131-organization-ownership` row of `sys_migration` kept `verified_at` null. The post-check waited for the `declared-rule-mirrors` departure on `sys_sharing_rule` to leave. That departure is the uncustomized `managed_by` `package` / `platform` rows with no organization. This release's own served boot writes those rows on every start, including the first v18 boot that the organization-ownership refusal stops, so they could never leave on this release. + - **What it sees now.** `--apply` reports `blocking: 0` and certifies: `verified_at` is set, with `blocking` 0 in the marker every reader checks. The mirror rows are named, counted and kept: + - the plan and the apply print a warning before the first write: `sys_sharing_rule holds N organization-less row(s) of … which stay: …`. The plan file's and the report's `warnings` carry it too; + - in the plan file and in the apply report (`--json` and the `--out` file), the `declared-rule-mirrors` entry of `tables[].departures[]` keeps `id`, `label`, `as` and `rows`, and gains `advisory`: why the rows stay, and what removes them. A departure without `advisory` still blocks; + - `tables[].remaining` no longer counts these rows. The run's `advisory` and the ledger's `advisory` count them, beside the unattributable rows; + - the rows keep `organization_id` NULL, so `sys_sharing_rule`'s `notNull` reads `willReceive: false` with `N row(s) stay NULL on this release, reported as advisory: …`. The plan file used to read `willReceive: true` for that table; + - the human summary prints `N row(s) advisory, not held against certification — …` under the table. Its closing warnings name the unattributable rows and the advisory rows separately; + - nothing is deleted. Every rule row stays as it was. + - **What still blocks.** Every other departure: `global-rung-move`, `template-promotion`, and a host fate supplement's own departures, even one named `declared-rule-mirrors`. Every other remaining row blocks too. + - **Already ran `--apply` and stopped at `blocking: 11`?** Plan again on this release, review the plan, and run `--apply` with it. This change leaves the inventory digest as it was, so a checkpoint recorded under this release's inventory resumes: only the post-check runs, and it certifies. A checkpoint recorded by a release whose inventory differed is superseded: every step runs again, each one idempotent, and the post-check certifies. + - **When blocking returns.** The release that retires the declared sharing-rule seeder (ADR-0131 D2) sets `DECLARED_SHARING_RULE_SEEDER_RUNS` to `false` in the same change. From that release on, these rows hold certification again until its ceremony deletes them. +- cdf3777: The v18 organization-ownership ceremony now completes on a long-lived 17.x database, and `os dev` runs it for you on a local development database (ADR-0131, amendment 2026-10-16). + + **`os dev` upgrades a v17-shape development database in place.** When the boot refuses a local SQLite development database that is still in the v17 organization-ownership shape, `os dev` first copies the database and its `telemetry` sibling (with `-wal` / `-shm`) into `/backups/organization-ownership-/`. It then runs `os migrate organization-ownership --dev` (the plan), applies that plan, and starts the server again. The plan, the apply report and the ceremony's log are kept beside the copy. If a step blocks, `os dev` stops and prints the blocker, where the copy is, and how to resume. `os serve` / `os start`, and any database that is not a local SQLite file, keep the manual ceremony and the boot refusal unchanged. + + **What the ceremony decides for you is printed as a warning before it writes anything.** The plan carries a `warnings` list. The plan prints it, `--apply` prints it again before its first write, and the apply report keeps it. + + **The `telemetry` sibling is covered.** A development boot (or an `OS_TELEMETRY_DB` deployment) keeps `sys_audit_log`, `sys_job_run`, `sys_notification` and the other lifecycle objects in a sibling database. The ceremony now plans, attributes and drops columns for each object in the database it lives in, reading anchors across databases. Before, the boot refusal read those tables in the sibling while the ceremony only read the primary, so an upgraded database could still never boot. A copy of such a table that an older boot left in the primary is listed under `staleCopies`, warned about, and left untouched; it no longer blocks the column drops. + + **The shipped permission sets an install seeded before 2026-08-15 are adopted.** Those installs store the eight shipped sets (`organization_admin`, `member_default`, …) as `managed_by: 'admin'` rows. The authored-conversion step used to refuse them with "rename the authored row". The ceremony now re-owns them as the platform seeds they are and deletes them as mirrors once every grant names its set, so the shipped definitions apply. The warning names each row; a Setup edit to one of them is not carried over, and the pre-upgrade backup keeps it. + + **`os migrate organization-ownership --dev`** composes the stack as `os serve --dev` does: the development auth secret's fallback (so the security and audit plugins), the development `.env*` files, and the `telemetry` sibling. A development boot's refusal now names the command with `--dev`, and every refusal now names an apply the ceremony accepts: `--out PLAN` first, then `--apply --plan-file PLAN --backup-taken`. A bare `--apply` is refused without a reviewed plan file. Before, running the ceremony from a shell without `NODE_ENV=development` composed neither plugin, and its apply blocked on the shipped permission sets and on `sys_audit_log`. + + `OrganizationOwnershipBootGuardPlugin` (`@objectstack/runtime`) takes `{ dev }`; `organizationOwnershipRefusalMessage` and `assertOrganizationOwnershipMigrated` take the same option. + +### Patch Changes + +- 8684079: **Shipped in 18.0.0-next.2**, whose CHANGELOG missed this entry (#23818); listed here one prerelease late. + + fix(metadata-protocol,metadata): on a database the organization-ownership ceremony has not migrated, the paged family reads, `DatabaseLoader`'s count and the package rollback agree with the environment-only reads; the ceremony's apply is named with `--plan-file` (ADR-0131 D6/D7) + + Clause-②: no + + Every change below matters only where legacy organization rows still sit in the `sys_metadata` family, the database `os serve` refuses to start on until `os migrate organization-ownership` has verified. On a migrated or fresh database every answer is unchanged. + + - **`@objectstack/metadata-protocol`: a paged read leaves the legacy rows out before the page is cut.** `auditMetaItem` (`limit`), `listCommits` (`limit`), `revertStoredPackage`'s first-active-row probe and `SysMetadataRepository.list({ limit })` used to filter a legacy organization's rows out after the limit. A page they sat in came back short, and `revertStoredPackage` answered `RESOURCE_CONFLICT` (409) "has never been published" for a package the environment had published when its first active row was an organization's. When a page holds a legacy row, the read is now repeated with those rows excluded in the query. + - **`@objectstack/metadata-protocol`: `rollbackToPackageCommit` refuses a legacy organization's commit by name.** It answered `COMMIT_NOT_FOUND` (404) for a commit that is there. It now gives `revertCommit`'s refusal: `NOT_OVERRIDABLE` (403), the same sentence, naming the commit and its organization. + - **`@objectstack/metadata`: `DatabaseLoader` counts what it reads.** `exists()` answered true for an address only a legacy organization row held, while `load()` answered null, and `queryHistory`'s `total` counted legacy history rows no page serves. Neither counts them now. + - **`@objectstack/metadata-protocol`: internal cleanup.** The organization plumbing that only ever carried the environment (`null`) is removed from the list merge and the served-row resolution. No export changes. + - **`@objectstack/spec`: wording.** The four ADR-0087 entries `18.sys-metadata{,-audit,-commit,-history}-organization-column-retired` now name the ceremony's reviewed apply, `os migrate organization-ownership --out PLAN` and then `--apply --plan-file PLAN`, which `--plan-file`'s own entry requires. No id, category, version or field the migration engine reads changes. + - **`@objectstack/cli`: wording.** `os migrate apply --allow-destructive`'s refusal on an unmigrated database now reads "a drop the organization-ownership ceremony owns, and the ceremony has not verified on this database". Its `--json` payload is unchanged. +- b485122: fix(cli): `os serve` refuses an `OS_DATABASE_URL` whose scheme no driver recognises, reads `pg://` and a scheme-less path as the runtime host does, and opens `sqlite://x.db` as `./x.db` + + Clause-②: no + + This is the `os serve` path a host config takes (one whose `plugins` hold plugin instances), and the one `OS_MODE=off|none|legacy` takes. `os dev` and `os start` reach it through their `serve` child. + + - **Before, an unrecognised scheme (`OS_DATABASE_URL=redis://127.0.0.1:6399/0`).** `os serve --dev` and `os dev` printed `✓ Server is ready` and the banner `Driver: SqlDriver(better-sqlite3) → :memory:`. The database the operator named was dropped without a word, and every write was lost at restart. In production (`os serve`, `os start`) the boot met the no-storage refusal, which tells the operator to set `OS_DATABASE_URL` even though they had set it. + - **After.** Both postures exit 1 before any plugin starts with `✗ Unsupported database URL scheme "redis://" (OS_DATABASE_URL / --database). Supported: …`. The message lists the recognised spellings and names only the scheme, never the URL, so credentials in the URL are not printed. The standalone runtime host (`os dev` / `os start` over a plain config, `os migrate`) already refused these schemes, so the two hosts now agree. A `.db` or `.wasm.db` ending no longer rescues an unrecognised scheme: `redis://h/app.db` and `redis://h/app.wasm.db` used to be handed to SQLite as a file path, and both are now refused. A `*.wasm.db` name still selects the WASM engine for a `file:` / `sqlite:` URL or a path with no scheme. + - **`pg://…` and a path with no scheme (`./data/app`, `/var/lib/objectstack/app`).** The runtime host already reads these as postgres and as a SQLite file. Before, this path booted `--dev` on `:memory:` for them and production met the no-storage refusal. Now `pg://` is a postgres datasource and a scheme-less path is that SQLite file, in dev and production. + - **An opaque scheme (two or more characters and a `:` with no `//`, such as `jdbc:postgresql://h/db` or `redis:h`).** Before, `--dev` booted on `:memory:` for these and production met the no-storage refusal. Now they get the same refusal, naming the scheme (`"jdbc:"`), in both postures. A one-letter drive is a path, not a scheme (`C:\data\app`, `C:/data/app`). A file name whose first segment contains a colon now reads as a scheme too: `backup:2026.db` was a SQLite file, and is now refused. Write `./backup:2026.db` or `file:backup:2026.db` for that file. + - **`sqlite://x.db`.** Before, this path stripped `sqlite:` and opened `//x.db`, a file at the filesystem root. The banner read `→ sqlite://x.db`. The runtime host already reads `sqlite:` and `sqlite://` as aliases of `file:` and opens `./x.db`. This path now uses that same rule (`normalizeDatabaseUrl` from `@objectstack/runtime`), so `sqlite://x.db` and `sqlite:x.db` open `./x.db` and `sqlite:///abs/x.db` opens `/abs/x.db`, on both hosts. `sqlite://x.wasm.db` likewise opens `./x.wasm.db` on the WASM engine. + + `postgres://`, `postgresql://`, `mysql://`, `mongodb://`, `libsql://`, `wasm-sqlite://`, `file:`, `:memory:` and `*.db` paths resolve as before. An unset or blank `OS_DATABASE_URL` keeps the dev SQLite default. An explicit `OS_DATABASE_DRIVER` still wins over the URL, which is then not classified. +- abb6df0: Clause-②: no + + fix(cli): `os storage orphans`, the `os secret rewrap` dry run and the `os migrate summary-nulls` dry run say they write no row or table, and that opening a SQLite file can still switch its journal mode, instead of saying nothing was written + + **What was wrong.** Each of these runs boots through the SQL driver before it prints, and the driver's connect switches a SQLite file to the journal mode it asks for (WAL, unless `OS_DATABASE_SQLITE_JOURNAL_MODE=delete` is set in the command's own environment or the project's `.env`). Measured on a file still on a rollback journal, each run moved the header's journal-mode bytes (18 and 19, from 1,1 to 2,2) and its change counter, and changed no row or table, while printing "nothing was written". Nothing was lost. + + **What changed.** Each sentence below now claims no row or table. Where the run opened a SQLite file, the report's line is followed by the note the other read-only arms already carry: "Opening a SQLite file can still switch its journal mode (to WAL by default), which changes its header only." On PostgreSQL or MySQL the line ends without it. + + - `os storage orphans` (`formatStrandedOrphanInventory`, `@objectstack/service-storage`). FROM: "This pass is READ-ONLY: nothing was written, tombstoned or deleted." TO: "This pass is READ-ONLY: no row or table was written, tombstoned or deleted." and the note. + - `os secret rewrap` (the dry run's note, `REWRAP_NOTES.dryRun`, also in `--json` `report.notes`). FROM: "Dry run: every attributed row was opened, re-sealed and verified in memory, and nothing was written. Re-run with --apply to write." TO: "… and no row or table was written. Re-run with --apply to write." The note already follows the dry run's closing line on a SQLite file, so this sentence carries none. + - `os migrate summary-nulls` (the dry run that found NULL roll-ups, `formatSummaryBackfillReport`, `@objectstack/objectql`). FROM: "Dry run — nothing was written. Re-run with --apply to backfill." TO: "Dry run — no row or table was written. Re-run with --apply to backfill." and the note. + + **Unchanged.** No exit code, flag, prompt, refusal or export signature changes, and no `--json` document gains a field; the note is printed in the text output only. What the driver does on connect is unchanged: once a file is in the mode the driver asks for, these runs leave it byte-identical. +- f7a5ebe: fix(runtime,cli): `os start` and `os dev` refuse a SQLite database whose directory cannot be made, at once, instead of hanging + + Clause-②: no + + **What was wrong.** The standalone host (`createStandaloneStack`, which `os start`, `os dev`'s standalone boot and every embedder run) prepared the directory of a file-backed SQLite database with Node's recursive `mkdirSync`. Node's recursive mkdir never returns when a level answers `ENOENT` although its parent exists: for `OS_DATABASE_URL=file:/proc/os-x/ctl.db` it looped `mkdir("/proc/os-x")` and `mkdir("/proc")` without end, holding the event loop. `os start` printed `→ Starting server...` and hung with no timeout and no message. The `sqlite-wasm` arm (`wasm-sqlite://…`, or `OS_DATABASE_DRIVER=sqlite-wasm`) did the same. `os dev` hung the same way in its own process when its default database lived under a state directory that cannot be made (for example `OS_HOME=/proc/os-x`). + + **What an operator sees now.** The boot exits non-zero within its normal start-up time, naming the database file, the directory and the operating system's error: + + ```text + ✗ [StandaloneStack] SQLite database file '/proc/os-x/ctl.db' cannot be created: its directory '/proc/os-x' could not be made: ENOENT: no such file or directory, mkdir '/proc/os-x' + ``` + + `os dev` with an unusable state directory prints `✗ The default database '…/data/objectstack.db' cannot be created: its directory '…/data' could not be made: …`, followed by the remedy: point `OS_HOME` at a directory that can be created, or choose the database with `--database`. + + Nothing else changes. A creatable path is still made, every missing level of it. The read-only boot (`os migrate plan` over an absent database file) still creates no directory. The refusal names the path, never the database URL. +- 161aee1: fix(cli): an `os serve` boot that binds no HTTP server sends no `objectstack:listening` message and writes no runtime state file, and the banner's `MCP:` row prints only when the MCP route is mounted + + Clause-②: no + + - **Before.** `os serve --no-server`, with no transport composed, sent `{ type: 'objectstack:listening', port, url: 'http://localhost:PORT' }` over an `ipc` channel and wrote `runtime...json` with the same `port` and `url`. Nothing listened on that port: a `GET` to it was refused. The docs define `objectstack:listening` as "The HTTP server is bound". + - **After, for a supervisor of a boot that binds no HTTP server** (`--no-server` with no config-mounted transport, or a transport plugin that did not load). It receives no `objectstack:listening` message. It still receives `objectstack:seed-settled`, which is then the boot's only message, so a parent waiting for the seed to settle does not hang. No `runtime...json` is written, so "is my server running, and where?" gets no answer naming an address. The file's payload, where it is written, is unchanged. + - **After, with an HTTP server composed.** Unchanged: the state file is written first, then the banner prints, then `objectstack:listening` is sent with the port actually bound, and `objectstack:seed-settled` follows it. That includes a `--no-server` boot whose config mounts its own `HonoServerPlugin`. The banner, the IPC message and the state file read the same check, a registered `http.server` service on the booted kernel, so they agree on whether there is an address. + - **The banner's `MCP:` row.** It and its "connect an AI client" hint now also require the plugin that mounts `/api/v1/mcp`, the runtime dispatcher, to be registered on the kernel. `os serve` composes the dispatcher only under `--server`. So a `--no-server` boot whose config mounts its own `HonoServerPlugin` prints `API:` but no longer prints an `MCP:` address, which answered 404 there. +- bb14934: fix(cli): an `os serve` boot that composes no HTTP server now stays up until it is stopped, as its banner's `Press Ctrl+C to stop` says + + Clause-②: no + + - **Before.** `os serve --no-server --preset minimal`, in production with no storage driver (no `OS_DATABASE_URL`), printed the ready banner ending `Press Ctrl+C to stop` and then exited 0 by itself, about 50 ms later, with no signal and without the kernel shutting down. The same command stayed up whenever the storage driver kept a connection pool (`--dev --no-server`, or `--no-server` with `OS_DATABASE_URL` set). Measured, that pool's reaping interval was the only thing keeping those boots alive, so whether a kernel-only boot stayed up depended on the driver. + - **After, for an operator or supervisor of a boot with no HTTP server composed** (`--no-server` with no config-mounted transport, or a transport plugin that did not load). The process stays up after its banner, whatever the driver, until SIGINT, SIGTERM or SIGQUIT. The kernel's own signal handling then drains it and exits with the kernel's code, 0 on a clean shutdown, as on a server boot. No signal listener is added. + - **Why "stay up" and not "run and exit".** The banner says `Press Ctrl+C to stop`. The kernel is built to own the process: a signal drains it, then exits it. The platform's background loops never keep a process alive by themselves, so the host decides the lifetime. The run-and-exit kernel boot already exists, and is explicit: `OS_MIGRATE_AND_EXIT=1`. Every in-repo kernel-only boot that is meant to exit sets it, and every other one is stopped by a signal. + - **Unchanged.** A boot with an HTTP server composed, including a `--no-server` boot whose config mounts its own `HonoServerPlugin`. `OS_MIGRATE_AND_EXIT=1`, which still shuts the kernel down and exits 0 before any banner. The IPC messages and the runtime state file. + - **If you relied on the exit.** A script that ran `os serve --no-server` and waited for the process to end now has to set `OS_MIGRATE_AND_EXIT=1` (bootstrap once, then exit 0), or stop the process with a signal. +- 66d456d: fix(cli): `os serve`'s refusal of a `memory://` or `mingo://` `OS_DATABASE_URL` names the URL's scheme and never prints the URL, so a password or token in it no longer reaches the console + + Clause-②: no + + This is the `os serve` path a host config takes (one whose `plugins` hold plugin instances), and the one `OS_MODE=off|none|legacy` takes. `os dev` and `os start` reach it through their `serve` child. Both schemes name the retired in-memory (mingo) engine, and both accept anything after the `//`, a `user:password@` and a query string included. The refusal interpolated the whole URL, and the CLI prints it as it exits 1. + + With `OS_DATABASE_URL=memory://PLACEHOLDER-USER:PLACEHOLDER-SECRET@127.0.0.1:6399/0?authToken=PLACEHOLDER-TOKEN`: + + - FROM: `✗ OS_DATABASE_URL "memory://PLACEHOLDER-USER:PLACEHOLDER-SECRET@127.0.0.1:6399/0?authToken=PLACEHOLDER-TOKEN" selects the in-memory (mingo) engine, …` + - TO: `✗ An OS_DATABASE_URL with the "memory://" scheme selects the in-memory (mingo) engine, …` + + `mingo://` reads the same way, with `"mingo://"`. These are the runtime host's words for the same URL (`os dev` / `os start` over a plain config, and `os migrate`), so the two hosts print one sentence. The rest of the sentence, which names the SQLite replacements (`os dev --fresh`, and the URL `:memory:`), is unchanged. + + What does not change: both schemes are still refused, with the same error class and exit code, in dev and production; every other URL resolves as before, to the same driver. +- 34b740f: fix(runtime,cli): an upper-case `FILE:` database URL opens the file its lower-case spelling opens, `file://NAME` opens `NAME` on both hosts, and the URL-to-filename rule is exported as `sqliteFilenameFromUrl` + + Clause-②: yes (widening) + + - **`FILE:` is stripped case-insensitively.** The standalone host classified `FILE:./app.db` as SQLite case-insensitively but stripped the prefix case-sensitively, so the prefix stayed in the file name: `OS_DATABASE_URL=FILE:./app.db` created a directory literally named `FILE:.` and put the database inside it, and `File:///tmp/app.db` was refused as an unsupported `File://` scheme. Both now open the file the lower-case spelling opens (`./app.db`, `/tmp/app.db`), as the `sqlite-wasm` path already did. Lower-case `file:` URLs and bare paths are unchanged. + - **New export: `sqliteFilenameFromUrl(url, kind)`** from `@objectstack/runtime`, `kind` being `'sqlite'` or `'sqlite-wasm'`. It is the one rule that turns a SQLite database URL (after `normalizeDatabaseUrl`) into a file name: `file:` / `file://` stripped case-insensitively, plus `wasm-sqlite:` for the WASM kind. For `'sqlite'` it throws a plain `Error` on a URL that is not a file path, naming the URL's scheme and never the URL. Nothing is removed. + - **`os serve`'s legacy storage path (`OS_MODE=off`, `bootMode: 'off'`, or a host config) turns a SQLite URL into a file by that same rule**, so both hosts open one file per URL. It used to strip only a lower-case `file:`: `OS_DATABASE_URL=file://NAME` opened `//NAME` at the filesystem root, where the standalone host opens `./NAME`, and `FILE:./app.db` made a `FILE:.` directory there too. `file:///abs/app.db` opens `/abs/app.db`; `sqlite:` / `sqlite://` URLs are unchanged. + - **What that rule refuses, the legacy path now refuses too, and fatally**, with the standalone host's own sentence (it names the URL's scheme, never the URL): an explicit `OS_DATABASE_DRIVER=sqlite` over a server URL such as `postgres://user:password@host/db`, which used to create directories named after the URL, credentials included, and a `file:` URL that names no file, which used to declare a database with an empty file name. Fix: point `OS_DATABASE_URL` at a file (`file:./app.db`), or select the driver the URL is for. The undocumented `sql://` prefix that path also stripped under an explicit `sqlite` selection is refused the same way; write `file:` instead. A blank `OS_DATABASE_URL` under an explicit `sqlite` selection is read as absent (`:memory:`), as an absent one always was. +- a31ff2e: Clause-②: no + + `os init` (every template: `app`, `plugin`, `empty`) and `os create plugin` now emit + `"overrides": { "vitest": "~4.0.0" }` in the scaffolded `package.json`, so the project installs on + npm 10, the npm Node 22 bundles. Since vitest 4.1.12 shipped a peer set that contradicts itself, + `npm install` of a fresh `os init` project crashed on npm 10.9.x with + `Cannot read properties of null (reading 'edgesOut')`. The override routes every `vitest` edge in + the npm graph to the range the scaffold declares; npm 11 installs the same tree, and pnpm ignores + the field. + + The override equals `devDependencies.vitest` wherever the manifest declares vitest directly, + because npm refuses an override that differs from the direct dependency (`EOVERRIDE`). In an `app` + or `empty` project, add vitest at the same range (`npm install -D vitest@~4.0.0`); a bare + `npm install -D vitest` is refused with `EOVERRIDE` while the override is in place. Existing + projects can add the same `overrides` block by hand. `os create plugin --in-repo` is unchanged. +- 41e84c0: fix(cli): `os dev` (and `pnpm dev` on a scaffold) prints why a watched save was refused, not only `✗ compile failed` + + Clause-②: no + + When a saved file makes the compile refuse, the watcher now prints the compile's output under its failure line: the same lines `pnpm build` prints on that tree, without `--verbose`. + + - **Before.** On a fresh `os init my-app -t app` scaffold (namespace `my_app`), saving an object named `task` printed `↻ recompiling (2 files)...` and then a bare `✗ compile failed (3782ms)`. The reason never appeared. The compile writes its refusals to stdout, and the watcher discarded stdout and printed stderr only. + - **After.** The failure line is followed by the compile's stdout, then its stderr. For that save it shows `✗ Object 'task' is missing the package namespace prefix. Rename it to 'my_app_task' (namespace = 'my_app').` and the lines around it. + + What does not change: a successful recompile prints exactly what it printed before, and the compile's progress output stays hidden on success. `--verbose` still passes the compile's output straight through, so on a failure nothing is printed twice. A failed compile still writes no artifact and still triggers no restart. +- 98ba1bf: fix(cli): `os migrate organization-ownership --apply` refuses an `--out` it cannot write before it executes anything + + Clause-②: no + + **What was wrong.** The apply wrote its report file only after the ceremony had run, with an exclusive create. Handed an `--out` that already existed (a re-run with the same path), it executed every step (attribution, the id-to-name rewrite, mirror deletion, the column drops) and only then exited 1 with `apply_refused` / `report-file-unwritable`, the same `--json` code a refusal before any write prints. An operator read a completed ceremony as one that never ran. + + **What an operator now sees.** + + - An `--out` that cannot be written (an existing file, a directory that does not exist, no write permission) is refused before the database is opened: exit 1, `{"error":"apply_refused","reason":"report-file-unwritable","file":"FILE","code":"EEXIST",…}`, and nothing is executed. The existing file is left as it was. Re-run with `--out` naming a new path. + - The report file is reserved (created exclusively) before the boot, and the report is written into that same file at the end, so no other process can take the path in between. Until then the file holds a short JSON note (`"kind": "organization-ownership-apply-reservation"`) saying a run reserved it. A run that is refused, fails a step or cannot boot removes it again; a run killed before its report leaves that note behind, never an empty file. + - A report that still cannot be kept after the ceremony executed (a write error, or the reserved file removed while the run went on) gets its own code: exit 1, `{"error":"apply_report_unwritable","reason":"report-write-failed","executed":true,"report":{…}}`, with the full report in the payload. The text output still says "The ceremony ran". Re-run `--apply` with a new `--out` to write the report; finished steps are not repeated. + - Without `--out`, the default `organization-ownership-apply-TIMESTAMP.json` is stamped with the moment the run reserves it (before the boot), no longer with the moment the report was generated. + + The plan run (without `--apply`) is unchanged: it changes nothing in the database, so writing its file last was never a hazard. +- 570c7a2: fix(cli): `os serve` no longer hangs after binding its port when its runtime state file's directory can never be made + + Clause-②: no + + This covers the runtime state file that the entry `fix(cli,runtime): a state, output or cache directory that can never be made is refused at once, naming the path and the operating system's code, instead of hanging` lists as `Not covered yet`. + + **What was wrong.** Once listening, `os serve` writes its runtime state file, `runtime...json`, into `OS_HOME` before it prints the ready banner. It made that directory with Node's recursive `mkdir`, which never returns when a level answers `ENOENT` although its parent exists, as `/proc` and `/sys` answer for a name they do not have. Under such an `OS_HOME`, with the database outside it, the server bound its port and then looped in its own process, holding the event loop: no `Press Ctrl+C to stop`, no listening message to an `os dev` parent, and no request answered. + + **What an operator sees now.** The directory is made with the same bounded walk as the other `OS_HOME` paths, so a directory that can never be made is refused at once. The state file has always been best-effort: the boot goes on without it and prints its banner, as it already did when the file could not be written for any other reason. + + Nothing else changes. Every state file written before is still written, in the same place, before the banner; a home that can be made is still made, every missing level of it. +- 2e15620: fix(cli,runtime): a state, output or cache directory that can never be made is refused at once, naming the path and the operating system's code, instead of hanging + + Clause-②: no + + **What was wrong.** These commands and runtime paths created their directories with Node's recursive `mkdir`. Node's recursive mkdir never returns when a level answers `ENOENT` although its parent exists — what `/proc` and `/sys` answer for a name they do not have. It looped `mkdir("/proc/x")` and `mkdir("/proc")` without end, holding the event loop, so the command printed nothing more and no timeout could fire: + + - `os start` with `--home` / `OS_HOME` on such a path hung after its header. + - `os serve` hung the same way on the empty-boot artifact stub under `OS_HOME`, and on the cache of an `http(s):` `OS_ARTIFACT_URL` under `OS_HOME`. + - `os create --dir`, `os init `, `os compile --output`, `os generate` (`--dir`, and `--output` for types, client and migration), `os i18n extract --out` and `os plugin build --out` hung on an output path there. + - `os login` and `os cloud login` never finished writing credentials under a `HOME` there, and the package enable / disable state the packages routes write under `OS_HOME` held the event loop of the host serving them. + + **What an operator sees now.** The command exits non-zero at once with the operating system's own error, which names the level that cannot be made: + + ```text + ✗ Cannot create home directory at /proc/os-x: ENOENT: no such file or directory, mkdir '/proc/os-x' + ``` + + Not covered yet: the runtime state file `os serve` writes into `OS_HOME` once it is listening. + + Nothing else changes. Every directory these paths made before is still made, every missing level of it; every path that was refused before is still refused, with the operating system's code. The one difference is the path that was never answered at all. +- a33f3d5: Clause-②: no + + fix(cli): `os migrate plan`, a refused `os migrate summary-nulls --recompute-undefined-on-empty` name and every `os secret rewrap` run say what they vouch for, and that opening a SQLite file can still switch its journal mode, instead of saying nothing was written + + **What was wrong.** Each of these runs boots through the SQL driver before it prints, and the driver's connect switches a SQLite file to the journal mode it asks for (WAL, unless `OS_DATABASE_SQLITE_JOURNAL_MODE=delete` is set in the command's own environment or the project's `.env`). Measured on a file still on a rollback journal, each run moved the header's journal-mode bytes (18 and 19, from 1,1 to 2,2) and its change counter, and changed no row or table, while printing "a plan writes nothing", "nothing was written" or "a finished run writes nothing". Nothing was lost. + + **What changed.** Each sentence below now names what it vouches for. On a SQLite file, its line is followed by the note the other read-only arms already carry: "Opening a SQLite file can still switch its journal mode (to WAL by default), which changes its header only." On PostgreSQL or MySQL the line ends without it. + + - `os migrate plan`, the unmanaged-table section. FROM: "They are reported for information only — nothing here drops them, and a plan writes nothing." TO: "… and a plan writes no row or table." and the note. + - `os migrate plan`, the declaration boot's note when a plugin's write was refused. FROM: "Refused N write(s) during the declaration boot — a plan writes nothing: …" TO: "… — a plan writes no row or table: …", and the note at the end of that note's line. + - `os migrate plan`, the same note when a raw command was forwarded. FROM: "If those commands only read, nothing was written; if one wrote, it landed." TO: "If those commands only read, no row or table was written; if one wrote, it landed.", and the note at the end of that note's line. + - `os migrate summary-nulls --recompute-undefined-on-empty` naming a roll-up the run cannot find (`backfillSummaryNulls`, `@objectstack/objectql`), in either mode. FROM: "Refused before any row was read; nothing was written." TO: "Refused before the walk read or wrote any row." and the note. Under `--apply` the boot before it can also create a table a never-started database lacks, so the sentence speaks for the walk only. + - `os secret rewrap`, the "Re-running is safe" note, in both modes and in `--json` `report.notes`. FROM: "… and a finished run writes nothing." TO: "… and a finished run writes no sys_secret row." Under `--apply` the note follows it; a dry run's closing line already carries it. A finished `--apply` run on a database that has never been started also creates its tables, so the sentence names the rows the re-wrap writes. + + **Unchanged.** No exit code, flag, prompt, refusal or export signature changes, and no `--json` document gains a field: the `--json` documents carry the changed sentences, and the note is printed in the text output only. What the driver does on connect is unchanged. +- c4111c6: fix(cli,driver-turso): `os migrate plan` and `os migrate apply` on a Turso REMOTE database no longer offer `--allow-destructive` for a change that database cannot apply + + Clause-②: yes (widening) + + On a primary reached over the Turso REMOTE transport (`libsql://…`), `os migrate plan` listed a NOT NULL tightening with the remedy `Run "os migrate apply --allow-destructive".` and closed with `(add --allow-destructive for drops / tightenings)`. That transport runs no SQLite table rebuild, so the apply then named the tightening as not applied and exited 1. The plan now says so before the apply: each change the database cannot apply (a tightened or relaxed NOT NULL, a dropped DEFAULT) is still listed, with its finding and `"os migrate apply" cannot apply it on this database: it needs the SQLite table rebuild, which the Turso REMOTE transport does not run.` in place of the remedy. The footer offers `--allow-destructive` only for the destructive changes the database can apply (a column drop, an index change) and says how many listed changes remain drift after the apply. The listing `os migrate apply` prints before its prompt reads the same way. Its counts of held-back destructive changes, before the prompt and after the run, offer a re-run with `--allow-destructive` only for the ones that re-run could apply, and name the rest as drift it cannot apply either. On every other database both commands read exactly as before, and their `--json` documents are unchanged on every database. + + `@objectstack/driver-turso`: `TursoDriver.driftApplyLimit(opType)` is new. It returns why the driver's `applyMigrationEntries` cannot perform a drift op of that type on this face, or `null` when it can. It answers from the same per-op declaration the REMOTE apply door dispatches by, and returns `null` for every op on the local and embedded-replica faces. A driver that does not implement it is read as applying every op the differ means it to. + + `@objectstack/driver-sql`: `driftFindingWithoutRemedy(entry)` is a new export. It returns a drift entry's finding without the `os migrate apply --allow-destructive` sentence that a destructive column entry's message ends with, and returns every other message unchanged; the messages themselves keep their exact text. +- 27d7c2a: `os serve --no-server` no longer reports an ordering fault for the datasource admin routes or for the unknown-hostname guard. A boot that composes no HTTP server used to print, in its boot diagnostics, `WARN [datasource-admin] route registration failed: [Kernel] Service 'http.server' not found (… a composition/ordering fault; registration order is not a contract.)`, although nothing was at fault. With `OS_ROOT_DOMAIN` set, the same boot also printed `WARN [unknown-hostname-guard] install failed` with the same kernel text. Each now prints its own degrade line, beside the same boot's other "not mounted" lines: `WARN [datasource-admin] http.server unavailable; REST routes not installed`, and `WARN [unknown-hostname-guard] http.server unavailable; guard not installed`. + + What still warns: on a boot that does compose an HTTP server, a route registration that throws still prints `[datasource-admin] route registration failed` with the error, and a guard install that throws still prints `[unknown-hostname-guard] install failed` with the error. A composed boot mounts `/api/v1/datasources` and installs the hostname guard as before, including under `--no-server` when the config mounts its own `HonoServerPlugin`. Both plugins now read only the canonical `http.server` service. Their old fallback to the deprecated `http-server` alias never ran: on a boot without `http.server` the canonical read threw first. So every boot that mounted these routes or installed the guard before still does. +- bef7bdc: fix(cli): `os migrate organization-ownership` plans a v17 database that still holds the retired `sys_scim_provider` table, listing it as fate 4 instead of refusing it (ADR-0131 D10) + + Clause-②: no + + `sys_scim_provider` was retired from the platform before the ceremony's inventory was taken, so the inventory had no row for it. Its ADR-0087 entry (`scim-provider-object-retired`) leaves the table in place, untouched, by ruling, so every database that booted a release from before the retirement still holds it, `organization_id` column included. The plan refused such a database with `uninventoried-platform-table` (`sys_scim_provider carries organization_id but the ADR-0131 D10 inventory gives it no fate`), and so did `--apply`, so the ceremony could not run there at all and `os dev` / `os serve` stayed refused. + + - **Now** the inventory lists `sys_scim_provider` with fate 4 (`report`), under a new census population, `retired-before-census`. The plan reports its rows and its NULL-organization rows by id. It never attributes them, not even to the Default Organization under `single`, and never deletes them. + - **NOT NULL:** the table is exempt, so the plan reports it as not receiving NOT NULL. The object is retired, no declaration anchors tenancy on the column, and a SCIM connection scoped to no organization was a legitimate row. + - The plan's `inventoryDigest` changes with the new row. A plan file written by an earlier CLI no longer matches, so `--apply --plan-file` refuses it with `plan-digest-mismatch`. Re-run `os migrate organization-ownership --out PLAN`, review it, and apply that. +- Updated dependencies [e375315] +- Updated dependencies [cbbd96a] +- Updated dependencies [b6805ac] +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [b9dbc3d] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [1eac7ad] +- Updated dependencies [9fcea20] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [14aff67] +- Updated dependencies [7428da0] +- Updated dependencies [b55be42] +- Updated dependencies [abb6df0] +- Updated dependencies [5b6c257] +- Updated dependencies [0b1179f] +- Updated dependencies [8776119] +- Updated dependencies [74b4362] +- Updated dependencies [f7a5ebe] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9d4fb3c] +- Updated dependencies [c5cf546] +- Updated dependencies [9cb9d95] +- Updated dependencies [cf07168] +- Updated dependencies [d795719] +- Updated dependencies [f846cc2] +- Updated dependencies [34b740f] +- Updated dependencies [30ce56e] +- Updated dependencies [2e0e451] +- Updated dependencies [94114c1] +- Updated dependencies [df2c551] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [0cfd0c4] +- Updated dependencies [ae4dfaf] +- Updated dependencies [099cc9d] +- Updated dependencies [77efc66] +- Updated dependencies [747c8e9] +- Updated dependencies [6422da5] +- Updated dependencies [6422da5] +- Updated dependencies [2034975] +- Updated dependencies [61e5ee9] +- Updated dependencies [8f4260f] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [67e8792] +- Updated dependencies [2e15620] +- Updated dependencies [6d1660d] +- Updated dependencies [0cb45e9] +- Updated dependencies [0b69564] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [b74434c] +- Updated dependencies [bbdfc05] +- Updated dependencies [605aa01] +- Updated dependencies [a33f3d5] +- Updated dependencies [6e18016] +- Updated dependencies [d050a8f] +- Updated dependencies [9e797a8] +- Updated dependencies [b25d98a] +- Updated dependencies [75ced06] +- Updated dependencies [75ced06] +- Updated dependencies [5fb8d15] +- Updated dependencies [c4111c6] +- Updated dependencies [2054a08] +- Updated dependencies [5c2ac83] +- Updated dependencies [4ffdebe] +- Updated dependencies [33228c5] +- Updated dependencies [d731b89] +- Updated dependencies [19955fb] +- Updated dependencies [905f57b] +- Updated dependencies [aefab7c] +- Updated dependencies [66d2757] +- Updated dependencies [c7168ac] +- Updated dependencies [44fe210] +- Updated dependencies [e246f06] +- Updated dependencies [a65a59b] +- Updated dependencies [b75129f] +- Updated dependencies [6dede7a] +- Updated dependencies [077b75f] +- Updated dependencies [62f959d] +- Updated dependencies [c1c0a90] +- Updated dependencies [ab3ba2c] +- Updated dependencies [d6bd98a] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [544b8d7] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] +- Updated dependencies [265ef64] +- Updated dependencies [a69a40a] +- Updated dependencies [e66f13c] +- Updated dependencies [cdf3777] + - @objectstack/lint@18.0.0-next.3 + - @objectstack/spec@18.0.0-next.3 + - @objectstack/service-storage@18.0.0-next.3 + - @objectstack/plugin-security@18.0.0-next.3 + - @objectstack/rest@18.0.0-next.3 + - @objectstack/runtime@18.0.0-next.3 + - @objectstack/service-job@18.0.0-next.3 + - @objectstack/metadata-core@18.0.0-next.3 + - @objectstack/service-automation@18.0.0-next.3 + - @objectstack/metadata-protocol@18.0.0-next.3 + - @objectstack/metadata@18.0.0-next.3 + - @objectstack/objectql@18.0.0-next.3 + - @objectstack/plugin-hono-server@18.0.0-next.3 + - @objectstack/plugin-audit@18.0.0-next.3 + - @objectstack/driver-sql@18.0.0-next.3 + - @objectstack/platform-objects@18.0.0-next.3 + - @objectstack/plugin-approvals@18.0.0-next.3 + - @objectstack/plugin-auth@18.0.0-next.3 + - create-objectstack@18.0.0-next.3 + - @objectstack/service-messaging@18.0.0-next.3 + - @objectstack/driver-turso@18.0.0-next.3 + - @objectstack/plugin-sharing@18.0.0-next.3 + - @objectstack/service-settings@18.0.0-next.3 + - @objectstack/client@18.0.0-next.3 + - @objectstack/cloud-connection@18.0.0-next.3 + - @objectstack/core@18.0.0-next.3 + - @objectstack/plugin-email@18.0.0-next.3 + - @objectstack/service-package@18.0.0-next.3 + - @objectstack/console@18.0.0-next.3 + - @objectstack/mcp@18.0.0-next.3 + - @objectstack/service-mailbox@18.0.0-next.3 + - @objectstack/account@18.0.0-next.3 + - @objectstack/setup@18.0.0-next.3 + - @objectstack/driver-memory@18.0.0-next.3 + - @objectstack/driver-mongodb@18.0.0-next.3 + - @objectstack/driver-sqlite-wasm@18.0.0-next.3 + - @objectstack/formula@18.0.0-next.3 + - @objectstack/observability@18.0.0-next.3 + - @objectstack/plugin-webhooks@18.0.0-next.3 + - @objectstack/service-analytics@18.0.0-next.3 + - @objectstack/service-cache@18.0.0-next.3 + - @objectstack/service-datasource@18.0.0-next.3 + - @objectstack/service-queue@18.0.0-next.3 + - @objectstack/service-realtime@18.0.0-next.3 + - @objectstack/service-sms@18.0.0-next.3 + - @objectstack/trigger-api@18.0.0-next.3 + - @objectstack/trigger-record-change@18.0.0-next.3 + - @objectstack/trigger-schedule@18.0.0-next.3 + - @objectstack/types@18.0.0-next.3 + - @objectstack/verify@18.0.0-next.3 + - @objectstack/plugin-pinyin-search@18.0.0-next.3 + ## 18.0.0-next.2 ### Minor Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index 9e6da68a536..cd6a407b93e 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/cli", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "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 cc0262e0fbd..be281c19ce9 100644 --- a/packages/client-react/CHANGELOG.md +++ b/packages/client-react/CHANGELOG.md @@ -1,5 +1,54 @@ # @objectstack/client-react +## 18.0.0-next.3 + +### Patch Changes + +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [6422da5] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [6d1660d] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [4ffdebe] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [e246f06] +- Updated dependencies [a65a59b] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/client@18.0.0-next.3 + - @objectstack/core@18.0.0-next.3 + ## 18.0.0-next.2 ### Patch Changes diff --git a/packages/client-react/package.json b/packages/client-react/package.json index aa9e74a3eae..d78cd859406 100644 --- a/packages/client-react/package.json +++ b/packages/client-react/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/client-react", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "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 3d8446da41a..03a9a71ccb7 100644 --- a/packages/client/CHANGELOG.md +++ b/packages/client/CHANGELOG.md @@ -1,5 +1,96 @@ # @objectstack/client +## 18.0.0-next.3 + +### Patch Changes + +- 6d1660d: Clause-②: no + + fix(client): `client.automation.getRuntimeStatus()` types its rows as the declared contract `FlowRuntimeState` (#23859) + + **What was wrong.** The method declared its answer as `{ flows: Array<{ name, enabled, bound }>; total }`. `GET /automation/_status` serves the automation engine's `getFlowRuntimeStates()` rows unchanged, and each row is the contract `FlowRuntimeState` from `@objectstack/spec/contracts`. The SDK's type therefore hid five optional fields the wire already carries: `status`, `triggerType`, `object`, `reason` (one sentence saying why an enabled, triggered flow is not armed) and `unboundCause` (which of the closed `FLOW_UNBOUND_CAUSES` that sentence describes). A caller that read `row.reason` or `row.unboundCause` got a type error and either cast past it or concluded the field did not exist. + + **What changes.** The return type is now `Promise<{ flows: FlowRuntimeState[]; total: number }>`. `name`, `enabled` and `bound` stay required, so existing callers compile unchanged. Read `row.unboundCause` to classify an unarmed flow, and render `row.reason` without parsing it. The request, the route and the values on the wire are unchanged. +- 66d2757: feat(spec)!: every installed-package row declares where it came from, a closed `source: 'code' | 'marketplace' | 'authored'`, and the `GET /packages` rows declare the `writable` verdict they already served (#23972) + + Clause-②: yes (narrowing — the served row gains two declared keys, and the record schema, the served-row union and the registry's installPackage signature each refuse what they accepted before) + + **BREAKING** — three published accept sets narrow. `InstalledPackageSchema` refuses a row without `source`, or with a value outside `code` / `marketplace` / `authored`. The served-row union `InstalledPackageAtEitherStageSchema` refuses a row without a boolean `writable`. `SchemaRegistry.installPackage` and `EngineSchemaRegistryView.installPackage` take `source` as their second argument and refuse a call without it. The migration is below. None of it is mechanically convertible, because only the door that registers a package knows where it came from. + + **What is new on the wire.** `GET /api/v1/packages` and `GET /api/v1/packages/:id` serve two declared keys on every row: + + - `source` names the package's origin. `code` means the deployment's own code registered it (a `defineStack()` host, a plugin's bundled manifest, a platform package). `marketplace` means the marketplace install-local plugin installed it, and that holds after a restart too, when the plugin rehydrates it from its ledger. `authored` means it is a database-authored package (a Studio base, a duplicate, an HTTP install), including after the restart that rehydrates it from `sys_packages`. The door that registers the package stamps the value. No reader derives it. + - `writable` is the server's own writability verdict (ADR-0070 D2), served on these rows since #14375 and now declared. It is computed per read and never stored, so it is declared on the served row (`InstalledPackageAtEitherStageSchema`) and not on the registry record. + + FROM → TO for a reader of these rows: + + | you did | do instead | + | --- | --- | + | treated `writable: true` as authored, and looked up each non-writable row in the marketplace's installed list (the control plane's, else install-local's) to tell marketplace from code | read `row.source` | + | read the undeclared `writable` | read the declared `writable` (unchanged value) | + + The two keys are separate statements: `source` says where the package came from, and `writable` says what this server lets an org do to it. + + **`InstalledPackageSchema` requires `source`.** It is `InstalledPackageSourceSchema`, exported from `@objectstack/spec/kernel`. A row without it, or with a value outside the closed set, is refused at the `source` key. + + **API change for code that registers packages (`@objectstack/objectql`).** The registry records the origin and never infers it: + + | you wrote | write instead | + | --- | --- | + | `registry.installPackage(manifest)` | `registry.installPackage(manifest, 'code')` (or `'marketplace'` / `'authored'`, whichever is where the package came from) | + | `registry.installPackage(manifest, settings, scope)` | `registry.installPackage(manifest, source, settings, scope)` | + | `ctx.getService('manifest').register(artifact)` for a marketplace install | `ctx.getService('manifest').register(artifact, { source: 'marketplace' })` | + + `SchemaRegistry.installPackage` throws a `TypeError` naming the closed set when `source` is missing or unknown, before anything is recorded. `ObjectQL.registerApp(body, scope?, source?)` and the `manifest` service record `'code'` when no source is given. They accept `'code'` or `'marketplace'` and refuse `'authored'`. The `IObjectQLEngine` registry contract (`@objectstack/spec/contracts`) declares the same `installPackage(manifest, source, settings?)`. + + **The one-line fix:** pass the package's origin as `installPackage`'s second argument. + + Readers: objectui's Setup → Packages Source column and its `?source=` filter (it switches from the two-read join to `source` in its own follow-up), and the client SDK's `packages.list()` / `packages.get()` row types. + + +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [6422da5] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [4ffdebe] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [e246f06] +- Updated dependencies [a65a59b] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/core@18.0.0-next.3 + ## 18.0.0-next.2 ### Minor Changes diff --git a/packages/client/package.json b/packages/client/package.json index 1af6b1f4c14..c51216218ab 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/client", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "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 0f9a1e5b63d..cd655bd48bc 100644 --- a/packages/cloud-connection/CHANGELOG.md +++ b/packages/cloud-connection/CHANGELOG.md @@ -1,5 +1,150 @@ # @objectstack/cloud-connection +## 18.0.0-next.3 + +### Minor Changes + +- 75ced06: fix(cloud-connection)!: the cloud-connection and install-local routes refuse a declared JSON body that does not parse with `400 INVALID_REQUEST`, instead of answering as if no body was sent + + Clause-②: no (narrowing) + + + + **BREAKING** (an accept-set narrowing), shipped as `minor` under the launch-window convention for breaking changes. + + `POST /api/v1/cloud-connection/bind/start`, `/bind/poll`, `/install` and `POST /api/v1/marketplace/install-local` read their bodies as `try { body = await c.req.json() } catch { body = {} }`, so a truncated body was answered as if the client had sent none: `/bind/poll` answered `400 "device_code is required"` for a body that carried `device_code` and lacked a closing brace. + + What changes for an integrator. A body sent as `application/json` (matched case-insensitively) that is not blank and does not parse: FROM the route's answer for an empty body, TO `400` with `{ "success": false, "error": { "code": "INVALID_REQUEST", "message": "The request body could not be parsed as JSON (it was sent as application/json). It was refused rather than read as an empty body: send a well-formed JSON document, or no body at all." } }`. Install-local refuses it after its sign-in and capability checks and before anything is fetched, registered or written. + + **Remedy, in one line.** Send a JSON body as well-formed JSON with `Content-Type: application/json`, or send no body. + + **Unchanged.** An empty or whitespace-only JSON body is still no body, and every route answers it as before (for `/bind/poll`, `400 "device_code is required"`). A body that parses, and a body that does not declare JSON, are read as before. +- c7168ac: Clause-②: yes (widening) + + fix(service-package,cloud-connection): a stored or installed package body converts forward under ADR-0087 D2 at the `sys_packages` boot hydration and at the marketplace install, and one refused `sys_packages` record no longer stops the hydration of the rest + + The registration door (`ObjectQL.convertPackageBodyForward`, `@objectstack/objectql`) already replays the versioned forward conversion for a package body that declares its `engines.protocol` range. Two more doors that load a body at rest now go through that same entry point, before the registry judges the body: + + - **`sys_packages` boot hydration** (`PackageServicePlugin.start`). A stored record whose range admits this runtime, built before a retirement (for example a `type: 'home'` page with `isDefault: true`), now hydrates converted, with the engine's named warning, where the registry used to refuse it. The stored row is not rewritten, so it converts again at every boot. + - A record the registry still refuses is reported **by name** at `error`, with the refusal attached, and the records after it keep hydrating. Before, the first refusal threw out of the whole loop to a `debug` line, and every durable package after it was absent from the registry with nothing saying so. + - `Hydrated N package(s) from sys_packages into registry` counts the records the registry accepted in that pass. + - The ADR-0087 D1 handshake still runs first: a record whose declared range excludes this runtime's major is refused by it, named, as before. + - **The marketplace install** (`MarketplaceInstallLocalPlugin`, install and boot rehydrate). The body is converted once per load, and that one converted body is what `manifest.register`, the handler binder, the side effects (translations, seed datasets) and the rehydrate's sample-data heal read. Before, `register` converted its own copy while the side readers read the raw ledger entry. The ledger keeps the body as installed. + + Nothing is narrowed. A body with no declared range (a manifest built in code) meets the registry's refusals exactly as before at both doors. The install primitive `protocol.installPackage` (`POST /api/v1/packages`) is authoring-side and stays strict: the registry judges the manifest as handed. + +### Patch Changes + +- 66d2757: feat(spec)!: every installed-package row declares where it came from, a closed `source: 'code' | 'marketplace' | 'authored'`, and the `GET /packages` rows declare the `writable` verdict they already served (#23972) + + Clause-②: yes (narrowing — the served row gains two declared keys, and the record schema, the served-row union and the registry's installPackage signature each refuse what they accepted before) + + **BREAKING** — three published accept sets narrow. `InstalledPackageSchema` refuses a row without `source`, or with a value outside `code` / `marketplace` / `authored`. The served-row union `InstalledPackageAtEitherStageSchema` refuses a row without a boolean `writable`. `SchemaRegistry.installPackage` and `EngineSchemaRegistryView.installPackage` take `source` as their second argument and refuse a call without it. The migration is below. None of it is mechanically convertible, because only the door that registers a package knows where it came from. + + **What is new on the wire.** `GET /api/v1/packages` and `GET /api/v1/packages/:id` serve two declared keys on every row: + + - `source` names the package's origin. `code` means the deployment's own code registered it (a `defineStack()` host, a plugin's bundled manifest, a platform package). `marketplace` means the marketplace install-local plugin installed it, and that holds after a restart too, when the plugin rehydrates it from its ledger. `authored` means it is a database-authored package (a Studio base, a duplicate, an HTTP install), including after the restart that rehydrates it from `sys_packages`. The door that registers the package stamps the value. No reader derives it. + - `writable` is the server's own writability verdict (ADR-0070 D2), served on these rows since #14375 and now declared. It is computed per read and never stored, so it is declared on the served row (`InstalledPackageAtEitherStageSchema`) and not on the registry record. + + FROM → TO for a reader of these rows: + + | you did | do instead | + | --- | --- | + | treated `writable: true` as authored, and looked up each non-writable row in the marketplace's installed list (the control plane's, else install-local's) to tell marketplace from code | read `row.source` | + | read the undeclared `writable` | read the declared `writable` (unchanged value) | + + The two keys are separate statements: `source` says where the package came from, and `writable` says what this server lets an org do to it. + + **`InstalledPackageSchema` requires `source`.** It is `InstalledPackageSourceSchema`, exported from `@objectstack/spec/kernel`. A row without it, or with a value outside the closed set, is refused at the `source` key. + + **API change for code that registers packages (`@objectstack/objectql`).** The registry records the origin and never infers it: + + | you wrote | write instead | + | --- | --- | + | `registry.installPackage(manifest)` | `registry.installPackage(manifest, 'code')` (or `'marketplace'` / `'authored'`, whichever is where the package came from) | + | `registry.installPackage(manifest, settings, scope)` | `registry.installPackage(manifest, source, settings, scope)` | + | `ctx.getService('manifest').register(artifact)` for a marketplace install | `ctx.getService('manifest').register(artifact, { source: 'marketplace' })` | + + `SchemaRegistry.installPackage` throws a `TypeError` naming the closed set when `source` is missing or unknown, before anything is recorded. `ObjectQL.registerApp(body, scope?, source?)` and the `manifest` service record `'code'` when no source is given. They accept `'code'` or `'marketplace'` and refuse `'authored'`. The `IObjectQLEngine` registry contract (`@objectstack/spec/contracts`) declares the same `installPackage(manifest, source, settings?)`. + + **The one-line fix:** pass the package's origin as `installPackage`'s second argument. + + Readers: objectui's Setup → Packages Source column and its `?source=` filter (it switches from the two-read join to `source` in its own follow-up), and the client SDK's `packages.list()` / `packages.get()` row types. + + +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [9fcea20] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [14aff67] +- Updated dependencies [7428da0] +- Updated dependencies [b55be42] +- Updated dependencies [abb6df0] +- Updated dependencies [8776119] +- Updated dependencies [f7a5ebe] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9d4fb3c] +- Updated dependencies [c5cf546] +- Updated dependencies [9cb9d95] +- Updated dependencies [cf07168] +- Updated dependencies [f846cc2] +- Updated dependencies [34b740f] +- Updated dependencies [30ce56e] +- Updated dependencies [df2c551] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [ae4dfaf] +- Updated dependencies [6422da5] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2e15620] +- Updated dependencies [0cb45e9] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [605aa01] +- Updated dependencies [a33f3d5] +- Updated dependencies [6e18016] +- Updated dependencies [d050a8f] +- Updated dependencies [9e797a8] +- Updated dependencies [b25d98a] +- Updated dependencies [4ffdebe] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [e246f06] +- Updated dependencies [a65a59b] +- Updated dependencies [6dede7a] +- Updated dependencies [077b75f] +- Updated dependencies [62f959d] +- Updated dependencies [d6bd98a] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] +- Updated dependencies [cdf3777] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/runtime@18.0.0-next.3 + - @objectstack/metadata-core@18.0.0-next.3 + - @objectstack/metadata-protocol@18.0.0-next.3 + - @objectstack/objectql@18.0.0-next.3 + - @objectstack/core@18.0.0-next.3 + - @objectstack/types@18.0.0-next.3 + ## 18.0.0-next.2 ### Minor Changes diff --git a/packages/cloud-connection/package.json b/packages/cloud-connection/package.json index 5e60c387171..a3a34c7a1b9 100644 --- a/packages/cloud-connection/package.json +++ b/packages/cloud-connection/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/cloud-connection", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "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 (cloud ADR-0008): the hub service, plan policy, and entitlements stay server-side.", "type": "module", diff --git a/packages/connectors/connector-imap/CHANGELOG.md b/packages/connectors/connector-imap/CHANGELOG.md index ef80cea399c..7687b9ca853 100644 --- a/packages/connectors/connector-imap/CHANGELOG.md +++ b/packages/connectors/connector-imap/CHANGELOG.md @@ -1,5 +1,9 @@ # @objectstack/connector-imap +## 18.0.0-next.3 + +No changes in this release. + ## 18.0.0-next.2 No changes in this release. diff --git a/packages/connectors/connector-imap/package.json b/packages/connectors/connector-imap/package.json index 49cca88c2b4..17d8abbb1cb 100644 --- a/packages/connectors/connector-imap/package.json +++ b/packages/connectors/connector-imap/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/connector-imap", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "license": "Apache-2.0", "description": "IMAP connector for ObjectStack — a read-only IMAP4rev1 session (EXAMINE, UID FETCH with BODY.PEEK) and the per-user-shaped mail account and credential abstraction the mailbox sync connects through.", "main": "dist/index.js", diff --git a/packages/connectors/connector-mcp/CHANGELOG.md b/packages/connectors/connector-mcp/CHANGELOG.md index 30d260eca09..ad50c7d20c8 100644 --- a/packages/connectors/connector-mcp/CHANGELOG.md +++ b/packages/connectors/connector-mcp/CHANGELOG.md @@ -1,5 +1,52 @@ # @objectstack/connector-mcp +## 18.0.0-next.3 + +### Patch Changes + +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [6422da5] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [4ffdebe] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [e246f06] +- Updated dependencies [a65a59b] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/core@18.0.0-next.3 + ## 18.0.0-next.2 ### Patch Changes diff --git a/packages/connectors/connector-mcp/package.json b/packages/connectors/connector-mcp/package.json index 56c633f75d3..3d5392c46ac 100644 --- a/packages/connectors/connector-mcp/package.json +++ b/packages/connectors/connector-mcp/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/connector-mcp", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "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 42300e9cce7..a83c7c04989 100644 --- a/packages/connectors/connector-openapi/CHANGELOG.md +++ b/packages/connectors/connector-openapi/CHANGELOG.md @@ -1,5 +1,52 @@ # @objectstack/connector-openapi +## 18.0.0-next.3 + +### Patch Changes + +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [6422da5] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [4ffdebe] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [e246f06] +- Updated dependencies [a65a59b] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/core@18.0.0-next.3 + ## 18.0.0-next.2 ### Patch Changes diff --git a/packages/connectors/connector-openapi/package.json b/packages/connectors/connector-openapi/package.json index ab844d81d7c..ea752525d1d 100644 --- a/packages/connectors/connector-openapi/package.json +++ b/packages/connectors/connector-openapi/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/connector-openapi", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "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 cbe506eb7f5..ab3fad9a385 100644 --- a/packages/connectors/connector-rest/CHANGELOG.md +++ b/packages/connectors/connector-rest/CHANGELOG.md @@ -1,5 +1,52 @@ # @objectstack/connector-rest +## 18.0.0-next.3 + +### Patch Changes + +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [6422da5] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [4ffdebe] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [e246f06] +- Updated dependencies [a65a59b] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/core@18.0.0-next.3 + ## 18.0.0-next.2 ### Patch Changes diff --git a/packages/connectors/connector-rest/package.json b/packages/connectors/connector-rest/package.json index c4b107ce27b..9a31ca4d519 100644 --- a/packages/connectors/connector-rest/package.json +++ b/packages/connectors/connector-rest/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/connector-rest", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "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 d0b216efd22..55ee8cc3322 100644 --- a/packages/connectors/connector-slack/CHANGELOG.md +++ b/packages/connectors/connector-slack/CHANGELOG.md @@ -1,5 +1,52 @@ # @objectstack/connector-slack +## 18.0.0-next.3 + +### Patch Changes + +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [6422da5] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [4ffdebe] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [e246f06] +- Updated dependencies [a65a59b] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/core@18.0.0-next.3 + ## 18.0.0-next.2 ### Patch Changes diff --git a/packages/connectors/connector-slack/package.json b/packages/connectors/connector-slack/package.json index 49ca8168644..6b6b1a6e3af 100644 --- a/packages/connectors/connector-slack/package.json +++ b/packages/connectors/connector-slack/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/connector-slack", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "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 61df10ee8e1..3d80cd8a308 100644 --- a/packages/console/CHANGELOG.md +++ b/packages/console/CHANGELOG.md @@ -1,5 +1,239 @@ # @objectstack/console +## 18.0.0-next.3 + +### Minor Changes + +- 265ef64: Console (objectui) refreshed to `5e15928b4631`. Frontend changes in this range: + + Derived from the changesets objectui declared over the range — 88 releasing of 91 changesets added across 82 non-merge commits; omitted: 3 release-nothing changesets, 2 commits carrying no changeset (they ship no package code). + + - **minor** — **The AI build conversation stays on the conversation and reads more compactly** (objectui#12724). (objectui `ac5907384`) + - **minor** — A section entry written as a bare field name no longer reads the object field's `visibleOn` when sections are normalized (`normalizeSectionField`, used by `ObjectForm`, `ModalForm… (objectui `684f22e84`) + - **minor** — Lists at narrow widths: a status chip is never cut to "Publish…", a table wider than its panel shows that it scrolls, and the Actions column is drawn only when a row offers an act… (objectui `4839f5caf`) + - **minor** — A `list-view` block drawn as a compact list in a narrow page region now shows a "View all" link under its rows (objectui#12703). (objectui `33ee9de4c`) + - **minor** — A list block in a page region narrower than half the page draws a compact list (objectui#12703). (objectui `a8add5c71`) + - **minor** — On a record page, a related list's "View all" opens the child object's list (objectui#12741). The destination is the ADR-0055 bare data surface, `/apps/APP/OBJECT/data?filter[FIEL… (objectui `8df26086d`) + - **minor** — `RelatedRecordHandlers` gains an optional `onViewAll` (objectui#12741). A host returns it from `resolve` to open the child object's full list page, scoped to the parent on the rel… (objectui `8df26086d`) + - **minor** — `record:related_list` now draws the "View all" link its `showViewAll` key declares (objectui#12741, ENFORCE, the objectui half of objectstack-ai/objectstack#23941). (objectui `8df26086d`) + - **minor** — The identity import no longer offers "Update existing (skip if no match)" (objectui#12697). (objectui `3e2c7634b`) + - **minor** — The import wizard offers only the write modes its host says the target accepts (objectui#12697). (objectui `3e2c7634b`) + - **minor** — **BREAKING** — The metadata-admin designer reads and writes the form-view visibility predicate as `visibleWhen` only (objectui#12696). **BREAKING** for a host that still spells the retired `visi… (objectui `236624d56`) + - **minor** — The console page header folds its actions before it cuts the description (objectui#12705). (objectui `80863adea`) + - **minor** — `LoginForm` no longer tells an SSO-enforced environment's users to use a password (objectui#12682). (objectui `9b69e1325`) + - **minor** — The audit log browser (`audit:log`) offers `sys_audit_log`'s list views as presets and links to the object list (objectui#12667, the objectui half of objectstack#23890). (objectui `8296c6c3c`) + - **minor** — App pages lay a region's blocks out on a 12-column grid, and the page description sits under its title (objectui#12624). (objectui `c9e67425e`) + - **minor** — Seven `marketplace.*` keys are removed from all ten language packs: `installedTitle`, `installedSubtitle`, `installedEmpty`, `installedAt`, `installedBy`, `installedPackageId` and… (objectui `9d857c998`) + - **minor** — Setup → Packages is the one list of what is installed: every package carries a Source, and marketplace installs carry their actions on their own row (objectui#12675, ruling B on o… (objectui `9d857c998`) + - **minor** — A settings page's `info_banner` row speaks the session locale, draws a warning in amber, and renders its body as Markdown (objectui#12671, the objectui half of objectstack#23855). (objectui `a2f38ddff`) + - **minor** — **BREAKING** — A page `list-view` node is judged in its `properties` bag, in `@objectstack/spec`'s list view spelling, and the retired `objectName` / `viewType` are refused by name (objectui#126… (objectui `378896da6`) + - **minor** — A navigation entry whose `visible` predicate cannot be evaluated is no longer drawn for everyone (objectui#12700). (objectui `71243db0d`) + - **minor** — New export `isRecordPageOf(page, objectName)` (objectui#12715): true when `page` is a record page bound to `objectName` (`type: 'record'` and `object` names it). It is the one rea… (objectui `c7462e7c8`) + - **minor** — The console renders a record on a NAMED record page (objectui#12715, the objectui layer of the print-page series). (objectui `c7462e7c8`) + - **minor** — Setup draws one page header on every page: the `sys_*` record lists, the metadata-type pages, the settings pages and the component pages. (objectui `c41ce3f24`) + - **minor** — The console's form page (`FormPage`, the `/forms/:name` and public `/f/:slug` routes) reads a form view field's and section's visibility predicate from `visibleWhen` only (objectu… (objectui `611566b4d`) + - **minor** — A form view field's `visibleOn` is no longer read when sections are normalized (`normalizeSectionField`, used by `ObjectForm`, `ModalForm` and the record form page) (objectui#1265… (objectui `611566b4d`) + - **minor** — `SchemaRenderer` no longer reads a page component's `visibility` key (objectui#12654). `@objectstack/spec` 18 retires it as a `retiredKey` tombstone naming `visibleWhen` (objectst… (objectui `611566b4d`) + - **minor** — **BREAKING** — A page `list-view` node is written in `@objectstack/spec`'s list view spelling, `data` and `type`, and a metadata page reads `properties.type` as the list kind (objectstack-ai/obj… (objectui `5c0846ebd`) + - **minor** — Show a write answer's advisory `warnings` to the user after save (objectui#12107). (objectui `6757a932b`) + - **minor** — A list names what an `object-ref` or `recipient-picker` field references, instead of printing its api name (objectui#12613). The Sharing Rules list read `crm_account` and `sales_m… (objectui `278bc29ea`) + - **patch** — Clause-②: no (objectui `5e15928b4`) + - **patch** — The console's startup sends one `get-session` and one `runtime/config` request instead of two of each: the session check and the early-branding request now hand their answers to t… (objectui `5d69b0e73`) + - **patch** — Opening the Cloud pricing or Welcome page now asks for the organisation's environment entitlements once. The plan cards, the onboarding next step and the workspace-timezone notice… (objectui `5d69b0e73`) + - **patch** — `AuthProvider` no longer asks `get-session` a second time when the console has just validated the stored session before rendering. The validated answer becomes the provider's firs… (objectui `5d69b0e73`) + - **patch** — Studio names what it shows by label on two more surfaces (objectui#12693). (objectui `cda4076be`) + - **patch** — `aggregate()` no longer reads a served `null` measure as a missing one. The analytics answer's `null` (the max or min over rows whose field is all NULL) now reaches the caller as… (objectui `35ef75036`) + - **patch** — The kanban board's drag-and-drop tells a screen reader the card's title and the column's label, in the console's language, instead of the record id and the option value (objectui#… (objectui `36e2f7b67`) + - **patch** — fix(app-shell): the Studio Validations editor says why it is read-only, to hover and to the keyboard (objectui#12691) (objectui `d0a00d795`) + - **patch** — The console's create / edit record modal now opens at the size an object's default form view authors in `modalSize` (`sm`, `default`, `lg`, `xl` or `full`). On desktop the authore… (objectui `2ff84f8bf`) + - **patch** — The "N eligible approver(s)" figure beside an approval's decision progress counts people, not slots (objectui#12781). The record page's approvals panel and the approval request pa… (objectui `f3a7b870a`) + - **patch** — fix(app-shell): "Discard draft" on the metadata editor names the item by its label, not its machine id (objectui#12690) (objectui `0f42aaf80`) + - **patch** — The developer Flow Runs page's flow picker names each labelled flow by its label again, on the list a real server serves (objectui#12783). (objectui `b52415010`) + - **patch** — The flow designer's start node offers each field on exactly the triggers the engine reads it for (objectui#12780). (objectui `79270e7c4`) + - **patch** — The app header titles a view stored with no `label` by its object's plural label, the name its tab shows (objectui#12805). (objectui `3288c12f7`) + - **patch** — A link from one doc to another in the docs portal now carries the console's base path (objectui#12686). The portal rewrites a doc body's `[x](./other_doc.md)` cross-reference to t… (objectui `8cf9cf305`) + - **patch** — **`marketplace.installedAdditiveNote` says what uninstall does now, in all ten packs** (objectui#12684). The note on a loaded marketplace row's Uninstall (its tooltip and accessib… (objectui `670ea1041`) + - **patch** — The metadata directory's "View all issues" link no longer paints warnings as errors (objectui#12768). (objectui `4279c2ed2`) + - **patch** — A list view's `date-range` user-filter chip draws the platform date picker on both bounds, and names them in the console's language (objectui#12784). It used to draw two native da… (objectui `99a0d3bf0`) + - **patch** — A settings row's source badge reads in the session's language, verb included (objectui#12694). The badge beside a setting's label said where its value comes from as an English ver… (objectui `ff215d934`) + - **patch** — A record on a print page prints its own title field (objectui#12787). Off a print page, `record:details` hides the row that the page header's H1 already shows: the declared name f… (objectui `6e4f14442`) + - **patch** — The ⌘K command palette closes after every command it runs, including one that does not navigate (objectui#12683). (objectui `3d39ca78c`) + - **patch** — The flow designer, canvas, Debug simulator and read-only flow viewer treat `http` as the HTTP node type, and no longer recognise `http_request`, `http_call` or `webhook` as node t… (objectui `5483297ea`) + - **patch** — `useActionTextLocalizer` resolves every `I18nLabel` copy an action declares (objectui#12757). It used to collapse a per-locale map only for `label` and `successMessage`, and only… (objectui `655e8977d`) + - **patch** — A page's `element:button` shows its inline action's copy in the user's language (objectui#12757). The spec types an inline action's `label`, `confirmText`, `successMessage` and `e… (objectui `655e8977d`) + - **patch** — A new report's or dataset's Label in a non-Latin script no longer names it `field` (objectui#12789). The report and dataset designers derive the Name from the Label until you type… (objectui `0b80f501f`) + - **patch** — A view stored with no `label` is named by its object's plural label on the object page, never by its qualified name (objectui#12774). The spec gives a view's `label` no default, a… (objectui `c4467ccb2`) + - **patch** — The permission set editor's provenance badge decides from `packageId` and the package artifact only (objectui#12753). Spec 18 retired the authored `managedBy` on a permission set… (objectui `c054d018d`) + - **patch** — An SSO sign-in reads in the session's language on both login pages (objectui#12762). The console's login page and app-shell's `DefaultLoginPage` passed `LoginForm` none of its fou… (objectui `8d80845af`) + - **patch** — The object designer no longer offers a "Relationship name" box on lookup, master-detail and tree fields (objectui#12758). (objectui `b275f5804`) + - **patch** — Clause-②: no (objectui `260ef7196`) + - **patch** — fix(components): `page:header` reads the spec's `variant: 'danger'` as destructive (objectui#12764) (objectui `69a408f64`) + - **patch** — In the collapsed (icon-mode) sidebar, a click on an icon reaches its row again. Before, a hidden group label could take the click instead (objectui#12744). (objectui `f1142634e`) + - **patch** — Studio: the "Read-only · Customize with an overlay" link at the end of an item's tab row (an open flow in Automations, an open permission set in Access) now folds to its layers ic… (objectui `2368d7af1`) + - **patch** — A new report's or dataset's Name keeps every underscore you type (objectui#12746). The report and dataset designers' Name box normalized each keystroke with the strict identifier… (objectui `94b2489f4`) + - **patch** — An inline edit of a `datetime` field on the record page keeps its time (objectui#12759). The record details section and the header highlights edited `date` and `datetime` fields w… (objectui `020d1788c`) + - **patch** — A record on a print page no longer prints its approval band: no "Recall approval" button, no approval status badge and no approval tally on paper (objectui#12751). (objectui `6367bfeba`) + - **patch** — List filters on `date` and `datetime` fields draw the platform date picker, the face record forms, action dialogs and grid cells already draw (objectui#12743). The native input to… (objectui `ed43c79ce`) + - **patch** — The flow designer no longer calls a live time-relative sweep "not in effect" (objectui#12672). (objectui `8cc02b444`) + - **patch** — The app bar's breadcrumb folds instead of cutting every segment short (objectui#12702). (objectui `4857cd284`) + - **patch** — Studio's Interfaces rail and the dashboard component list show the app's navigation and the widget titles in the session's language (objectui#12688). (objectui `41c1d9e93`) + - **patch** — The list toolbar draws Filter, Sort and Display as 28 px icon buttons at every width, on every list that mounts it: app object lists, Setup lists and related lists (objectui#12695… (objectui `0171c2494`) + - **patch** — After a reload, the console no longer keeps the previous organization's id once the server says the session has no active organization (objectui#12698). better-auth clears a sessi… (objectui `6e141d999`) + - **patch** — The derived highlight strip on a record page no longer picks a field from a field group that declares a `visibleWhen` (objectui#12720). (objectui `360e27a14`) + - **patch** — Metadata Diagnostics no longer paints a clean sweep as an error (objectui#12721). (objectui `a214fbe2e`) + - **patch** — Browse Marketplace says what it is loading, and a failed catalog read names its class and offers Retry (objectui#12710). (objectui `a23e80464`) + - **patch** — Setup → Packages: the Scope badge now reads the server's `writable` verdict for its writable / read-only half (objectui#12723). A marketplace install has no `scope` and the server… (objectui `8dc313530`) + - **patch** — The grid's inline cell editor and the kanban required-fields dialog draw `date` and `datetime` fields with the platform date picker, the face record forms and the action dialogs a… (objectui `d66d7e834`) + - **patch** — Editing a formula field no longer shows a false "retired `formula` key" error (objectui#12681). (objectui `7ead04904`) + - **patch** — Home answers a `current_user.can()` navigation gate from the caller's permissions (objectui#12700). (objectui `2dbe5f533`) + - **patch** — A period-over-period delta from a zero baseline no longer claims "100%" (objectui#12677). (objectui `0666a66f3`) + - **patch** — A collapsible record details section on a print page prints its header as a plain heading, with no disclosure chevron and no clickable-header chrome (objectui#12732). (objectui `464ae629b`) + - **patch** — Opening a record page sends one approvals read and one record read (objectui#12701). (objectui `e219a3d6c`) + - **patch** — In the metadata designer's create mode, a Name the author typed is no longer replaced when the Label is typed afterwards (objectui#12680). This holds for the object, report and da… (objectui `1d869199c`) + - **patch** — A caller the Setup app refuses gets the access-denied screen on Setup's item pages instead of those pages under another app's chrome (objectui#12706). (objectui `a88c20604`) + - **patch** — Console lists link names that point at another object's record, the way the record page does (objectui#12673). (objectui `b393152f7`) + - **patch** — A record details section on a print page prints every field it declares, with no "Show N empty fields" button on paper (objectui#12717). (objectui `263e9ddcc`) + - **patch** — A form section that declares no `columns` now draws two fields per row from the container's `@md` width (one below it) on every page form host — `ObjectForm`, `SplitForm`, `Tabbed… (objectui `96fa4b9bc`) + - **patch** — Setup's permission-set and position holders offer Add and Remove only to a caller holding the grant the server asks for those writes (objectui#12082). (objectui `fac2c2496`) + - **patch** — The app bar's Edit app pencil now appears only where Studio can edit: for a metadata author, on an app whose package Studio lists as writable. A read-only package's app (such as a… (objectui `40e587342`) + + ⚠️ 3 of these carry a breaking change: 3 by the author's own breaking annotation in the changeset body — objectui declares no `major` inside a launch window (`scripts/check-changeset-no-major.mjs`). Each is marked **BREAKING** in the list above — read them before compiling the release record. + + **In this console build, declared nowhere** — objectui merged 2 commits in this range with no `.changeset/*.md`. The code is inside the pin above and ships here, but nothing upstream declared them, so they appear in no objectui CHANGELOG and in no entry above. Listed by subject rather than counted, because a count cannot tell a dependency bump from a form-behaviour change (objectstack#6174); the upstream gate that would prevent this is objectui#3387. + + - _(no changeset)_ fix(e2e/live): start-backend.sh records the server's pid, not a subshell's (objectui#12676) (#12737) (objectui `c22068922`) + - _(no changeset)_ chore(scripts): raise every eager-closure ceiling by 20% and move the regression the gate must catch to 695 KiB (objectui#12653) (#12699) (objectui `e6175b57f`) + + Clause-②: no + + + + objectui range: `f8267df93aba...5e15928b4631` +- a69a40a: **Shipped in 18.0.0-next.2**, whose CHANGELOG missed this entry (#23818); listed here one prerelease late. + + Console (objectui) refreshed to `daeb77fc84dd`. Frontend changes in this range: + + Derived from the changesets objectui declared over the range — 4 releasing of 4 changesets added across 5 non-merge commits; omitted: 1 commit carrying no changeset (they ship no package code). + + - **minor** — Studio, Interfaces pillar: an html or react page reads like a code page. The source editor's plain-text fallback numbers its lines and wraps long ones as the code editor does, and… (objectui `830eb1f2d`) + - **patch** — A new record page seeded in Studio is saved with the `default` layout template instead of `full-width`, and the page node's `template` member is typed by reference to the spec's (… (objectui `daeb77fc8`) + - **patch** — Two Studio pickers are named by the labels shown beside them (objectui#12510). Nothing changes on screen. (objectui `3e09c1e8d`) + - **patch** — Studio's empty surfaces now carry the action that fills them (objectui#12514). (objectui `f23eb01cb`) + + **In this console build, declared nowhere** — objectui merged 1 commit in this range with no `.changeset/*.md`. The code is inside the pin above and ships here, but nothing upstream declared it, so it appears in no objectui CHANGELOG and in no entry above. Listed by subject rather than counted, because a count cannot tell a dependency bump from a form-behaviour change (objectstack#6174); the upstream gate that would prevent this is objectui#3387. + + - _(no changeset)_ docs(studio-redesign): move the 61 PR screenshots under after/ to an evidence branch and remove them from main (#12583) (objectui `7066dae53`) + + objectui range: `f96f771140f1...daeb77fc84dd` +- e66f13c: Console (objectui) refreshed to `f8267df93aba`. Frontend changes in this range: + + Derived from the changesets objectui declared over the range — 46 releasing of 46 changesets added across 40 non-merge commits; omitted: 3 commits carrying no changeset (they ship no package code). + + - **minor** — **BREAKING** — The page renderer no longer compiles a `kind: 'jsx'` page node as `html`. This completes the retirement of the page kind `jsx` that `@objectstack/spec` 18 made (objectstack-ai/obj… (objectui `f8267df93`) + - **minor** — The page schemas no longer import or attach `@objectstack/spec`'s page-parse judge of a view-data component's retired `api` binding, which objectstack#23784 removed from the spec… (objectui `15d8ca6da`) + - **minor** — Home: "Recently accessed" brings you back to the records and list views you opened, beside the activity feed (objectui#12604). (objectui `f0e760afe`) + - **minor** — The details grid passes a pointer field's `referenceDeletedWhen` to `resolveRecordPointer` (objectui#12489). (objectui `023087f63`) + - **minor** — The record details grid draws a platform-deleted pointer target as a tombstone in the declared option's label (objectui#12489). (objectui `023087f63`) + - **minor** — `resolveRecordPointer` reads `referenceDeletedWhen`, so a pointer pair can say the platform deleted its target (objectui#12489). (objectui `023087f63`) + - **minor** — The console top bar is 48 px (`h-12`) in every frame, the height both approved console artboards draw (objectui#12639). It was 56 px (`h-14`). (objectui `dcd104102`) + - **minor** — List grids show the record a pointer field names, and the Approvals Inbox says who else is waiting (objectui#12608). (objectui `f78efdd1e`) + - **minor** — Locale keys for the console home's "My work" section (objectui#12602), in all ten packs. (objectui `db8ce2f41`) + - **minor** — The console home's "Needs your attention" card becomes the "My work" section of the approved home artboard (objectui#12602), and the pending-drafts banner above the greeting moves… (objectui `db8ce2f41`) + - **minor** — Resolve `@objectstack/*` `18.0.0-next.2` (objectui#12626). (objectui `f62b6ff4e`) + - **minor** — A kanban under a list view puts every record of the view in its column, not the list's first page, and its cards carry the view's first columns (objectui#12619). (objectui `8614705c1`) + - **minor** — **BREAKING** — `page:header` no longer reads `showStar`, ahead of `@objectstack/spec` 18 retiring the key (objectstack#23650). (objectui `608033b82`) + - **minor** — The console home's top half follows the approved home artboard (objectui#12601): a search field in the top bar, a greeting with a one-line work summary, two compact builder chips,… (objectui `6c4d00da0`) + - **minor** — `DefaultLoginPage` passes the translated names of `LoginForm`'s new password reveal toggle, `auth.login.showPasswordText` and `auth.login.hidePasswordText` (objectui#12600). It ke… (objectui `8ed7e5a8f`) + - **minor** — `LoginForm` gains an optional `hideHeader` prop and a password reveal toggle, and the shared auth form styles take the console login artboard's sizes (objectui#12600). (objectui `8ed7e5a8f`) + - **minor** — The console's login page follows the approved "refined centred" artboard (objectui#12600). (objectui `8ed7e5a8f`) + - **minor** — New `auth.login` strings for the console login page, in all ten language packs (objectui#12600): `welcomeTitle`, `welcomeDescription`, `showPasswordText`, `hidePasswordText`, `foo… (objectui `8ed7e5a8f`) + - **minor** — Every wide centred dialog keeps a 1rem gutter on each side of the window from `sm` (640px) up, by one shared class (objectui#12603). (objectui `726d8c9fc`) + - **minor** — A grouped grid now draws one table with one sticky column header. Each group is a 36 px band row between the rows: a chevron, the group value and its count, in one cell that spans… (objectui `619e3f7a4`) + - **minor** — The Studio's Publish and Changes buttons no longer open the pending-changes sheet the same way. (objectui `92c2a6976`) + - **minor** — **A record dialog with no authored layout opens bounded, and draws two fields per row wherever no `columns` is declared** (objectui#12521). Three defaults of `ModalForm` change. N… (objectui `7b5e55893`) + - **minor** — **BREAKING** — The console's list toolbar no longer has a write into the shared view, and the data-source method that write used is retired (objectui#12504). (objectui `3eb3ec138`) + - **minor** — Studio's Add field asks for the label and type before it creates the field, and the field type picker also searches descriptions (objectui#12516). (objectui `1b3b5b102`) + - **patch** — Action-param and bulk-action dialogs draw `date` and `datetime` params with the platform date picker, the same face record forms draw since objectui#12622 (objectui#12642). (objectui `c6b7d8203`) + - **patch** — The cloud-connection panel's "Not connected" card no longer repeats the page subtitle (objectui#12655). (objectui `efa9cb4fa`) + - **patch** — Record page sections: a collapsible section's header is now a real button named by the section title (objectui#12625). It carries `aria-expanded`, is reachable by Tab, and Enter o… (objectui `4be76e030`) + - **patch** — Studio's permission-set editor no longer offers the two delegated-admin switches that `@objectstack/spec` 18 retired (objectui#12406, the objectui half of objectstack#23488). (objectui `a98a23c78`) + - **patch** — Inside an app, the product mark stays in the top bar as the way home, and the app menu gains Back to home, All apps and Settings (objectui#12605). (objectui `a5bb7ac2f`) + - **patch** — Setup › Packaged automation keeps a flow row to the table's height when the platform says why the flow is not armed (objectui#12609). (objectui `8a8bcf3dd`) + - **patch** — Record forms draw `date` and `datetime` fields with the platform date picker instead of the browser's native date input (objectui#12622). (objectui `ca7a4c316`) + - **patch** — The record header draws one primary action and the rest as secondary buttons (objectui#12617). (objectui `ef71a09f9`) + - **patch** — **`marketplace.installedAdditiveNote` is plain text in all ten packs** (objectui#12610). Each value dropped its leading `Note:` markup (in that pack's language);… (objectui `f74638c1b`) + - **patch** — **Installed Apps' kernel note is the tooltip of the Uninstall it qualifies** (objectui#12610). The "kernel API is additive only" note was a paragraph at the foot of the list, show… (objectui `f74638c1b`) + - **patch** — **A settings action row draws its label once, on the button** (objectui#12610). The settings renderer's `action_button` row printed the manifest's `label` as the row label and aga… (objectui `f74638c1b`) + - **patch** — Marketplace cards show the whole app name, and the id keeps its package name (objectui#12616). (objectui `07b063249`) + - **patch** — Studio at a narrow width keeps the words that matter readable (objectui#12611). (objectui `a8560b228`) + - **patch** — The list toolbar's Display menu now closes once a layout is chosen (segmented or dropdown switcher), returning focus to the Display button, as Escape already does; the panel's tog… (objectui `79c199692`) + - **patch** — The Studio's Automations rail orders its object groups the way the package's app presents its objects (objectui#12612). The groups used to follow the alphabet of the object labels… (objectui `0a41305b0`) + - **patch** — Metric tiles keep their title readable in narrow tiles. The title now owns the first line (up to two lines, with an ellipsis on the second), the value never wraps and steps down o… (objectui `63d00da3d`) + - **patch** — The pending-changes sheet no longer lists the package stamp as one of the author's own changes (objectui#12594). Expanding a new object draft showed `_packageId` under "Also chang… (objectui `33fbc861d`) + - **patch** — An `lg` or `xl` `ModalForm` dialog keeps a 1rem gutter on each side of the window from `sm` (640px) up (objectui#12593). (objectui `85e8961f2`) + - **patch** — A boolean cell's read-only checkbox is named by its field (objectui#12584). (objectui `7d0c174ba`) + - **patch** — Studio's way home leads back to the Studio landing, and the two links that leave for Setup say so (objectui#12513). (objectui `f5eec6383`) + - **patch** — **The console's New and Edit dialog on a field-heavy object opens bounded on desktop** (objectui#12521). For an object with twelve or more authorable fields, `@objectstack/spec`'s… (objectui `7b5e55893`) + - **patch** — Studio: the draft status chip sits in the same place in every pillar's item header (objectui#12524). "Unpublished draft" in Data, Automations and Interfaces, and Access's "saved a… (objectui `ae923ece2`) + + ⚠️ 3 of these carry a breaking change: 3 by the author's own breaking annotation in the changeset body — objectui declares no `major` inside a launch window (`scripts/check-changeset-no-major.mjs`). Each is marked **BREAKING** in the list above — read them before compiling the release record. + + **In this console build, declared nowhere** — objectui merged 3 commits in this range with no `.changeset/*.md`. The code is inside the pin above and ships here, but nothing upstream declared them, so they appear in no objectui CHANGELOG and in no entry above. Listed by subject rather than counted, because a count cannot tell a dependency bump from a form-behaviour change (objectstack#6174); the upstream gate that would prevent this is objectui#3387. + + - _(no changeset)_ fix(live-e2e): the backend cold install writes overrides.vitest so npm 10 can install the published graph (objectui#12649) (#12674) (objectui `d66ceea5a`) + - _(no changeset)_ docs(skills): point two published member counts at their declarations instead of numbers (objectui#12588) (#12591) (objectui `8415d9f11`) + - _(no changeset)_ ci: delete the spec-main-shape-gate copy, the spec shape gate runs in objectstack (objectui#12352) (#12596) (objectui `c6a42be39`) + + Clause-②: no + + + + objectui range: `daeb77fc84dd...f8267df93aba` + ## 18.0.0-next.2 ### Major Changes diff --git a/packages/console/package.json b/packages/console/package.json index e4d2b97eba8..7650f3d84d9 100644 --- a/packages/console/package.json +++ b/packages/console/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/console", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "description": "Prebuilt Console SPA pinned to this framework release, installed as a dependency of @objectstack/cli. Source of truth: @object-ui/console (https://github.com/objectstack-ai/objectui).", "license": "Apache-2.0", "homepage": "https://github.com/objectstack-ai/objectstack/tree/main/packages/console", diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 057b5815b5c..fb990185c35 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -1,5 +1,78 @@ # @objectstack/core +## 18.0.0-next.3 + +### Minor Changes + +- e246f06: feat(plugin-auth,platform-objects,core)!: an API key an administrator's forced password reset revoked is restored by a platform administrator only; its owner creates a new key (#23993) + + **BREAKING** (an accept-set narrowing at the `sys_api_key` write door), shipped as `minor` under the launch-window convention for breaking changes. For an operator: a key revoked by `POST /api/v1/auth/admin/set-user-password` (the forced reset, which revokes the user's keys by default) can no longer be restored by its owner; a platform administrator restores it with the Restore API Key action, or the owner creates a new key. For an integrator: `PATCH /api/v1/data/sys_api_key/{id}` with `{ revoked: false }` on such a key now answers `403 PERMISSION_DENIED` for its owner, where it answered `200`; and `sys_api_key` gains a readonly `revoked_reason` column. + + Clause-②: yes (narrowing) + + What widens: `sys_api_key` declares one more field, `revoked_reason`. What narrows: the owner's restore of a key whose `revoked_reason` is `admin_password_reset` is refused, where it was admitted. + + + + #23287 ruling C′ makes the forced reset revoke the user's API keys by default, for a suspected compromise: a key that outlives the reset is the hole the reset was meant to close. Its changeset names who acts afterwards: a platform administrator restores such a key, or the owner mints a replacement. The owner could nevertheless restore it through the same door as any revoked key, because the row recorded no reason. Now it does. + + - **`sys_api_key.revoked_reason` (`@objectstack/platform-objects`).** A readonly `select` whose one value is `admin_password_reset`, set when the forced reset revoked the key and cleared when the key is restored. Empty for a key revoked from the API Keys list. Translated in `zh-CN`, `ja-JP` and `es-ES`. + - **The stamp (`@objectstack/plugin-auth`).** The forced reset's revocation writes `revoked_reason: admin_password_reset` beside `revoked: true`, the one place the reason is set. A key already revoked before the reset is not written, so it keeps no reason. + - **The restore guard (`@objectstack/plugin-auth`).** One `beforeUpdate` hook on `sys_api_key`, registered after the ADR-0092 D2 identity write guard. A user-context restore of a key carrying the reason is refused `403 PERMISSION_DENIED` unless the caller has platform-administrator standing (the ADR-0068 D2 rung); the refusal names both roads. Every restore it admits, the administrator's included, clears the reason. Revoking an already-revoked key leaves the reason in place, and the column is not on the D2 whitelist, so no data-API write can clear it. On a multi-row update every row is judged by its own stored state. System-context writes (the mint, the last-used stamp, the forced reset) are never refused. + - **The key door's message (`@objectstack/core`).** `API_KEY_REVOKED` for a key the forced reset revoked now says only a platform administrator can restore it; any other revoked key keeps today's sentence. Code, status and every carried field are unchanged. + + What changes for you: + + - **Refused now:** the owner restoring a key the forced reset revoked: `403 PERMISSION_DENIED`, naming the platform administrator and the new-key road. The administrator's restore works as before and clears the reason. + - **Unchanged:** revoking and restoring a key from the API Keys list, by its owner or an administrator; a forced reset with `revokeSessionsAndKeys: false`, which revokes nothing. + - **Keys revoked by a forced reset before this release** carry no reason, so their owner can still restore them. Such keys exist only where `18.0.0-next.2` or a `main` build since 2026-10-14 ran a forced reset; nothing backfills them, because the reset's audit row records counts, not key ids. + +### Patch Changes + +- 4ffdebe: Clause-②: no + + A call with a revoked API key no longer hears "a revoked key never works again". The key row's own Restore API Key action clears the flag and the same key works again at once, so the `403 API_KEY_REVOKED` message now says the key was revoked, that its owner or a platform administrator can restore it (the Restore API Key action on the API Keys list), and otherwise to create a new key (`POST /api/v1/keys`, or the API Keys page) and replace this one wherever it is configured. The code, the status, the fields the refusal carries and the expired and inactive messages are unchanged. The error catalog's `API_KEY_REVOKED` Fix line says the same. +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [6422da5] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [a65a59b] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/types@18.0.0-next.3 + ## 18.0.0-next.2 ### Minor Changes diff --git a/packages/core/package.json b/packages/core/package.json index f094ec2f24d..096dc60f762 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/core", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "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 b6a4b7d23e2..ab1d763809b 100644 --- a/packages/create-objectstack/CHANGELOG.md +++ b/packages/create-objectstack/CHANGELOG.md @@ -1,5 +1,35 @@ # create-objectstack +## 18.0.0-next.3 + +### Patch Changes + +- d795719: fix(create-objectstack): a blank scaffold installs on npm 10 again — its `package.json` overrides `vitest` to the range it already declares + + Clause-②: no + + A project from `npm create objectstack` (or `npx create-objectstack`) could not be installed with npm 10, the npm that Node 22 bundles and the one the scaffolder falls back to when pnpm is absent. `npm install` exited 1 with `Cannot read properties of null (reading 'edgesOut')`. The template's own dependencies were not the cause. Optional `vitest` peers deeper in the dependency tree led npm 10 to vitest 4.1.12, whose peer declarations contradict each other, and npm 10's resolver crashes on that. npm 11 installs the same project. + + The blank template now ships: + + ```json + "overrides": { + "vitest": "~4.0.0" + } + ``` + + This routes every `vitest` in the npm dependency graph to the range the project declares in `devDependencies`. npm 11 already resolves the whole tree to that version, and with the override npm 10.9.x installs the identical tree. pnpm does not read npm's `overrides` field, so pnpm installs are unchanged. + + If you change `devDependencies.vitest` in a scaffolded project, change `overrides.vitest` to the same value. npm refuses an override that differs from the direct dependency (`EOVERRIDE: Override for vitest@… conflicts with direct dependency`). An existing project that hits the `edgesOut` crash on npm 10 can install with npm 11 or later. +- 0b69564: Clause-②: no + + When the dependency install fails (a network error, a registry outage, an engine mismatch, an npm crash), the + scaffolder's closing "Next steps" now lists `npm install` (or `pnpm install`, whichever package manager the run + detected) before `npm run dev` and `npm run validate`, the same as it does under `--skip-install`. Before, a + failed install printed the same list as a successful one, so the next command it named ran against an empty + `node_modules`. A successful install still prints no install step. No flag, exit code, prompt or scaffolded file + changes. + ## 18.0.0-next.2 No changes in this release. diff --git a/packages/create-objectstack/package.json b/packages/create-objectstack/package.json index 00701ccce43..b93cb2c4c45 100644 --- a/packages/create-objectstack/package.json +++ b/packages/create-objectstack/package.json @@ -1,6 +1,6 @@ { "name": "create-objectstack", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "description": "Create a new ObjectStack project — npx create-objectstack", "bin": { "create-objectstack": "./bin/create-objectstack.js" diff --git a/packages/drivers/driver-memory/CHANGELOG.md b/packages/drivers/driver-memory/CHANGELOG.md index 826fc64c0a5..c1959074e5e 100644 --- a/packages/drivers/driver-memory/CHANGELOG.md +++ b/packages/drivers/driver-memory/CHANGELOG.md @@ -1,5 +1,53 @@ # @objectstack/driver-memory +## 18.0.0-next.3 + +### Patch Changes + +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [6422da5] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [4ffdebe] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [e246f06] +- Updated dependencies [a65a59b] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/core@18.0.0-next.3 + - @objectstack/types@18.0.0-next.3 + ## 18.0.0-next.2 ### Minor Changes diff --git a/packages/drivers/driver-memory/package.json b/packages/drivers/driver-memory/package.json index 52f0a481d55..d2c98f15a11 100644 --- a/packages/drivers/driver-memory/package.json +++ b/packages/drivers/driver-memory/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/driver-memory", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "license": "Apache-2.0", "description": "In-Memory Driver for ObjectStack (Reference Implementation)", "main": "dist/index.js", diff --git a/packages/drivers/driver-mongodb/CHANGELOG.md b/packages/drivers/driver-mongodb/CHANGELOG.md index 1412f4b7bee..3b8e5e9bb38 100644 --- a/packages/drivers/driver-mongodb/CHANGELOG.md +++ b/packages/drivers/driver-mongodb/CHANGELOG.md @@ -1,5 +1,53 @@ # @objectstack/driver-mongodb +## 18.0.0-next.3 + +### Patch Changes + +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [6422da5] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [4ffdebe] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [e246f06] +- Updated dependencies [a65a59b] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/core@18.0.0-next.3 + - @objectstack/types@18.0.0-next.3 + ## 18.0.0-next.2 ### Minor Changes diff --git a/packages/drivers/driver-mongodb/package.json b/packages/drivers/driver-mongodb/package.json index 373d5596cbf..0ae329cb735 100644 --- a/packages/drivers/driver-mongodb/package.json +++ b/packages/drivers/driver-mongodb/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/driver-mongodb", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "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/drivers/driver-sql/CHANGELOG.md b/packages/drivers/driver-sql/CHANGELOG.md index 51d8a08c85b..ef75c43b765 100644 --- a/packages/drivers/driver-sql/CHANGELOG.md +++ b/packages/drivers/driver-sql/CHANGELOG.md @@ -1,5 +1,86 @@ # @objectstack/driver-sql +## 18.0.0-next.3 + +### Minor Changes + +- c4111c6: fix(cli,driver-turso): `os migrate plan` and `os migrate apply` on a Turso REMOTE database no longer offer `--allow-destructive` for a change that database cannot apply + + Clause-②: yes (widening) + + On a primary reached over the Turso REMOTE transport (`libsql://…`), `os migrate plan` listed a NOT NULL tightening with the remedy `Run "os migrate apply --allow-destructive".` and closed with `(add --allow-destructive for drops / tightenings)`. That transport runs no SQLite table rebuild, so the apply then named the tightening as not applied and exited 1. The plan now says so before the apply: each change the database cannot apply (a tightened or relaxed NOT NULL, a dropped DEFAULT) is still listed, with its finding and `"os migrate apply" cannot apply it on this database: it needs the SQLite table rebuild, which the Turso REMOTE transport does not run.` in place of the remedy. The footer offers `--allow-destructive` only for the destructive changes the database can apply (a column drop, an index change) and says how many listed changes remain drift after the apply. The listing `os migrate apply` prints before its prompt reads the same way. Its counts of held-back destructive changes, before the prompt and after the run, offer a re-run with `--allow-destructive` only for the ones that re-run could apply, and name the rest as drift it cannot apply either. On every other database both commands read exactly as before, and their `--json` documents are unchanged on every database. + + `@objectstack/driver-turso`: `TursoDriver.driftApplyLimit(opType)` is new. It returns why the driver's `applyMigrationEntries` cannot perform a drift op of that type on this face, or `null` when it can. It answers from the same per-op declaration the REMOTE apply door dispatches by, and returns `null` for every op on the local and embedded-replica faces. A driver that does not implement it is read as applying every op the differ means it to. + + `@objectstack/driver-sql`: `driftFindingWithoutRemedy(entry)` is a new export. It returns a drift entry's finding without the `os migrate apply --allow-destructive` sentence that a destructive column entry's message ends with, and returns every other message unchanged; the messages themselves keep their exact text. + +### Patch Changes + +- 8776119: **Shipped in 18.0.0-next.2**, whose CHANGELOG missed this entry (#23818); listed here one prerelease late. + + feat(spec): `referenceDeletedWhen` beside `referenceVia` marks a polymorphic pointer's target as platform-deleted + + Clause-②: yes + + `FieldSchema` gains one optional key beside `referenceVia`: `referenceDeletedWhen: { field, value }`. On rows where the sibling `select` field `field` holds the option `value`, the record the pointer names was deleted by the platform's own assertion, and a renderer draws a tombstone from that option's own label instead of a link. Example: `record_id: Field.text({ referenceVia: 'object_name', referenceDeletedWhen: { field: 'cancel_reason', value: 'record_deleted' } })`. + + Any other shape is refused at parse, naming the field and the option: + + - the key on a field that declares no `referenceVia` (`FieldSchema`), or an unknown key inside it; + - a `field` that is the declaring field itself, is not declared on the object, is not a `select`, is a `multiple: true` select, or declares no inline `options` (a `picklist`-backed select); a `value` that is not one of the sibling's option values (`ObjectSchema`, at every door: `ObjectSchema.create()`, `defineStack`, raw `.parse()` and the metadata save door). + + Nothing that parsed before is refused: the key is new. Its liveness row is `planned` until the renderer that reads it ships. + + The other packages classify the new key where every field key is classified: + + - **`@objectstack/metadata-core`.** `FIELD_REFERENCE_POSITIONS` deletes a served field's `referenceDeletedWhen` when its `field` names a field the caller cannot read (field-level security); `value` is an option value and never a field name. + - **`@objectstack/driver-sql`.** `FIELD_KEY_STORAGE_CLASS` classifies it `presentation`: no DDL reads it. + - **`@objectstack/platform-objects`.** The field form's new `referenceDeletedWhen` row carries its labels and help text in zh-CN, ja-JP and es-ES. +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [6422da5] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [4ffdebe] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [e246f06] +- Updated dependencies [a65a59b] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/core@18.0.0-next.3 + - @objectstack/observability@18.0.0-next.3 + - @objectstack/types@18.0.0-next.3 + ## 18.0.0-next.2 ### Minor Changes diff --git a/packages/drivers/driver-sql/package.json b/packages/drivers/driver-sql/package.json index bd4f78a9053..9d3f6ac40df 100644 --- a/packages/drivers/driver-sql/package.json +++ b/packages/drivers/driver-sql/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/driver-sql", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "license": "Apache-2.0", "description": "SQL Driver for ObjectStack - Supports PostgreSQL, MySQL, SQLite via Knex", "main": "dist/index.js", diff --git a/packages/drivers/driver-sqlite-wasm/CHANGELOG.md b/packages/drivers/driver-sqlite-wasm/CHANGELOG.md index f627e9d5e75..07e016f2ce6 100644 --- a/packages/drivers/driver-sqlite-wasm/CHANGELOG.md +++ b/packages/drivers/driver-sqlite-wasm/CHANGELOG.md @@ -1,5 +1,54 @@ # @objectstack/driver-sqlite-wasm +## 18.0.0-next.3 + +### Patch Changes + +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [6422da5] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [c4111c6] +- Updated dependencies [4ffdebe] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [e246f06] +- Updated dependencies [a65a59b] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/driver-sql@18.0.0-next.3 + - @objectstack/core@18.0.0-next.3 + ## 18.0.0-next.2 ### Minor Changes diff --git a/packages/drivers/driver-sqlite-wasm/package.json b/packages/drivers/driver-sqlite-wasm/package.json index 8f2d3c52521..6c2396faaf5 100644 --- a/packages/drivers/driver-sqlite-wasm/package.json +++ b/packages/drivers/driver-sqlite-wasm/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/driver-sqlite-wasm", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "license": "Apache-2.0", "description": "WASM SQLite Driver for ObjectStack — runs in browser/WebContainer (StackBlitz) without native bindings", "keywords": [ diff --git a/packages/drivers/driver-turso/CHANGELOG.md b/packages/drivers/driver-turso/CHANGELOG.md index ae21a3ae5b2..957c49f364e 100644 --- a/packages/drivers/driver-turso/CHANGELOG.md +++ b/packages/drivers/driver-turso/CHANGELOG.md @@ -1,5 +1,111 @@ # @objectstack/driver-turso +## 18.0.0-next.3 + +### Minor Changes + +- ae4dfaf: feat(driver-turso,cli): the v18 organization-ownership ceremony runs on a Turso remote database + + Clause-②: yes (widening) + + `os migrate organization-ownership`, the plan and `--apply --backup-taken`, now completes against a Turso database reached over the remote transport (`libsql://…`), the transport hosted environments use. Before, the plan stopped at its boot with `Deferred schema DDL is not supported by the Turso REMOTE transport`, and the apply's column drops had no remote path either. + + - **`@objectstack/driver-turso`**, remote face: + - `detectManagedDrift()` answers instead of refusing with `NOT_IMPLEMENTED` / 501. The verdict is the shared differ's, the one the local face runs; the physical schema it diffs is read from the remote catalog (`pragma_table_info`, `sqlite_master`, `pragma_index_list` / `pragma_index_info`, and the ADR-0120 D4 duplicate probe). The declarations it diffs against are recorded by every remote schema door and by the DDL-free `registerObjectMetadata`, so a boot that skipped schema sync is covered too. + - `applyMigrationEntries()` applies `drop_index`, `create_index`, `recreate_index`, `replace_unique_index` and `drop_column` instead of refusing. Each entry runs as one libSQL `write` batch, so it lands whole or not at all: a `drop_column` drops the column's indexes with it in the same transaction, and a re-created index that fails to build leaves the old one in place. An entry is reported `applied` only once a read-back shows its target state. Every other column entry (nullability, width, a dropped DEFAULT) needs the SQLite table rebuild, which this transport does not run, so it is reported `skipped` and logged, as the in-place dialects report an op they cannot perform. + - The remote face declares `driftReportCaveat`: a sentence saying its drift report is for reading only and naming the reason. The OS_ARTIFACT_URL boot gate reads it (below). The local and embedded-replica faces declare `null`. + - The remote driver's `config` now carries its `url`, so the CLI labels a remote database by its URL (credential-free) instead of `better-sqlite3`. The ceremony writes that label into its plan and refuses an apply whose plan names another database; that refusal can now tell two remote databases apart. + - **`@objectstack/cli`**: the ceremony's plan and apply boots skip schema sync, instead of arming deferred DDL, when the primary datasource is a Turso remote one. No DDL is attempted, so the plan stays read-only, and every other datasource the boot connects is still deferred. The environment's own boot has already run the additive sync the ceremony reads. Every other `os migrate` command keeps refusing on that transport: their dry runs list held-back schema work, which a boot that holds nothing back cannot list. + - **`@objectstack/runtime`**: `createStandaloneStack` takes `skipSchemaSync`, passed to `ObjectQLPlugin` as the option `OS_SKIP_SCHEMA_SYNC=1` names. Unset, the plugin reads the variable as before. + + On a Turso remote primary, the `OS_ARTIFACT_URL` boot migration gate on `os serve` keeps its earlier answer: cannot judge, warn and boot. It applies no drift and refuses no boot. Only its warning's wording changes, from `Could not check the physical schema` to `Did not check the physical schema against the artifact: …`, followed by the driver's reason. The gate now decides this from a capability the driver declares (`driftReportCaveat`, a string on the remote face and `null` on the local and embedded-replica faces), not from a refusal it catches. Two follow-ups, filed separately and not closed by this change, have to land before that gate may act on a remote report: + - #23847: the remote transport's schema sync honours `storage.notNull` on create and add column, so the report stops naming a tightening its own sync produced; + - #23848: `os migrate apply` works on a Turso remote database, so the destructive drift the gate would refuse on has a remedy there. +- 61e5ee9: fix(cli,driver-turso): `os migrate plan` and `os migrate apply` run on a Turso REMOTE database, and the apply names every drift entry it could not apply + + Clause-②: yes + + On a primary reached over the Turso REMOTE transport (`libsql://…`, every hosted tenant), `os migrate plan` and `os migrate apply` used to stop at their boot with `Deferred schema DDL is not supported by the Turso REMOTE transport`, so the remedy the drift report and the `OS_ARTIFACT_URL` boot gate print for destructive drift, `os migrate apply --allow-destructive`, could not run there at all. Both commands now boot the way `os migrate organization-ownership` does on that transport: with schema sync skipped instead of deferred. What an operator on a remote primary now sees: + + - **The drift half runs.** `os migrate plan` lists the drift; `os migrate apply` applies what the transport can express, after the same confirmation: a column drop (with the indexes keyed on it, in one transaction) and every index entry. Nothing is written before the prompt, as before. + - **The additive half is reported as NOT examined, never as "nothing to create".** Both commands print a warning under the database line saying that tables and columns the metadata declares and the database lacks are neither listed nor created by this run, and that the serving boot (`os serve` / `os start`) creates them on this transport. The `--json` documents carry `schemaSync: "skipped"`, so their `pending: []` reads as not examined; an apply with no drift answers `message: "in_sync_partial"`, not `"in_sync"`. + - **Every entry the driver did not apply is named,** by table, column and kind, in both faces (`--json`: a new `notApplied` array on the post-apply document). The REMOTE transport runs no SQLite table rebuild, so a tightening, a relaxed NOT NULL, a width change or a dropped DEFAULT is not applied there. + - **A destructive entry confirmed with `--allow-destructive` that was not applied makes the run PARTIAL:** the human face ends `PARTIAL apply: …` and the command exits `1`; `--json` carries `message: "partial"` and exits `1`. A non-destructive entry that was not applied is named and the exit stays `0`. + + On every other database the apply behaves as before, with one difference in the same direction: a destructive change confirmed with `--allow-destructive` that the driver could not apply (its DDL failed, or the dialect cannot perform it) now exits `1` and is named, where the run used to exit `0` with a count. Its human line for the entries held back without `--allow-destructive` now reads `Skipped N destructive change(s) (re-run with --allow-destructive to include them)`. + + `@objectstack/driver-turso`: the REMOTE face's `driftReportCaveat` no longer says a destructive entry has no remedy on this transport; it says `os migrate apply --allow-destructive` applies a column drop or an index change there and names the rest. + + The other `os migrate` commands whose dry run arms deferred DDL (`duplicates`, `account-issuer`, `unmapped-columns`, `multi-value-columns`, and the dry runs of the data commands) still refuse on the REMOTE transport, unchanged. +- c4111c6: fix(cli,driver-turso): `os migrate plan` and `os migrate apply` on a Turso REMOTE database no longer offer `--allow-destructive` for a change that database cannot apply + + Clause-②: yes (widening) + + On a primary reached over the Turso REMOTE transport (`libsql://…`), `os migrate plan` listed a NOT NULL tightening with the remedy `Run "os migrate apply --allow-destructive".` and closed with `(add --allow-destructive for drops / tightenings)`. That transport runs no SQLite table rebuild, so the apply then named the tightening as not applied and exited 1. The plan now says so before the apply: each change the database cannot apply (a tightened or relaxed NOT NULL, a dropped DEFAULT) is still listed, with its finding and `"os migrate apply" cannot apply it on this database: it needs the SQLite table rebuild, which the Turso REMOTE transport does not run.` in place of the remedy. The footer offers `--allow-destructive` only for the destructive changes the database can apply (a column drop, an index change) and says how many listed changes remain drift after the apply. The listing `os migrate apply` prints before its prompt reads the same way. Its counts of held-back destructive changes, before the prompt and after the run, offer a re-run with `--allow-destructive` only for the ones that re-run could apply, and name the rest as drift it cannot apply either. On every other database both commands read exactly as before, and their `--json` documents are unchanged on every database. + + `@objectstack/driver-turso`: `TursoDriver.driftApplyLimit(opType)` is new. It returns why the driver's `applyMigrationEntries` cannot perform a drift op of that type on this face, or `null` when it can. It answers from the same per-op declaration the REMOTE apply door dispatches by, and returns `null` for every op on the local and embedded-replica faces. A driver that does not implement it is read as applying every op the differ means it to. + + `@objectstack/driver-sql`: `driftFindingWithoutRemedy(entry)` is a new export. It returns a drift entry's finding without the `os migrate apply --allow-destructive` sentence that a destructive column entry's message ends with, and returns every other message unchanged; the messages themselves keep their exact text. + +### Patch Changes + +- 2034975: fix(driver-turso): a Turso remote database enforces the `storage.notNull` a field declares, on the tables and columns its schema sync creates + + Clause-②: no + + **What was wrong.** On a Turso database reached over the remote transport (`libsql://…`, the transport hosted environments use), schema sync created every declared column nullable. A field declared `storage: { notNull: true }` was not enforced there: an insert that left it empty was stored. The same declaration on a local SQLite file (`file:`) has always produced a `NOT NULL` column. The drift report, which compares the database with the declarations through the shared differ, also read each such column as destructive `tighten_not_null` drift on a database the remote sync had just created. + + **What changes.** The remote face's `CREATE TABLE` and `ALTER TABLE … ADD COLUMN` add `NOT NULL` to a column whose field declares `storage.notNull`, wherever the local face adds it: on every field type, multi-value fields included, and never for `required` alone. On a remote database synced after this release, an insert that leaves such a field empty is refused by the database, and the drift report names no `tighten_not_null` for it. + + **What does not change.** + + - A column that already exists is not touched. A remote database synced by an earlier release keeps its nullable columns, and its drift report still names them `tighten_not_null`; no data at rest is migrated. So the remote face still declares its drift-report caveat (reworded to say this), and the `OS_ARTIFACT_URL` boot gate still does not act on a remote report. + - Adding a `storage.notNull` field to a remote table that already holds rows is refused by SQLite (`Cannot add a NOT NULL column with default value NULL`), as it is on a local SQLite file, and that object's schema sync fails loudly. The remote error now names the refused statement. One case still differs: when the field also declares a literal `defaultValue`, the local face creates the column with that DEFAULT and accepts it, while the remote face emits no declared column DEFAULT and refuses it. +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [6422da5] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [c4111c6] +- Updated dependencies [4ffdebe] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [e246f06] +- Updated dependencies [a65a59b] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/driver-sql@18.0.0-next.3 + - @objectstack/core@18.0.0-next.3 + ## 18.0.0-next.2 ### Minor Changes diff --git a/packages/drivers/driver-turso/package.json b/packages/drivers/driver-turso/package.json index 93983321244..c2c07f4bbb5 100644 --- a/packages/drivers/driver-turso/package.json +++ b/packages/drivers/driver-turso/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/driver-turso", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "license": "Apache-2.0", "description": "Turso/libSQL Driver for ObjectStack — Edge-first SQLite with embedded replicas", "keywords": [ diff --git a/packages/formula/CHANGELOG.md b/packages/formula/CHANGELOG.md index a5fff53a276..455627a2cf6 100644 --- a/packages/formula/CHANGELOG.md +++ b/packages/formula/CHANGELOG.md @@ -1,5 +1,49 @@ # @objectstack/formula +## 18.0.0-next.3 + +### Patch Changes + +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [6422da5] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [a65a59b] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + ## 18.0.0-next.2 ### Patch Changes diff --git a/packages/formula/package.json b/packages/formula/package.json index 80bd4de8765..9af238d73c8 100644 --- a/packages/formula/package.json +++ b/packages/formula/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/formula", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "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 007e33dd766..7db279973be 100644 --- a/packages/lint/CHANGELOG.md +++ b/packages/lint/CHANGELOG.md @@ -1,5 +1,130 @@ # @objectstack/lint +## 18.0.0-next.3 + +### Patch Changes + +- e375315: fix(lint): the six `data-model-rules.ts` rule ids print a fix line of one remedy sentence, and `os explain RULE_ID` carries what those lines used to explain + + Clause-②: no + + - **Shorter fix lines.** Each finding of these six rule ids now carries a `fix` of one remedy sentence of at most 200 characters on every variant measured, including long object and column names. The three ADR-0120 uniqueness ids carry it as the `hint` that `os validate` and `os build` print after `fix: `; all six print it as `os lint --fix`'s `→ fix:` line. The longest of each, FROM → TO (characters of the fix text alone): + - `unique/legacy-organization-composite` 592 → 161; + - `unique/unscoped-declared-index` 380 → 150; + - `object/missing-name-field` 327 → 188; + - `unique/double-declaration` 303 → 159 (the contradiction arm; the two redundancy arms were already short and now drop one word); + - `rollup/non-numeric-aggregand` 300 → 165 (the line no longer names the parent object, so its length no longer grows with that name); + - `field/select-missing-options` 207 → 180. + - **What moved.** The text the fix lines stopped saying is in each id's `RULE_EXPLANATIONS` entry, which `os explain RULE_ID` prints: + - `unique/unscoped-declared-index`: what `unique: 'organization'` makes the driver build, and the `os migrate meta --from 17` route for existing sources (one new paragraph); + - `unique/legacy-organization-composite`: that the respelling makes the listed organization column NULL-safe in place, what the constraint then means, and that duplicate NULL-organization rows block the `recreate_index` with a report rather than failing a boot (one paragraph extended, one new); + - `unique/double-declaration`: where each scope word may be written (one new paragraph). + - `object/missing-name-field` now points at `os explain title-format-retired`, whose entry already says why a `titleFormat` template does not count. `field/select-missing-options` and `rollup/non-numeric-aggregand` lost only wording; the rollup entry already carries the reasoning. + - **Runtime publish gate:** none of the six runs there. The three uniqueness entries are `cli`-only registry rules, and the other three run only in `os lint`'s `lintDataModel` sweep. So no 422 `issues[].hint` and no 2xx `advisories[].hint` changes. + - **Unchanged:** every `message` (the verdict), rule id, severity and `path`, and what each rule accepts or refuses. +- cbbd96a: fix(lint): seven more rule ids print a `fix:` line of one remedy sentence, and `os explain RULE_ID` carries what those lines used to explain + + Clause-②: no + + - **Shorter fix lines.** Each finding of these 7 rule ids now carries a `hint` (the CLI's `fix:` line, the runtime advisory's `hint`) of one remedy sentence of at most 200 characters on every variant the lint suite fires (lengths are the `hint` alone; the CLI prints it after `fix: `). The longest of each, before → after: + - flow reference syntax (`lint-flow-patterns.ts`): `flow-double-brace-interpolation` (1413 → 197) and `flow-bare-dollar-reference` (1234 → 198); + - `liveness-ledger-unreadable` (270 → 82), `capability-reference-unknown` (241 → 131), `page-section-count-not-multi-value` (228 → 167), `list-view-filters-in-views-mode` (219 → 135) and `page-field-unknown` (206 → 94). + - **The flow ids keep the judge's spelling, and only where it evaluates.** In a value slot the line names the spec value-slot judge's own CEL envelope for the reference, read out of the judge's remedy and never re-spelled: FROM `A value slot reads a CEL value envelope, … so a bare $ reference is stored as written. $source.id names $source, which is not one of the flow engine's own $ variables, … Write {source.id} as { dialect: 'cel', source: 'source.id' }. CEL refuses an absent variable or key … (the guarded form writes null).` (681) TO ``A value slot stores a bare `$` reference as written: write `{source.id}` as { dialect: 'cel', source: 'source.id' }.`` (116). Where an envelope at the string's place would not evaluate (a string inside an object or list literal, a legacy `assignments: [{ variable, value }]` shape, a `$User` path that never resolved), the line names no envelope and points at `os explain`. It never names the build's own refusal for a `{{ }}` string, whose spelling keeps the outer braces as literal text. In a text slot the line names the `{{ }}` hole for each reference the engine binds, and for any other `$` root says to compute the value into a variable with an `assignment` node and write `{{ v }}`. At a notify or http literal it says neither brace dialect resolves there: write the value itself. + - **Lists are no longer echoed.** `page-field-unknown` no longer appends the object's whole field list; it names the nearest declared field ("Did you mean") instead. + - **`os explain RULE_ID` carries the rest.** What the fix lines stopped saying is each id's `RULE_EXPLANATIONS` entry in `@objectstack/lint`: the `has()` guard for a value that may be absent, the run user's `current_user` null guard and the `sys_user` read for any other `$User` path, the whole-value literal and the canonical `assignments` map, what a filter operand's `null` does, which slots read `{{ }}`, and where a brace token in a literal is refused; that a capability another installed package declares needs no change; why page filter controls do nothing on an object list view (ADR-0047); that a missing liveness ledger is a platform packaging fault; which field types take `multiple: true`. Six ids gain an entry, so their `rule:` line now carries the `os explain` pointer: `flow-double-brace-interpolation`, `flow-bare-dollar-reference`, `liveness-ledger-unreadable`, `page-field-unknown`, `page-section-count-not-multi-value` and `list-view-filters-in-views-mode`. `capability-reference-unknown`'s entry gains one sentence. + - **At the runtime publish gate** (Studio, REST `PUT /api/v1/meta`, and the MCP and AI authors that write through that door), the two flow ids fire on a `flow` write as 2xx `advisories[].hint`, FROM → TO measured at the door: a value-slot `$source.id` 681 → 116, `$User.Id` 681 → 121, `$User.Email` 742 → 198, a nested literal 1231 → 198, a legacy assignment 1030 → 198; a text-slot `$User.Id` 767 → 144 and `$error.message` 114 → 83; a value-slot `{{ }}` 746 → 116, a notify literal 297 → 137, a value no slot reads 362 → 158. Studio's save-advisory toast prints an advisory's `hint` after its message. `capability-reference-unknown` and `list-view-filters-in-views-mode` run at the CLI only; the two page ids do not fire on a `page` write; `liveness-ledger-unreadable` fires only on an install whose `@objectstack/spec` lost a ledger file. + - **Unchanged:** every `message` (the verdict), rule id, severity and `path`, and what each rule accepts or refuses. +- b6805ac: fix(lint): the twelve flow anti-pattern rule ids print a `fix:` line of one remedy sentence, and `os explain RULE_ID` carries what those lines used to explain + + Clause-②: no + + - **Shorter fix lines.** Each finding of these 12 rule ids in `lint-flow-patterns.ts` now carries a `hint` (the CLI's `fix:` line, the runtime finding's `hint`) of one remedy sentence of at most 200 characters on every variant the lint suite fires (lengths are the `hint` alone; the CLI prints it after `fix: `). The longest of each, before → after: `flow-loop-body-uncontained` (800 → 174), `flow-multi-write-unfiltered` (695 → 73), `flow-try-catch-without-catch` (594 → 137), `flow-decision-inclusive-overlap` (437 → 159), `flow-approval-revise-target-not-service-owned` (412 → 76), `flow-time-relative-antipattern` (397 → 163), `flow-date-equality-filter` (379 → 123), `flow-runas-unscoped` (372 → 105), `flow-decision-mode-invalid` (295 → 134), `flow-decision-unconditional-branch` (286 → 149; its other arm, 142, is unchanged), `flow-error-label-not-fault` (239 → 81) and `flow-branch-label-unmatched` (224 → 122). + - **Examples.** `flow-loop-body-uncontained` FROM ``Contain the failure per iteration: put a `try_catch` INSIDE the body and move this node into its `try` region — … A `catch` region cannot be empty … Measured: with it, all 5 items are processed … See content/docs/automation/flows.mdx §"Per-iteration containment".`` (800) TO ``Move this node into the `try` region of a `type: 'try_catch'` node inside the loop body, with `catch: { nodes: [ { id: 'handled', type: 'assignment', label: 'Handled' } ] }`.`` (174). `flow-multi-write-unfiltered` FROM ``Write the constraint you mean into `filter` (e.g. `{ status: 'closed' }` — see examples/app-showcase `showcase_inquiry_purge`, …). If emptying 'lead' really is the intent, keep it: … Distinct from the run-time erased-condition guard, …`` (684–695) TO ``Write the constraint you mean into `filter`, e.g. `{ status: 'closed' }`.`` (73). `flow-runas-unscoped` FROM ``Declare `runAs:'system'` to make the elevation explicit and intended (the run reads/writes every record). A schedule flow cannot scope to a user — … (ADR-0049, ADR-0073 D5)`` (362–372) TO ``Declare `runAs:'system'` to make the elevation explicit and intended (the run reads/writes every record).`` (105). `flow-time-relative-antipattern` used to spell its range query as ``BETWEEN {TODAY()} and {TODAY()+N}``, in the single-brace dialect flows no longer resolve; its line now names the declared `timeRelative` sweep, and its entry spells the range with `$gte` / `$lt`. + - **`os explain RULE_ID` carries the rest.** Each of the 12 ids gains a `RULE_EXPLANATIONS` entry in `@objectstack/lint`, so its `rule:` line now carries the `os explain` pointer. What the fix lines stopped saying is there: which node types count as able to fail and how containment is judged; the two empty `catch` spellings the parse refuses, what the minimal handler may omit, and the measured result with it; when failing fast or retry-then-fail is a legitimate reading; the worked `showcase_inquiry_purge` example, keeping a meant whole-object write, and how the rule differs from the run-time erased-condition guard; when to keep `mode: 'inclusive'` and where `os migrate meta` left it on a flow authored against protocol major 17; what ends the revise window and why ADR-0044's amendment moved it off `wait`; the declared `timeRelative` sweep and the hand-written range-query alternative; why abutting one-day windows match each row once; why a user-less run is refused rather than run unscoped, and the anonymous-endpoint exception (ADR-0138 D2b); that `registerFlow` refuses an invalid `mode` with the same sentence; dropping `type: 'decision'` for a meant fan-out; which failures a `fault` edge routes and which labels read as an error path; and one branching mechanism per decision. + - **At the runtime publish gate** (Studio, REST `PUT /api/v1/meta`, and the MCP and AI authors that write through that door), all 12 ids fire on a `flow` write, FROM → TO measured at the door. As 422 `issues[].hint`: `flow-approval-revise-target-not-service-owned` 412 → 76, `flow-runas-unscoped` 367 → 105 (schedule), 372 → 105 (time-relative) and 362 → 105 (api), `flow-decision-mode-invalid` 295 → 134, `flow-branch-label-unmatched` 224 → 122. As 2xx `advisories[].hint`: `flow-loop-body-uncontained` 800 → 174, `flow-multi-write-unfiltered` 695 → 73 and 684 → 73, `flow-try-catch-without-catch` 594 → 137, `flow-decision-inclusive-overlap` 437 → 159, `flow-time-relative-antipattern` 397 → 163, `flow-date-equality-filter` 379 → 123, `flow-decision-unconditional-branch` 286 → 149, `flow-error-label-not-fault` 239 → 81. No write changes channel. + - **Unchanged:** every `message` (the verdict), rule id, severity and `path`, and what each rule accepts or refuses. +- 105902f: feat(spec)!: retire the view form's `visibleOn` and the page component's `visibility` as keys; their ADR-0089 renames leave the load path + + Clause-②: yes (narrowing) + + **BREAKING (authoring):** ADR-0089 made `visibleWhen` the one conditional-visibility key, and through 17 the old spellings were declared `[DEPRECATED]` keys that the parse folded onto `visibleWhen` without a word: `visibleOn` on a form view's fields and sections, and `visibility` on a page component. At 18 all three positions carry a `retiredKey()` tombstone naming `visibleWhen`, the parse-time fold is gone, and the two ADR-0087 D2 conversions `view-visibleOn-to-visibleWhen` and `page-component-visibility-to-visibleWhen` (protocol 15) are retired from the load path (`retiredAfter: '17.7.0'`). + + ## FROM → TO + + | you wrote (17.7 and earlier) | write instead | + | --- | --- | + | `visibleOn: "…"` on a form field (`view.form.sections[].fields[]`) | `visibleWhen: "…"` | + | `visibleOn: "…"` on a form section (`view.form.sections[]`) | `visibleWhen: "…"` | + | `visibility: "…"` on a page component (`page.components[]`, slots and nested containers included) | `visibleWhen: "…"` | + + **The one-line fix:** rename the key to `visibleWhen`; the CEL predicate is unchanged. Where a node wrote both keys, `visibleWhen` was the one in effect: delete the old key. + + **What an author who still writes it sees.** `tsc` fails at a typed authoring site (`FormFieldInput`, `FormSection`, `PageComponent` type the key as retired), and the parse refuses it by name at its own path — `defineStack` throws `STACK_SCHEMA_INVALID` (422), `os validate` / `os build` print the issue, and the metadata save door answers 422: + + > `page.components[].visibility` was removed in @objectstack/spec 18 (ADR-0089 D2) — it was the page component's deprecated spelling of the conditional-visibility predicate, which the parse folded into `visibleWhen` until this release. Use `visibleWhen`. Rename the key; the value (a CEL predicate) is unchanged. Where a component writes both, `visibleWhen` is the one that was in effect: delete `visibility`. + + **No tool makes the rename.** Both conversions belong to protocol 15, below the migration floor, so `os migrate meta --from 17` applies no edit for either key and `--write` writes none: it prints each written key among the refusals the migrated stack still has, and lists the D3 entry `ui-visibility-alias-keys-retired` as the hand edit. + + **Stored metadata needs no edit.** Stored-row rehydration and the artifact-ingestion door inside its window still replay both conversions, so a `sys_metadata` view or page row written with the old key keeps loading with `visibleWhen`. A script that saves a view or page through the metadata API with the old key is refused and needs the same rename. + + ## The retirement kit + + - **Tombstones** on `FormFieldSchema`, `FormSectionSchema` and `PageComponentSchema`; `RETIRED_KEYS_BY_MAJOR[18]`: `ui/FormField:visibleOn`, `ui/FormSection:visibleOn`, `ui/PageComponent:visibility`; the authorable-surface rows read `[RETIRED]`. + - **The fold is gone.** The three shapes are plain closed objects again (no `.transform` pipe): a section's group-reference rule no longer lists `visibleOn` (the tombstone answers it first), and a slotted page node's `type` refusal now arrives at its own path instead of nested in an `invalid_union`. + - **The D3 entry `ui-visibility-alias-keys-retired`**, one for both keys. + - **No deprecation window**, per the project's startup-stage posture. + + `@objectstack/lint`: the visibility-predicate rules (`visibility-root-mislayered`, `visibility-bare-identifier`, `visibility-predicate-syntax`) and the predicate-path rules read `visibleWhen` only. `os validate`, `os build`, `os verify`, `defineStack` and the metadata save door parse first and refuse either retired key by name, so a body they judge cannot carry one. `os lint` is the one door that hands these rules an unparsed stack: there a predicate written under a retired key is not judged, by design — the parsing doors refuse the key itself, and `os lint` reports no refused shape. + + `@objectstack/metadata-core`: `detectUnboundFormViewPredicateRoots` reads `visibleWhen` only. Its caller scans an artifact after `applyArtifactForwardConversions`, which renames a legacy `visibleOn` first. + + Not part of it: `normalizeVisibleWhen` and `VISIBILITY_ALIAS_KEYS` stay exported (no schema here calls them any more); objectui's renderer reads of the old keys leave on the objectui side. + + ⚠️ **The out-of-repo consumer population is NOT MEASURED.** `@objectstack/spec` is published, so this is breaking for consumers no telemetry was consulted for. + + +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [6422da5] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [a65a59b] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/formula@18.0.0-next.3 + - @objectstack/sdui-parser@18.0.0-next.3 + ## 18.0.0-next.2 ### Minor Changes diff --git a/packages/lint/package.json b/packages/lint/package.json index d2d88c6b587..f2d1219a506 100644 --- a/packages/lint/package.json +++ b/packages/lint/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/lint", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "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 8272c88406b..a1d3b531009 100644 --- a/packages/mcp/CHANGELOG.md +++ b/packages/mcp/CHANGELOG.md @@ -1,5 +1,54 @@ # @objectstack/plugin-mcp-server +## 18.0.0-next.3 + +### Patch Changes + +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [6422da5] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [4ffdebe] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [e246f06] +- Updated dependencies [a65a59b] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/core@18.0.0-next.3 + - @objectstack/formula@18.0.0-next.3 + - @objectstack/types@18.0.0-next.3 + ## 18.0.0-next.2 ### Minor Changes diff --git a/packages/mcp/package.json b/packages/mcp/package.json index 8c36962cfd2..9e9872f5282 100644 --- a/packages/mcp/package.json +++ b/packages/mcp/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/mcp", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "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 d63016d07ae..bc077f83cbf 100644 --- a/packages/metadata-core/CHANGELOG.md +++ b/packages/metadata-core/CHANGELOG.md @@ -1,5 +1,204 @@ # @objectstack/metadata-core +## 18.0.0-next.3 + +### Minor Changes + +- d050a8f: fix(spec,objectql)!: an installed or stored page carrying a declaration spec 18 retires converts at load instead of stopping the kernel build — `isDefault` off a bound record page is deleted and `template: 'full-width'` re-spelled `default`, each with a named notice; the authoring, save and publish doors keep refusing + + Clause-②: yes (widening) + + **What changes.** Protocol 18 refused `isDefault: true` on a page that is not a record page bound to an object, and closed the page `template` set (retiring `full-width`), at every door — the page parse (`definePage`, `defineStack`, so `os validate` / `os build`, and the metadata save door) and the kernel's package registration (`SchemaRegistry.installPackage` / `registerItem`, `MisplacedDefaultPageError`). The registration refusal also fired when a kernel was built from a package that was installed BEFORE the retirement: every hosted environment holding such a package failed its kernel build, with no remedy short of upgrading the package in each one. Data at rest now converts, and only data at rest: + + | Door | Before | Now | + | :--- | :--- | :--- | + | `definePage` / `defineStack` (`os validate`, `os build`), the metadata save door, package publish | refused at `isDefault` / `template` | **unchanged** — refused with the same sentence | + | a `sys_metadata` page row read back | served with `_diagnostics.valid: false` | converted on read: the flag deleted, `full-width` → `default`, `OS_METADATA_CONVERTED` notice | + | a compiled artifact inside the window its `engines.protocol` opens | the artifact door refused the artifact | converted before the strict parse, as every retired entry is | + | a package body registered through `manifest.register` / `ObjectQL.registerApp` that declares `engines.protocol` (an installed package, a marketplace install) | `MisplacedDefaultPageError`, kernel build stopped | converted before anything registers, with one `warn` per (package, conversion) naming the conversion id and the first site; the body is left unchanged on disk | + | the same body with NO declared range (a manifest a plugin builds in code), or declaring a floor newer than every retirement | refused | **unchanged** — refused | + + **The two ADR-0087 D2 entries**, both `retiredFromLoadPath` from the day they land (the authoring funnel never replays them): + + - `page-is-default-off-record-page-removed` (`page.isDefault`): deletes `isDefault: true` from exactly the pages `misplacedDefaultPageRefusal` refuses — a `home`, `app`, `utility` or `list` page, or a record page with no `object`. Lossless: nothing read the flag there (an app opens on its first navigation item). A default record page bound to an object keeps its flag. + - `page-template-full-width-to-default` (`page.template`): `'full-width'` → `'default'`. Lossless: the console drew both names with one template component. An unregistered template name (`centered`, `two-column`) and a region the template does not place have no lossless rewrite and are NOT converted — they stay with the parse, and the D3 entries `page-is-default-off-record-page-refused` / `page-template-closed-region-unplaced-refused` name the hand edits. + + **`@objectstack/objectql`** — `ObjectQL.registerApp` (and the `manifest` service, for an artifact carrying `packages[]`) replays the versioned forward conversion (`applyArtifactForwardConversions`, `@objectstack/metadata-core`) over a package body that declares its protocol range, before `installPackage`; the converted copy is what the package record and every registered item carry. Public: `ObjectQL.convertPackageBodyForward(body)`. + + **`@objectstack/metadata-core`** — `applyArtifactForwardConversions` reads the declared range off a FLAT package body (the manifest half beside its collections, no `manifest` key) when the definition carries no `manifest`; a definition with a `manifest` object is read there, as before. + + Migration: none for authors — the authoring doors refuse exactly what they refused. An installed package converts again at every registration until it is rebuilt (`os build`) and re-published with tooling from a release that ships the retirement; the boot log's warn names the conversion and the site. + + + +### Patch Changes + +- 105902f: feat(spec)!: retire the view form's `visibleOn` and the page component's `visibility` as keys; their ADR-0089 renames leave the load path + + Clause-②: yes (narrowing) + + **BREAKING (authoring):** ADR-0089 made `visibleWhen` the one conditional-visibility key, and through 17 the old spellings were declared `[DEPRECATED]` keys that the parse folded onto `visibleWhen` without a word: `visibleOn` on a form view's fields and sections, and `visibility` on a page component. At 18 all three positions carry a `retiredKey()` tombstone naming `visibleWhen`, the parse-time fold is gone, and the two ADR-0087 D2 conversions `view-visibleOn-to-visibleWhen` and `page-component-visibility-to-visibleWhen` (protocol 15) are retired from the load path (`retiredAfter: '17.7.0'`). + + ## FROM → TO + + | you wrote (17.7 and earlier) | write instead | + | --- | --- | + | `visibleOn: "…"` on a form field (`view.form.sections[].fields[]`) | `visibleWhen: "…"` | + | `visibleOn: "…"` on a form section (`view.form.sections[]`) | `visibleWhen: "…"` | + | `visibility: "…"` on a page component (`page.components[]`, slots and nested containers included) | `visibleWhen: "…"` | + + **The one-line fix:** rename the key to `visibleWhen`; the CEL predicate is unchanged. Where a node wrote both keys, `visibleWhen` was the one in effect: delete the old key. + + **What an author who still writes it sees.** `tsc` fails at a typed authoring site (`FormFieldInput`, `FormSection`, `PageComponent` type the key as retired), and the parse refuses it by name at its own path — `defineStack` throws `STACK_SCHEMA_INVALID` (422), `os validate` / `os build` print the issue, and the metadata save door answers 422: + + > `page.components[].visibility` was removed in @objectstack/spec 18 (ADR-0089 D2) — it was the page component's deprecated spelling of the conditional-visibility predicate, which the parse folded into `visibleWhen` until this release. Use `visibleWhen`. Rename the key; the value (a CEL predicate) is unchanged. Where a component writes both, `visibleWhen` is the one that was in effect: delete `visibility`. + + **No tool makes the rename.** Both conversions belong to protocol 15, below the migration floor, so `os migrate meta --from 17` applies no edit for either key and `--write` writes none: it prints each written key among the refusals the migrated stack still has, and lists the D3 entry `ui-visibility-alias-keys-retired` as the hand edit. + + **Stored metadata needs no edit.** Stored-row rehydration and the artifact-ingestion door inside its window still replay both conversions, so a `sys_metadata` view or page row written with the old key keeps loading with `visibleWhen`. A script that saves a view or page through the metadata API with the old key is refused and needs the same rename. + + ## The retirement kit + + - **Tombstones** on `FormFieldSchema`, `FormSectionSchema` and `PageComponentSchema`; `RETIRED_KEYS_BY_MAJOR[18]`: `ui/FormField:visibleOn`, `ui/FormSection:visibleOn`, `ui/PageComponent:visibility`; the authorable-surface rows read `[RETIRED]`. + - **The fold is gone.** The three shapes are plain closed objects again (no `.transform` pipe): a section's group-reference rule no longer lists `visibleOn` (the tombstone answers it first), and a slotted page node's `type` refusal now arrives at its own path instead of nested in an `invalid_union`. + - **The D3 entry `ui-visibility-alias-keys-retired`**, one for both keys. + - **No deprecation window**, per the project's startup-stage posture. + + `@objectstack/lint`: the visibility-predicate rules (`visibility-root-mislayered`, `visibility-bare-identifier`, `visibility-predicate-syntax`) and the predicate-path rules read `visibleWhen` only. `os validate`, `os build`, `os verify`, `defineStack` and the metadata save door parse first and refuse either retired key by name, so a body they judge cannot carry one. `os lint` is the one door that hands these rules an unparsed stack: there a predicate written under a retired key is not judged, by design — the parsing doors refuse the key itself, and `os lint` reports no refused shape. + + `@objectstack/metadata-core`: `detectUnboundFormViewPredicateRoots` reads `visibleWhen` only. Its caller scans an artifact after `applyArtifactForwardConversions`, which renames a legacy `visibleOn` first. + + Not part of it: `normalizeVisibleWhen` and `VISIBILITY_ALIAS_KEYS` stay exported (no schema here calls them any more); objectui's renderer reads of the old keys leave on the objectui side. + + ⚠️ **The out-of-repo consumer population is NOT MEASURED.** `@objectstack/spec` is published, so this is breaking for consumers no telemetry was consulted for. + + +- 8776119: **Shipped in 18.0.0-next.2**, whose CHANGELOG missed this entry (#23818); listed here one prerelease late. + + feat(spec): `referenceDeletedWhen` beside `referenceVia` marks a polymorphic pointer's target as platform-deleted + + Clause-②: yes + + `FieldSchema` gains one optional key beside `referenceVia`: `referenceDeletedWhen: { field, value }`. On rows where the sibling `select` field `field` holds the option `value`, the record the pointer names was deleted by the platform's own assertion, and a renderer draws a tombstone from that option's own label instead of a link. Example: `record_id: Field.text({ referenceVia: 'object_name', referenceDeletedWhen: { field: 'cancel_reason', value: 'record_deleted' } })`. + + Any other shape is refused at parse, naming the field and the option: + + - the key on a field that declares no `referenceVia` (`FieldSchema`), or an unknown key inside it; + - a `field` that is the declaring field itself, is not declared on the object, is not a `select`, is a `multiple: true` select, or declares no inline `options` (a `picklist`-backed select); a `value` that is not one of the sibling's option values (`ObjectSchema`, at every door: `ObjectSchema.create()`, `defineStack`, raw `.parse()` and the metadata save door). + + Nothing that parsed before is refused: the key is new. Its liveness row is `planned` until the renderer that reads it ships. + + The other packages classify the new key where every field key is classified: + + - **`@objectstack/metadata-core`.** `FIELD_REFERENCE_POSITIONS` deletes a served field's `referenceDeletedWhen` when its `field` names a field the caller cannot read (field-level security); `value` is an option value and never a field name. + - **`@objectstack/driver-sql`.** `FIELD_KEY_STORAGE_CLASS` classifies it `presentation`: no DDL reads it. + - **`@objectstack/platform-objects`.** The field form's new `referenceDeletedWhen` row carries its labels and help text in zh-CN, ja-JP and es-ES. +- 30ce56e: Clause-②: no + + Setup pages describe their records in plain admin sentences. Business Units now reads "Your + company structure — divisions, departments, regions and offices — used to scope data and + reports.", Notification Events "Every notification the platform sent, one row per event.", + Notification Preferences "Which topics and channels each user receives; admins set the + defaults.", Notification Templates "The subject and body each notification topic sends, per + channel and language." and HTTP Deliveries "Outgoing HTTP calls made by webhooks and flows, one + row per call, with its delivery status." Field help on Users, Organizations, Invitations and SSO + Providers, and the metadata-storage and notification-delivery object descriptions, no longer + carry design-record numbers; those references stay in the code comments. The zh-CN, ja-JP and + es-ES translations follow. Wording only: no field, permission or behaviour changes. +- df2c551: fix(metadata-core): the package installs on npm 10 again, and into projects that use vitest 5, because it no longer declares an optional `vitest` peer + + Clause-②: no + + On npm 10, the npm that Node 22 bundles, `npm install @objectstack/metadata-core` exited 1 with `Cannot read properties of null (reading 'edgesOut')`. So did installing a package that depends on it, such as `@objectstack/objectql`. The cause was this package's optional peer `vitest: ^4.0.0`. It led npm 10 to vitest 4.1.12, whose own peer declarations contradict each other, and npm 10's resolver crashes on that. The same peer also made npm refuse (`ERESOLVE`) to install the package into a project that already uses vitest 5. + + The package's main entry never imports `vitest`. Only the test-only `@objectstack/metadata-core/testing` entry does, so the peer is removed. Nothing the package exports changes. + + If you import `@objectstack/metadata-core/testing`, list `vitest` in your own `devDependencies`, as that entry's documentation already says. This package runs that suite on vitest 4. In a pnpm workspace with hoisting turned off (`hoist=false`), that entry can no longer find `vitest` from inside a workspace package. Declare the peer back in your `pnpm-workspace.yaml`: + + ```yaml + packageExtensions: + '@objectstack/metadata-core': + peerDependencies: + vitest: '*' + ``` + + `@objectstack/plugin-auth` still fails the same way on npm 10, because its `better-auth` dependency declares its own optional `vitest` peer. Install it with npm 11, or add `"overrides": { "vitest": "~4.0.0" }` to your project's `package.json`. +- adad73f: feat(spec)!: a select option declares its lifecycle `kind` (`open` / `won` / `lost`), and every option of an object's `stageField` must declare it + + Clause-②: yes (narrowing) + + `SelectOptionSchema` gains one optional key, `kind`, a closed vocabulary of three lifecycle categories that means the same on every object: `open` (a step on the way), `won` (the successful end: a deal's Closed Won, a lead's Converted, a task's Done, a case's Resolved) and `lost` (the unsuccessful end: Closed Lost, Unqualified, Cancelled). It is the spelling `record:path` `stages[].terminal` already takes, so the data layer and the page say one thing one way. Example: `{ label: 'Closed Lost', value: 'closed_lost', kind: 'lost' }`. The exported type `SelectOptionKind` names the three values. + + - On a plain single-value `select` / `radio` the key is optional, and an absent kind reads as `open`. Nothing is written in at parse: an option without `kind` parses without it. + - On any other field's options (`multiselect`, `checkboxes`, `tags`, a `multiple: true` select, a lookup's static options) `FieldSchema` refuses it at `options..kind`: a lifecycle is one value per record. + - A shared `picklist`'s options accept it (the same option shape). A form view's option list does not (`FormSelectOptionSchema`): it is refused there as an unknown key whose message points at the object field's own option. + + **BREAKING (authoring):** when an object's `stageField` names a field, every option of that field must declare `kind`. `ObjectSchema` refuses the object otherwise, at every door that parses one (`ObjectSchema.create()`, `defineStack`, `os validate`, `os build`, the metadata save door), with one issue per option at `fields..options..kind` naming the field and the option. There is no default under `stageField`. Also refused, at `stageField`: a stage field that takes its options from a `picklist` (the object cannot see them to check), and a stage field carrying options that holds no single option code per record (`multiselect`, a `multiple: true` select). Unchanged: `stageField: false`, a `stageField` naming no declared field, and one naming a field with no options. + + Migration: add `kind` to each option of the stage field, marking each terminal state `won` or `lost` and every other state `open`: + + - `status: Field.select({ options: [{ label: 'New', value: 'new' }, { label: 'Converted', value: 'converted' }, { label: 'Unqualified', value: 'unqualified' }] })` with `stageField: 'status'` → the same options with `kind: 'open'`, `kind: 'won'` and `kind: 'lost'`. + - A picklist-backed stage field → declare its options inline on the field. + - An object with no linear lifecycle → `stageField: false`. + + No conversion makes this edit, because which option ends a record's life successfully is the author's to say. `os migrate meta` lists the D3 entry `stage-field-option-kind-required` with the prescription. + + The Studio object designer's options repeater offers `kind`; the field form's options repeater lists it. + + The other packages follow the new key: + + - **`@objectstack/platform-objects`.** The metadata-form translation bundles (`./metadata-translations`) carry the options repeater's new `kind` row, with its label and help text in zh-CN, ja-JP and es-ES. + - **`@objectstack/metadata-core`.** The published contract fixture `FLS_CONTRACT_OBJECT` (`@objectstack/metadata-core/testing`) names `salary_grade` as its `stageField`, so its two options now declare `kind: 'open'`, and the fixture meets the stage-field rule: `ObjectSchema` raises no issue at `stageField` or under `fields.salary_grade.options`. The fixture is not otherwise a spec-valid object (its `script` actions carry no `body` or `target`), and this change leaves that as it was. + + +- 077b75f: fix(metadata-core): the protocol handshake reads an `engines.protocol` `||` union, so `'^17.7.0 || ^18.0.0'` loads on protocol 18 instead of being refused as incompatible + + Clause-②: no + + - **What was wrong.** `rangeAdmitsMajor` had no branch for `||`. A union reached the caret branch and was read as a caret on its first alternative, so `'^17.7.0 || ^18.0.0'` answered `OS_PROTOCOL_INCOMPATIBLE` on protocol 18. Every door that runs the ADR-0087 D1 handshake refused it: `sys_packages` boot rehydration, the marketplace install-local POST and rehydrate, and `protocol.installPackage`. + - **What it does now.** A union admits a major when any alternative admits it. When none does and any alternative is unrecognized (an empty one included, `'^17 ||'`), the union answers `unparsed-range`: the package loads with the existing `[protocol]` warning and is never refused. A union whose every alternative refuses is refused as before. Each alternative is read with the unchanged single-range grammar (caret, tilde, hyphen, comparators, `x`, `*`), so a range without `||` reads exactly as it did. + - **The refusal's `targetMajor`.** For a refused union it is the lowest alternative's major, whatever order the alternatives are written in, so `'^17 || ^16'` and `'^16 || ^17'` both name `objectstack migrate meta --from 16`. It used to read the first alternative. + - **The artifact forward-conversion floor.** `parseRangeFloor` reads a union's floor as its lowest alternative's (`'^18.0.0 || ^17.1.0'` → `17.1.0`, as for `'^17.1.0 || ^18.0.0'`), and `null` when an alternative is unreadable. It used to read the first alternative, so a union written high-first opened a narrower conversion window than the same range written low-first. +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [6422da5] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [a65a59b] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + ## 18.0.0-next.2 ### Minor Changes diff --git a/packages/metadata-core/package.json b/packages/metadata-core/package.json index 081ebded175..8565c2675b8 100644 --- a/packages/metadata-core/package.json +++ b/packages/metadata-core/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/metadata-core", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "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 00dd256bc41..1ee9fb8840b 100644 --- a/packages/metadata-fs/CHANGELOG.md +++ b/packages/metadata-fs/CHANGELOG.md @@ -1,5 +1,18 @@ # @objectstack/metadata-fs +## 18.0.0-next.3 + +### Patch Changes + +- Updated dependencies [105902f] +- Updated dependencies [8776119] +- Updated dependencies [30ce56e] +- Updated dependencies [df2c551] +- Updated dependencies [adad73f] +- Updated dependencies [d050a8f] +- Updated dependencies [077b75f] + - @objectstack/metadata-core@18.0.0-next.3 + ## 18.0.0-next.2 ### Patch Changes diff --git a/packages/metadata-fs/package.json b/packages/metadata-fs/package.json index 07386f3065b..be5f59211c7 100644 --- a/packages/metadata-fs/package.json +++ b/packages/metadata-fs/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/metadata-fs", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "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 d84c3e80636..719392b2979 100644 --- a/packages/metadata-protocol/CHANGELOG.md +++ b/packages/metadata-protocol/CHANGELOG.md @@ -1,5 +1,257 @@ # @objectstack/metadata-protocol +## 18.0.0-next.3 + +### Minor Changes + +- 14aff67: **Shipped in 18.0.0-next.2**, whose CHANGELOG missed this entry (#23818); listed here one prerelease late. + + fix(metadata-protocol)!: a draft save naming no package of an object its package-less definition owns is refused when its draft address is a package's draft, with a way out to the package-less draft (ADR-0029 D9.9) + + Clause-②: no (narrowing) + + + + **BREAKING** (an accept-set narrowing), shipped as `minor` under the launch-window convention for breaking changes. + + The ADR-0029 D9.9 check of the object save door is now asked of the package the write lands in. A draft save naming no package (`PUT /api/v1/meta/object/NAME?mode=draft`, Studio's designer save) leaves the binding to the draft address, which is the item's one package `Q` when an active row is bound to `Q`, or `Q`'s draft when no active row is bound to a package and `Q`'s draft is the only one stored (ADR-0048). The check used to ask with the request's empty package, so an object whose owner is its package-less definition read as its own re-save, and the save wrote `Q`'s draft. It answered `200`, and the promote door then refused every publish of that draft. + + What changes for you, where object `NAME` is owned by its package-less definition and `Q` is any package. Every refusal below is `422 OBJECT_OVERLAY_PACKAGE_MISMATCH` with nothing written, carrying `packageId: Q` and `ownerPackageId: null`. Only rows written before the save door refused a package's row beside the package-less one, or drafts bound to a package stored before that row existed, reach these states. + + - Over a row bound to `Q` stored beside the package-less one, with no draft, with a draft bound to no package, or with both that draft and `Q`'s: FROM `PUT /api/v1/meta/object/NAME?mode=draft` answering `200` with `Q`'s draft written (a new one, or the draft bound to no package adopted into `Q`), TO the refusal "A draft save naming no package of object 'NAME' writes a draft bound to 'Q', which no publish can serve: the object is served by its package-less definition. First DELETE /api/v1/meta/object/NAME?package=Q, then send this save again: it writes the package-less draft. Nothing was written." + - The same row with `Q`'s draft and no draft bound to no package: FROM `200` into `Q`'s draft, TO the refusal naming `Q`'s draft discard after the row's delete: "First DELETE /api/v1/meta/object/NAME?package=Q, then ?state=draft&package=Q (the draft discard), then send this save again: …". Deleting the row alone leaves `Q`'s draft, which the address then still names. Each package's draft is named the same way when several are stored. + - No row bound to a package, and `Q`'s draft the only draft stored: FROM `200` into `Q`'s draft, TO the refusal "First DELETE /api/v1/meta/object/NAME?state=draft&package=Q (the draft discard), then send this save again: it writes the package-less draft. …". + + **Remedy, in one line.** Send the requests the refusal names, in its order, then the same draft save: it writes the package-less draft, and the table, its records and the served definition stay as they are. A discard removes only that package's draft, which no publish could serve; the save sent again carries the edit. To move the object into `Q` instead, send the draft save naming `Q`: its refusal names that move. + + **Unchanged.** + + - A draft save naming no package where no package's row and no package's draft is stored: it writes the draft bound to no package. + - A draft save naming no package where the draft bound to no package is the address (no package's row, that draft stored): it is written, as before. + - A draft save naming no package over rows bound to two or more packages, or over two packages' drafts with no package row and no draft bound to no package: `409 RESOURCE_CONFLICT`, as before. + - Every save naming a package, every publish-mode save, and the promote doors: same answers and texts. + - An object a code package ships, an object owned by a runtime row bound to a real package, and every type except `object`. + - Stored rows are not rewritten on upgrade. +- cf07168: feat(metadata-protocol)!: the metadata save door refuses a retired flow spelling on a NEW flow, and a stored flow keeps re-saving + + Clause-②: yes (narrowing) + + **BREAKING (API save door):** a flow saved through `saveMetaItem` (`PUT /api/v1/meta/flow/:name`, the runtime dispatcher, and every caller of the protocol) with no stored row of that name at the save's address is now judged as authored input. It is canonicalized without the ADR-0087 D2 conversions retired from the authoring funnel, so a spelling the loader stopped lifting at 18 is no longer rewritten and stored: it meets the flow parse's refusal, `422 INVALID_METADATA`, with an issue located at the key and the same prescription `os validate` and `os build` print. Draft and publish mode alike. Before, the door lifted the old spelling, stored the new one and said nothing, so the client that wrote it (an AI writing flows through MCP or REST above all) was never told and wrote it again. + + What counts as a stored row: the row the save overwrites, at its state and package, and for a draft save with no draft yet the active row the draft overlays, so Studio's first draft edit of a stored flow is a re-save. + + Not changed: + + - A re-save of a stored flow keeps replaying the full chain, retired entries included, so a legacy row stays editable and is healed by the save, exactly as before. + - The server's own writes of bodies at rest keep the full chain: the stored-metadata migration (`os migrate meta --stored`), a package duplicate and an unmanaged package import. + - The automation engine's flow rehydration (`registerFlow`) and the read path, which still serves a stored flow verbatim. + + Migration, for a client that creates flows through the API: + + - `wait` node loose `config.duration` / `config.timerDuration` → `waitEventConfig.timerDuration`; `config.signal` / `config.signalName` → `waitEventConfig.signalName`; `config.eventType` → `waitEventConfig.eventType`. On the block itself, `timeoutMs` → `timerDuration` and `onTimeout` is removed. + - `connector_action` node loose `config.connectorId` / `config.actionId` / `config.input` → its `connectorConfig` block. + - CRUD node `config.filters` → `config.filter`; `config.object` → `config.objectName`. + - `map` and `subflow` node `config.flow` → `config.flowName`. + - `notify` node `config.to` → `recipients`, `subject` → `title`, `body` → `message`, `url` → `actionUrl`, `source: { object, id }` → `sourceObject` / `sourceId`. + - `script` node `config.functionName` → `function`, `input` → `inputs`; `actionType` / `template` / `recipients` / `variables` / `script` are removed. + - `errorHandling.retryDelayMs` and a `try_catch` node's `retry.retryDelayMs` → `backoffMs`; the flow keys `active` / `template`, a node's `outputSchema` and `errorHandling.fallbackNodeId` are removed. + + Each refusal names the key to write. `os migrate meta --from 16` lists the same edits for source files. Rows already stored need nothing. + + `@objectstack/service-automation`: `AutomationEngine.canonicalizeStoredFlow(name, definition, options?)` takes `{ includeRetired?: boolean }`. Omitted, it is `true` (the full chain, as every caller holding a body at rest wants). `false` is the authoring posture the save door asks for. `registerFlow` does not take the option. + + +- 6e18016: feat(metadata-protocol)!: a flow re-save that newly introduces a retired spelling is refused like a new flow's + + Clause-②: yes (narrowing) + + **BREAKING (API save door), the re-save case:** a flow saved through `saveMetaItem` (`PUT /api/v1/meta/flow/:name`, the runtime dispatcher, and every caller of the protocol) over a stored row is now judged as authored input when its body introduces a spelling retired from the authoring funnel that the stored row does not carry. Adding a node with an old spelling to a legacy flow, or writing one on a node the stored row holds in the canonical spelling, is no longer rewritten and stored: the save meets the flow parse's refusal, `422 INVALID_METADATA`, with an issue located at the key and the same prescription `os validate` and `os build` print, exactly as a new flow's does. Draft and publish mode alike. The whole body is judged that way, so the refusal also lists the retired spellings the stored row already carries; fixing the added one alone is enough to save. Before, a re-save healed every retired spelling in the body, the new one included, and said nothing. + + What counts as introduced: a retired conversion that fires on the request at a site where it does not fire on the stored row. A site is the key's path, with each node (and edge) named by its `id` rather than its position, so moving, adding or removing nodes does not change it. The stored rows compared are the row the save overwrites, and for a draft save the active row the draft overlays as well, so Studio's repeated draft saves of a legacy flow keep saving. + + Not changed: + + - A re-save whose retired spellings the stored row all carries, such as the served legacy body saved back unchanged, keeps replaying the full chain and is healed as before. + - A new flow with a retired spelling is refused, as it was. + - The server's own writes of bodies at rest keep the full chain: the stored-metadata migration (`os migrate meta --stored`), a package duplicate and an unmanaged package import. + - The automation engine's flow rehydration and the read path, which still serves a stored flow verbatim. + + Migration, for a client that edits flows through the API: write the spellings below on any node or key you add or change. The legacy spellings already on the stored row may stay until the row is healed. + + - `wait` node loose `config.duration` / `config.timerDuration` → `waitEventConfig.timerDuration`; `config.signal` / `config.signalName` → `waitEventConfig.signalName`; `config.eventType` → `waitEventConfig.eventType`. On the block itself, `timeoutMs` → `timerDuration` and `onTimeout` is removed. + - `connector_action` node loose `config.connectorId` / `config.actionId` / `config.input` → its `connectorConfig` block. + - CRUD node `config.filters` → `config.filter`; `config.object` → `config.objectName`. + - `map` and `subflow` node `config.flow` → `config.flowName`. + - `notify` node `config.to` → `recipients`, `subject` → `title`, `body` → `message`, `url` → `actionUrl`, `source: { object, id }` → `sourceObject` / `sourceId`. + - `script` node `config.functionName` → `function`, `input` → `inputs`; `actionType` / `template` / `recipients` / `variables` / `script` are removed. + - `errorHandling.retryDelayMs` and a `try_catch` node's `retry.retryDelayMs` → `backoffMs`; the flow keys `active` / `template`, a node's `outputSchema` and `errorHandling.fallbackNodeId` are removed. + + Each refusal names the key to write. + + +- a65a59b: feat(spec)!: the ADR-0078 conversion-conflict channel is retired — `CONVERSION_CONFLICT_CODE`, `ConversionConflictNotice`, `ConversionConflictDetail`, the `onConflict` / `reservedNodeTypes` conversion options, the `conflicts` arrays and the `FLOW_CONVERSION_CONFLICT` 409 + + Clause-②: yes (narrowing) + + **BREAKING (published API):** the ADR-0087 conversion API carried a channel for a rename REFUSED because its old token was a live name in the environment: a conversion reported it through `ConversionContext.reportConflict`, `applyConversions` handed it to an `onConflict` sink as a `ConversionConflictNotice` (code `OS_METADATA_CONVERSION_CONFLICT`), the automation engine collected it in `canonicalizeStoredFlow`'s `conflicts` array, and the metadata protocol answered it with `409 FLOW_CONVERSION_CONFLICT`. Its only producer was the live-executor guard inside the flow-node HTTP callout rename. Protocol 18 reserved those three names (`FLOW_RESERVED_NODE_TYPES`), so the engine refuses an executor under one, no live owner can exist, and the guard went. Nothing can raise a conversion conflict any more, so the channel leaves the published surface rather than staying as a live-looking failure mode nothing triggers. + + ## FROM → TO + + | you wrote | write instead | + | --- | --- | + | `import { CONVERSION_CONFLICT_CODE, type ConversionConflictNotice, type ConversionConflictDetail } from '@objectstack/spec'` | delete the import — no replacement | + | `applyConversions(stack, { onConflict, reservedNodeTypes, … })` (likewise `applyConversionsToFlow`, `applyConversionsToStoredItem`, `collectConversionNotices`) | delete both options; keep `onNotice` / `onTodo` | + | a conversion's `apply` reading `context.reservedNodeTypes` or calling `context.reportConflict` | a rename over an open namespace reserves its old names instead (the `FLOW_RESERVED_NODE_TYPES` pattern), so the refusal happens at registration; a site with no lossless rewrite goes to `context.reportTodo` | + | an `IAutomationService.canonicalizeStoredFlow` implementation returning `conflicts: […]` (or a `StoredFlowCanonicalization` from `@objectstack/metadata-protocol` carrying one) | drop the member; return `parsed`, `storable`, `notices` and optionally `todos` | + | a client branch on `error.code === 'FLOW_CONVERSION_CONFLICT'` / HTTP 409 from a flow save, a package duplication or an unmanaged package import | delete the branch — no request can produce it | + + **The one-line fix:** delete every reference; the branch it guarded was already dead, so deleting it changes no behaviour. + + **What an upgrader sees.** Importing a removed name is `TS2305`; passing a removed option, or returning a `conflicts` member from a typed canonicalizer, is an excess-property `tsc` error. `ErrorCode` no longer parses `FLOW_CONVERSION_CONFLICT`. + + **Runtime behaviour is unchanged.** The removed branches could not fire: a stored flow carrying a reserved callout name still loads as `http` (the rewrite has been unconditional since the names were reserved), and `saveMetaItem`, `duplicatePackage`, `importUnmanagedPackage` and `os migrate meta --stored` answer exactly as before. No metadata document needs editing. + + ## The retirement kit + + - **`@objectstack/spec`:** the three exports leave the root; `ApplyConversionsOptions` loses `onConflict` and `reservedNodeTypes`, `ConversionContext` loses `reservedNodeTypes` and `reportConflict`, the `IAutomationService.canonicalizeStoredFlow` result loses `conflicts`; the error-code ledger drops `FLOW_CONVERSION_CONFLICT` by its own "Retiring a code" rule (a row comes out with its last emitter). + - **`@objectstack/service-automation`:** `AutomationEngine.canonicalizeStoredFlow` no longer returns `conflicts`, and the engine no longer builds a live-name set for the conversion pass. + - **`@objectstack/metadata-protocol`:** `StoredFlowCanonicalization` loses `conflicts`; the flow save door's 409, the stored-migration pass's `failed` row for a refused rename, `duplicatePackage`'s failed item and `importUnmanagedPackage`'s 409 are gone with it. + - **The D3 entry `conversion-conflict-channel-removed`.** No D2 conversion and no tombstone: no metadata document ever carried any of it. + - **No deprecation window**, per the project's startup-stage posture. + + ⚠️ **The out-of-repo consumer population is NOT MEASURED.** The three packages are published, so this is breaking for consumers no telemetry was consulted for. + + +- d6bd98a: fix(metadata-protocol): `duplicatePackage` is refused, with nothing copied, when the install of the copy's package record is refused + + Clause-②: no (narrowing) + + + + - **Refused now.** `duplicatePackage`, and `POST /api/v1/packages/:id/duplicate` which calls it, installs the copy's package record before it copies any item. When that install is refused, the duplicate is now refused with that same refusal, and nothing is minted or copied. A copy whose namespace another installed package owns answers `422 NAMESPACE_CONFLICT` with the registry's own sentence (ADR-0048); that covers an explicit `targetNamespace` and one derived from the last segment of `targetPackageId`. A failed `sys_packages` write answers the install's own `500`. Before, both refusals were dropped and the items were copied anyway: bound to an id with no package record (absent from `GET /packages`), after a namespace conflict inside the namespace the other package owns, and the answer read `success: true`. + - **Unchanged.** A duplicate whose package record installs copies exactly as before, with the same answer shape, and per-item failures still ride `failed[]`. A source with no package record still copies without installing one. + - **Remedy.** Choose a target namespace no installed package owns: pass `targetNamespace` explicitly, or pick a `targetPackageId` whose last segment derives a free one. For a deliberate migration, `OS_METADATA_COLLISION=warn` still downgrades the namespace gate to a warning, and the duplicate then proceeds. + - **Logged.** The refusal is logged once at `warn` by `[protocol.duplicatePackage]`, naming the target and source ids and the refusal's code and message. For a store fault the error itself is attached, so its cause reaches the log. + +### Patch Changes + +- ee0e115: docs: the default-record-page remedies name the other package's page, not the whole package + + Clause-②: no + + A second `isDefault: true` record page for one object is refused with `DEFAULT_RECORD_PAGE_CONFLICT`. The package registry's refusal already told the author to keep one default or "compose the deployment without the other package's page". Three sibling texts still told them to drop the whole package, though a deployment can keep a package and leave out the one page it ships: + + - `@objectstack/metadata-protocol`: the metadata save door's refusal now reads "a page a package ships stays the default unless the deployment is composed without that package's page." Only the remedy clause changed; `code`, `status`, `object` and `holders` are unchanged. + - `@objectstack/spec`: `PageSchema.isDefault`'s TSDoc and the error-code ledger's comment on `DEFAULT_RECORD_PAGE_CONFLICT` say the same. + - `@objectstack/runtime`: `AppPlugin`'s private `bundle` field now documents the three readers outside the class that read it structurally, so a rename is not made blind. + + No behaviour changes; nothing to migrate. +- 8684079: **Shipped in 18.0.0-next.2**, whose CHANGELOG missed this entry (#23818); listed here one prerelease late. + + fix(metadata-protocol,metadata): on a database the organization-ownership ceremony has not migrated, the paged family reads, `DatabaseLoader`'s count and the package rollback agree with the environment-only reads; the ceremony's apply is named with `--plan-file` (ADR-0131 D6/D7) + + Clause-②: no + + Every change below matters only where legacy organization rows still sit in the `sys_metadata` family, the database `os serve` refuses to start on until `os migrate organization-ownership` has verified. On a migrated or fresh database every answer is unchanged. + + - **`@objectstack/metadata-protocol`: a paged read leaves the legacy rows out before the page is cut.** `auditMetaItem` (`limit`), `listCommits` (`limit`), `revertStoredPackage`'s first-active-row probe and `SysMetadataRepository.list({ limit })` used to filter a legacy organization's rows out after the limit. A page they sat in came back short, and `revertStoredPackage` answered `RESOURCE_CONFLICT` (409) "has never been published" for a package the environment had published when its first active row was an organization's. When a page holds a legacy row, the read is now repeated with those rows excluded in the query. + - **`@objectstack/metadata-protocol`: `rollbackToPackageCommit` refuses a legacy organization's commit by name.** It answered `COMMIT_NOT_FOUND` (404) for a commit that is there. It now gives `revertCommit`'s refusal: `NOT_OVERRIDABLE` (403), the same sentence, naming the commit and its organization. + - **`@objectstack/metadata`: `DatabaseLoader` counts what it reads.** `exists()` answered true for an address only a legacy organization row held, while `load()` answered null, and `queryHistory`'s `total` counted legacy history rows no page serves. Neither counts them now. + - **`@objectstack/metadata-protocol`: internal cleanup.** The organization plumbing that only ever carried the environment (`null`) is removed from the list merge and the served-row resolution. No export changes. + - **`@objectstack/spec`: wording.** The four ADR-0087 entries `18.sys-metadata{,-audit,-commit,-history}-organization-column-retired` now name the ceremony's reviewed apply, `os migrate organization-ownership --out PLAN` and then `--apply --plan-file PLAN`, which `--plan-file`'s own entry requires. No id, category, version or field the migration engine reads changes. + - **`@objectstack/cli`: wording.** `os migrate apply --allow-destructive`'s refusal on an unmigrated database now reads "a drop the organization-ownership ceremony owns, and the ceremony has not verified on this database". Its `--json` payload is unchanged. +- 66d2757: feat(spec)!: every installed-package row declares where it came from, a closed `source: 'code' | 'marketplace' | 'authored'`, and the `GET /packages` rows declare the `writable` verdict they already served (#23972) + + Clause-②: yes (narrowing — the served row gains two declared keys, and the record schema, the served-row union and the registry's installPackage signature each refuse what they accepted before) + + **BREAKING** — three published accept sets narrow. `InstalledPackageSchema` refuses a row without `source`, or with a value outside `code` / `marketplace` / `authored`. The served-row union `InstalledPackageAtEitherStageSchema` refuses a row without a boolean `writable`. `SchemaRegistry.installPackage` and `EngineSchemaRegistryView.installPackage` take `source` as their second argument and refuse a call without it. The migration is below. None of it is mechanically convertible, because only the door that registers a package knows where it came from. + + **What is new on the wire.** `GET /api/v1/packages` and `GET /api/v1/packages/:id` serve two declared keys on every row: + + - `source` names the package's origin. `code` means the deployment's own code registered it (a `defineStack()` host, a plugin's bundled manifest, a platform package). `marketplace` means the marketplace install-local plugin installed it, and that holds after a restart too, when the plugin rehydrates it from its ledger. `authored` means it is a database-authored package (a Studio base, a duplicate, an HTTP install), including after the restart that rehydrates it from `sys_packages`. The door that registers the package stamps the value. No reader derives it. + - `writable` is the server's own writability verdict (ADR-0070 D2), served on these rows since #14375 and now declared. It is computed per read and never stored, so it is declared on the served row (`InstalledPackageAtEitherStageSchema`) and not on the registry record. + + FROM → TO for a reader of these rows: + + | you did | do instead | + | --- | --- | + | treated `writable: true` as authored, and looked up each non-writable row in the marketplace's installed list (the control plane's, else install-local's) to tell marketplace from code | read `row.source` | + | read the undeclared `writable` | read the declared `writable` (unchanged value) | + + The two keys are separate statements: `source` says where the package came from, and `writable` says what this server lets an org do to it. + + **`InstalledPackageSchema` requires `source`.** It is `InstalledPackageSourceSchema`, exported from `@objectstack/spec/kernel`. A row without it, or with a value outside the closed set, is refused at the `source` key. + + **API change for code that registers packages (`@objectstack/objectql`).** The registry records the origin and never infers it: + + | you wrote | write instead | + | --- | --- | + | `registry.installPackage(manifest)` | `registry.installPackage(manifest, 'code')` (or `'marketplace'` / `'authored'`, whichever is where the package came from) | + | `registry.installPackage(manifest, settings, scope)` | `registry.installPackage(manifest, source, settings, scope)` | + | `ctx.getService('manifest').register(artifact)` for a marketplace install | `ctx.getService('manifest').register(artifact, { source: 'marketplace' })` | + + `SchemaRegistry.installPackage` throws a `TypeError` naming the closed set when `source` is missing or unknown, before anything is recorded. `ObjectQL.registerApp(body, scope?, source?)` and the `manifest` service record `'code'` when no source is given. They accept `'code'` or `'marketplace'` and refuse `'authored'`. The `IObjectQLEngine` registry contract (`@objectstack/spec/contracts`) declares the same `installPackage(manifest, source, settings?)`. + + **The one-line fix:** pass the package's origin as `installPackage`'s second argument. + + Readers: objectui's Setup → Packages Source column and its `?source=` filter (it switches from the two-read join to `source` in its own follow-up), and the client SDK's `packages.list()` / `packages.get()` row types. + + +- Updated dependencies [e375315] +- Updated dependencies [cbbd96a] +- Updated dependencies [b6805ac] +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [30ce56e] +- Updated dependencies [df2c551] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [6422da5] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [4ffdebe] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [e246f06] +- Updated dependencies [a65a59b] +- Updated dependencies [077b75f] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/lint@18.0.0-next.3 + - @objectstack/spec@18.0.0-next.3 + - @objectstack/metadata-core@18.0.0-next.3 + - @objectstack/metadata@18.0.0-next.3 + - @objectstack/core@18.0.0-next.3 + - @objectstack/formula@18.0.0-next.3 + - @objectstack/types@18.0.0-next.3 + - @objectstack/sdui-parser@18.0.0-next.3 + ## 18.0.0-next.2 ### Minor Changes diff --git a/packages/metadata-protocol/package.json b/packages/metadata-protocol/package.json index cd729eff52b..ee2a4915652 100644 --- a/packages/metadata-protocol/package.json +++ b/packages/metadata-protocol/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/metadata-protocol", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "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 e4c017ef5b2..85f5c1c9d41 100644 --- a/packages/metadata/CHANGELOG.md +++ b/packages/metadata/CHANGELOG.md @@ -1,5 +1,72 @@ # @objectstack/metadata +## 18.0.0-next.3 + +### Patch Changes + +- 8684079: **Shipped in 18.0.0-next.2**, whose CHANGELOG missed this entry (#23818); listed here one prerelease late. + + fix(metadata-protocol,metadata): on a database the organization-ownership ceremony has not migrated, the paged family reads, `DatabaseLoader`'s count and the package rollback agree with the environment-only reads; the ceremony's apply is named with `--plan-file` (ADR-0131 D6/D7) + + Clause-②: no + + Every change below matters only where legacy organization rows still sit in the `sys_metadata` family, the database `os serve` refuses to start on until `os migrate organization-ownership` has verified. On a migrated or fresh database every answer is unchanged. + + - **`@objectstack/metadata-protocol`: a paged read leaves the legacy rows out before the page is cut.** `auditMetaItem` (`limit`), `listCommits` (`limit`), `revertStoredPackage`'s first-active-row probe and `SysMetadataRepository.list({ limit })` used to filter a legacy organization's rows out after the limit. A page they sat in came back short, and `revertStoredPackage` answered `RESOURCE_CONFLICT` (409) "has never been published" for a package the environment had published when its first active row was an organization's. When a page holds a legacy row, the read is now repeated with those rows excluded in the query. + - **`@objectstack/metadata-protocol`: `rollbackToPackageCommit` refuses a legacy organization's commit by name.** It answered `COMMIT_NOT_FOUND` (404) for a commit that is there. It now gives `revertCommit`'s refusal: `NOT_OVERRIDABLE` (403), the same sentence, naming the commit and its organization. + - **`@objectstack/metadata`: `DatabaseLoader` counts what it reads.** `exists()` answered true for an address only a legacy organization row held, while `load()` answered null, and `queryHistory`'s `total` counted legacy history rows no page serves. Neither counts them now. + - **`@objectstack/metadata-protocol`: internal cleanup.** The organization plumbing that only ever carried the environment (`null`) is removed from the list merge and the served-row resolution. No export changes. + - **`@objectstack/spec`: wording.** The four ADR-0087 entries `18.sys-metadata{,-audit,-commit,-history}-organization-column-retired` now name the ceremony's reviewed apply, `os migrate organization-ownership --out PLAN` and then `--apply --plan-file PLAN`, which `--plan-file`'s own entry requires. No id, category, version or field the migration engine reads changes. + - **`@objectstack/cli`: wording.** `os migrate apply --allow-destructive`'s refusal on an unmigrated database now reads "a drop the organization-ownership ceremony owns, and the ceremony has not verified on this database". Its `--json` payload is unchanged. +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [30ce56e] +- Updated dependencies [df2c551] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [6422da5] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [4ffdebe] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [e246f06] +- Updated dependencies [a65a59b] +- Updated dependencies [077b75f] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/metadata-core@18.0.0-next.3 + - @objectstack/core@18.0.0-next.3 + - @objectstack/types@18.0.0-next.3 + - @objectstack/metadata-fs@18.0.0-next.3 + ## 18.0.0-next.2 ### Minor Changes diff --git a/packages/metadata/package.json b/packages/metadata/package.json index 464b32e769d..f8039894d61 100644 --- a/packages/metadata/package.json +++ b/packages/metadata/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/metadata", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "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 782ed24091a..88f0de52312 100644 --- a/packages/objectql/CHANGELOG.md +++ b/packages/objectql/CHANGELOG.md @@ -1,5 +1,220 @@ # @objectstack/objectql +## 18.0.0-next.3 + +### Minor Changes + +- d050a8f: fix(spec,objectql)!: an installed or stored page carrying a declaration spec 18 retires converts at load instead of stopping the kernel build — `isDefault` off a bound record page is deleted and `template: 'full-width'` re-spelled `default`, each with a named notice; the authoring, save and publish doors keep refusing + + Clause-②: yes (widening) + + **What changes.** Protocol 18 refused `isDefault: true` on a page that is not a record page bound to an object, and closed the page `template` set (retiring `full-width`), at every door — the page parse (`definePage`, `defineStack`, so `os validate` / `os build`, and the metadata save door) and the kernel's package registration (`SchemaRegistry.installPackage` / `registerItem`, `MisplacedDefaultPageError`). The registration refusal also fired when a kernel was built from a package that was installed BEFORE the retirement: every hosted environment holding such a package failed its kernel build, with no remedy short of upgrading the package in each one. Data at rest now converts, and only data at rest: + + | Door | Before | Now | + | :--- | :--- | :--- | + | `definePage` / `defineStack` (`os validate`, `os build`), the metadata save door, package publish | refused at `isDefault` / `template` | **unchanged** — refused with the same sentence | + | a `sys_metadata` page row read back | served with `_diagnostics.valid: false` | converted on read: the flag deleted, `full-width` → `default`, `OS_METADATA_CONVERTED` notice | + | a compiled artifact inside the window its `engines.protocol` opens | the artifact door refused the artifact | converted before the strict parse, as every retired entry is | + | a package body registered through `manifest.register` / `ObjectQL.registerApp` that declares `engines.protocol` (an installed package, a marketplace install) | `MisplacedDefaultPageError`, kernel build stopped | converted before anything registers, with one `warn` per (package, conversion) naming the conversion id and the first site; the body is left unchanged on disk | + | the same body with NO declared range (a manifest a plugin builds in code), or declaring a floor newer than every retirement | refused | **unchanged** — refused | + + **The two ADR-0087 D2 entries**, both `retiredFromLoadPath` from the day they land (the authoring funnel never replays them): + + - `page-is-default-off-record-page-removed` (`page.isDefault`): deletes `isDefault: true` from exactly the pages `misplacedDefaultPageRefusal` refuses — a `home`, `app`, `utility` or `list` page, or a record page with no `object`. Lossless: nothing read the flag there (an app opens on its first navigation item). A default record page bound to an object keeps its flag. + - `page-template-full-width-to-default` (`page.template`): `'full-width'` → `'default'`. Lossless: the console drew both names with one template component. An unregistered template name (`centered`, `two-column`) and a region the template does not place have no lossless rewrite and are NOT converted — they stay with the parse, and the D3 entries `page-is-default-off-record-page-refused` / `page-template-closed-region-unplaced-refused` name the hand edits. + + **`@objectstack/objectql`** — `ObjectQL.registerApp` (and the `manifest` service, for an artifact carrying `packages[]`) replays the versioned forward conversion (`applyArtifactForwardConversions`, `@objectstack/metadata-core`) over a package body that declares its protocol range, before `installPackage`; the converted copy is what the package record and every registered item carry. Public: `ObjectQL.convertPackageBodyForward(body)`. + + **`@objectstack/metadata-core`** — `applyArtifactForwardConversions` reads the declared range off a FLAT package body (the manifest half beside its collections, no `manifest` key) when the definition carries no `manifest`; a definition with a `manifest` object is read there, as before. + + Migration: none for authors — the authoring doors refuse exactly what they refused. An installed package converts again at every registration until it is rebuilt (`os build`) and re-published with tooling from a release that ships the retirement; the boot log's warn names the conversion and the site. + + +- b25d98a: Clause-②: yes (widening) + + On insert, `previous` is now bound to `null` in validation rules and in declarative hook + conditions, so a rule or condition that reads it decides the insert — the binding a flow's start + condition already gets on its create leg. Until now `previous` was unbound on insert: any + reference to it faulted `Unknown variable: previous`, and the write was refused (validation) or + aborted (hook) as `unevaluable`. So this widens what an insert can pass: a rule shaped + `previous != null && …` ("judge updates only") now ADMITS the inserts it used to refuse with that + fault, and a field's `requiredWhen: previous == null` now requires the field on insert instead of + refusing every insert. `previous == null || …` judges an insert, `previous != null && …` skips + one. A column read off `previous` on insert (`previous.stage`) still has no value and still + refuses the write; the refusal now names that read and the guard to add. + + A violating insert under a rule that reads `previous` now answers with the rule's own message. + The platform's own `validity_window_order` on `sys_user_permission_set` is the measured case: an + inverted grant window answers `400 VALIDATION_FAILED` with "Valid Until must be later than Valid + From …", not a CEL fault. + + The binding is the same for every predicate a write evaluates — a `script` / `cross_field` + condition, a `conditional`'s `when`, a field's `requiredWhen` and an option's `visibleWhen` — + and for a hook's `condition` on `beforeInsert` / `afterInsert`; the validate-only preview judges + an insert the same way. Only the CEL binding is `null`: a hook's `ctx.previous` is unchanged. No + shipped rule's verdict changes: every rule and hook this repository ships that reads `previous` + refused and admitted the same inserts before and after; only the refusal's reason moved. A write + to an existing row whose prior state is not in hand keeps `previous` unbound and still fails + closed, so `previous == null` never reads an update as an insert. +- 66d2757: feat(spec)!: every installed-package row declares where it came from, a closed `source: 'code' | 'marketplace' | 'authored'`, and the `GET /packages` rows declare the `writable` verdict they already served (#23972) + + Clause-②: yes (narrowing — the served row gains two declared keys, and the record schema, the served-row union and the registry's installPackage signature each refuse what they accepted before) + + **BREAKING** — three published accept sets narrow. `InstalledPackageSchema` refuses a row without `source`, or with a value outside `code` / `marketplace` / `authored`. The served-row union `InstalledPackageAtEitherStageSchema` refuses a row without a boolean `writable`. `SchemaRegistry.installPackage` and `EngineSchemaRegistryView.installPackage` take `source` as their second argument and refuse a call without it. The migration is below. None of it is mechanically convertible, because only the door that registers a package knows where it came from. + + **What is new on the wire.** `GET /api/v1/packages` and `GET /api/v1/packages/:id` serve two declared keys on every row: + + - `source` names the package's origin. `code` means the deployment's own code registered it (a `defineStack()` host, a plugin's bundled manifest, a platform package). `marketplace` means the marketplace install-local plugin installed it, and that holds after a restart too, when the plugin rehydrates it from its ledger. `authored` means it is a database-authored package (a Studio base, a duplicate, an HTTP install), including after the restart that rehydrates it from `sys_packages`. The door that registers the package stamps the value. No reader derives it. + - `writable` is the server's own writability verdict (ADR-0070 D2), served on these rows since #14375 and now declared. It is computed per read and never stored, so it is declared on the served row (`InstalledPackageAtEitherStageSchema`) and not on the registry record. + + FROM → TO for a reader of these rows: + + | you did | do instead | + | --- | --- | + | treated `writable: true` as authored, and looked up each non-writable row in the marketplace's installed list (the control plane's, else install-local's) to tell marketplace from code | read `row.source` | + | read the undeclared `writable` | read the declared `writable` (unchanged value) | + + The two keys are separate statements: `source` says where the package came from, and `writable` says what this server lets an org do to it. + + **`InstalledPackageSchema` requires `source`.** It is `InstalledPackageSourceSchema`, exported from `@objectstack/spec/kernel`. A row without it, or with a value outside the closed set, is refused at the `source` key. + + **API change for code that registers packages (`@objectstack/objectql`).** The registry records the origin and never infers it: + + | you wrote | write instead | + | --- | --- | + | `registry.installPackage(manifest)` | `registry.installPackage(manifest, 'code')` (or `'marketplace'` / `'authored'`, whichever is where the package came from) | + | `registry.installPackage(manifest, settings, scope)` | `registry.installPackage(manifest, source, settings, scope)` | + | `ctx.getService('manifest').register(artifact)` for a marketplace install | `ctx.getService('manifest').register(artifact, { source: 'marketplace' })` | + + `SchemaRegistry.installPackage` throws a `TypeError` naming the closed set when `source` is missing or unknown, before anything is recorded. `ObjectQL.registerApp(body, scope?, source?)` and the `manifest` service record `'code'` when no source is given. They accept `'code'` or `'marketplace'` and refuse `'authored'`. The `IObjectQLEngine` registry contract (`@objectstack/spec/contracts`) declares the same `installPackage(manifest, source, settings?)`. + + **The one-line fix:** pass the package's origin as `installPackage`'s second argument. + + Readers: objectui's Setup → Packages Source column and its `?source=` filter (it switches from the two-read join to `source` in its own follow-up), and the client SDK's `packages.list()` / `packages.get()` row types. + + + +### Patch Changes + +- a69a40a: **Shipped in 18.0.0-next.2**, whose CHANGELOG missed this entry (#23818); listed here one prerelease late. + + feat(spec)!: a page declares nothing the console does not honour — `isDefault: true` is refused off a record page bound to an object, `template` closes to the registered layouts, and a region the template does not place is refused + + Clause-②: yes (narrowing) + + **BREAKING (authoring):** two accept sets of `PageSchema` narrow to what the console runs, and the `PageTypeSchema` text stops promising what it does not. + + | Writing | Before | Now | + | :--- | :--- | :--- | + | `isDefault: true` on a `home`, `app`, `utility` or `list` page, or on a record page with no `object` | accepted, stored and served; nothing read it | refused at `isDefault` — by `definePage`, `defineStack` (so `os validate` and `os build`, `STACK_SCHEMA_INVALID` 422), the metadata save door (`422 INVALID_METADATA`) and package registration (`SchemaRegistry.installPackage` / `registerItem`, `422 INVALID_METADATA`) | + | `template: 'full-width'` | accepted; drawn exactly as `default` | refused by name: "Page `template: 'full-width'` was removed in @objectstack/spec 18 … Use `template: 'default'`" | + | any other `template` outside `default`, `header-sidebar-main`, `three-column`, `dashboard` (`two-column`, `single-column`, …) | accepted; drawn as the page type's default layout | refused at `template` | + | on a `kind: 'full'` page, a region its template does not place (`left_sidebar`, `right_sidebar`, `content`, …) | accepted; stacked under `main` at full width, its `width` ignored | refused at `regions.N.name`, naming the template's slots and the rename | + | on a `kind: 'full'` page, a second region filling a slot an earlier one fills (two `main` regions; on `dashboard`, a second `header` or `footer`) | accepted; not drawn at all — the console draws the first region of a name and leaves that name out of the rest | refused at `regions.N.name`, naming the region that already fills the slot | + + Migration: + + - **`isDefault` on a non-record page** → delete it. A page does not choose where an app opens: an app's landing page is its **first navigation item** (by `order`), so to land on the page put its entry first in the app's `navigation`; the root landing follows the app's own `isDefault`. A record page meant as an object's default record page binds that object with `object`. + - **`template: 'full-width'`** → `template: 'default'` (or delete the key — `default` is the default). The page renders exactly as before. + - **A `template` held in a widened `string`** (a shared base object, `{ template: 'default' }` spread into `definePage`) no longer type-checks against the closed set → `template: 'default' as const`. + - **An unregistered template** → one of `default` (header, sidebar, main, aside, footer), `header-sidebar-main` (header, sidebar, main), `three-column` (header, sidebar, main, aside, footer) or `dashboard` (header, footer, and every other region a grid cell). + - **An unplaced region** → name it for the slot it fills: `left_sidebar` → `sidebar`, `right_sidebar` → `aside`, `content` → `main`; or choose `dashboard` when the regions are a grid. Names match case-insensitively, as the console's do. `slotted`, `html` and `react` pages are not judged on region names. + - **A second region for a filled slot** → move its components into the first region of that name, or name it for an unfilled slot. On `dashboard`, two regions of any other one name stay two grid cells and are accepted. + + The template set and each template's slots are now published: `PageTemplateSchema`, `PAGE_TEMPLATE_SLOTS` and `PAGE_TEMPLATE_OPEN_GRID` (`@objectstack/spec/ui`), with the two checks `checkPageDefaultPlacement` and `checkPageRegionPlacement` and the one refusal sentence `misplacedDefaultPageRefusal` that the parse and the registry share. + + `os migrate meta` lists both as hand edits — the D3 entries `page-is-default-off-record-page-refused` and `page-template-closed-region-unplaced-refused`. No D2 conversion rewrites them: where an app opens, and which slot a region fills, are the author's calls. + + **Stored metadata keeps loading.** No conversion touches these shapes and the read path does not refuse them, so a `sys_metadata` page row already stored with a non-record `isDefault: true`, `template: 'full-width'` or an unplaced region is served unchanged and renders as before (a stored row hydrates into the registry without a package, so the registry does not judge it either). Saving such a row again through the metadata API is refused until it is fixed. + + The text, without a contract change: `PageTypeSchema`'s docblock and enum comments now say that `home`, `app` and `utility` are the same region layout at three content widths — `home` is not a landing declaration, and `utility` is an ordinary inline page (the console has no floating or docked shell slot for it); `utility` stays a page type, read by the print door and the page-type width. The `print`-on-`utility` refusal no longer calls a utility page "a floating panel". + + `@objectstack/objectql`: the registry refuses `isDefault: true` on a non-record page a package registers (`MisplacedDefaultPageError`, `INVALID_METADATA` 422), at the package door and at the package-bound item seam, with the page parse's sentence. + + +- abb6df0: Clause-②: no + + fix(cli): `os storage orphans`, the `os secret rewrap` dry run and the `os migrate summary-nulls` dry run say they write no row or table, and that opening a SQLite file can still switch its journal mode, instead of saying nothing was written + + **What was wrong.** Each of these runs boots through the SQL driver before it prints, and the driver's connect switches a SQLite file to the journal mode it asks for (WAL, unless `OS_DATABASE_SQLITE_JOURNAL_MODE=delete` is set in the command's own environment or the project's `.env`). Measured on a file still on a rollback journal, each run moved the header's journal-mode bytes (18 and 19, from 1,1 to 2,2) and its change counter, and changed no row or table, while printing "nothing was written". Nothing was lost. + + **What changed.** Each sentence below now claims no row or table. Where the run opened a SQLite file, the report's line is followed by the note the other read-only arms already carry: "Opening a SQLite file can still switch its journal mode (to WAL by default), which changes its header only." On PostgreSQL or MySQL the line ends without it. + + - `os storage orphans` (`formatStrandedOrphanInventory`, `@objectstack/service-storage`). FROM: "This pass is READ-ONLY: nothing was written, tombstoned or deleted." TO: "This pass is READ-ONLY: no row or table was written, tombstoned or deleted." and the note. + - `os secret rewrap` (the dry run's note, `REWRAP_NOTES.dryRun`, also in `--json` `report.notes`). FROM: "Dry run: every attributed row was opened, re-sealed and verified in memory, and nothing was written. Re-run with --apply to write." TO: "… and no row or table was written. Re-run with --apply to write." The note already follows the dry run's closing line on a SQLite file, so this sentence carries none. + - `os migrate summary-nulls` (the dry run that found NULL roll-ups, `formatSummaryBackfillReport`, `@objectstack/objectql`). FROM: "Dry run — nothing was written. Re-run with --apply to backfill." TO: "Dry run — no row or table was written. Re-run with --apply to backfill." and the note. + + **Unchanged.** No exit code, flag, prompt, refusal or export signature changes, and no `--json` document gains a field; the note is printed in the text output only. What the driver does on connect is unchanged: once a file is in the mode the driver asks for, these runs leave it byte-identical. +- a33f3d5: Clause-②: no + + fix(cli): `os migrate plan`, a refused `os migrate summary-nulls --recompute-undefined-on-empty` name and every `os secret rewrap` run say what they vouch for, and that opening a SQLite file can still switch its journal mode, instead of saying nothing was written + + **What was wrong.** Each of these runs boots through the SQL driver before it prints, and the driver's connect switches a SQLite file to the journal mode it asks for (WAL, unless `OS_DATABASE_SQLITE_JOURNAL_MODE=delete` is set in the command's own environment or the project's `.env`). Measured on a file still on a rollback journal, each run moved the header's journal-mode bytes (18 and 19, from 1,1 to 2,2) and its change counter, and changed no row or table, while printing "a plan writes nothing", "nothing was written" or "a finished run writes nothing". Nothing was lost. + + **What changed.** Each sentence below now names what it vouches for. On a SQLite file, its line is followed by the note the other read-only arms already carry: "Opening a SQLite file can still switch its journal mode (to WAL by default), which changes its header only." On PostgreSQL or MySQL the line ends without it. + + - `os migrate plan`, the unmanaged-table section. FROM: "They are reported for information only — nothing here drops them, and a plan writes nothing." TO: "… and a plan writes no row or table." and the note. + - `os migrate plan`, the declaration boot's note when a plugin's write was refused. FROM: "Refused N write(s) during the declaration boot — a plan writes nothing: …" TO: "… — a plan writes no row or table: …", and the note at the end of that note's line. + - `os migrate plan`, the same note when a raw command was forwarded. FROM: "If those commands only read, nothing was written; if one wrote, it landed." TO: "If those commands only read, no row or table was written; if one wrote, it landed.", and the note at the end of that note's line. + - `os migrate summary-nulls --recompute-undefined-on-empty` naming a roll-up the run cannot find (`backfillSummaryNulls`, `@objectstack/objectql`), in either mode. FROM: "Refused before any row was read; nothing was written." TO: "Refused before the walk read or wrote any row." and the note. Under `--apply` the boot before it can also create a table a never-started database lacks, so the sentence speaks for the walk only. + - `os secret rewrap`, the "Re-running is safe" note, in both modes and in `--json` `report.notes`. FROM: "… and a finished run writes nothing." TO: "… and a finished run writes no sys_secret row." Under `--apply` the note follows it; a dry run's closing line already carries it. A finished `--apply` run on a database that has never been started also creates its tables, so the sentence names the rows the re-wrap writes. + + **Unchanged.** No exit code, flag, prompt, refusal or export signature changes, and no `--json` document gains a field: the `--json` documents carry the changed sentences, and the note is printed in the text output only. What the driver does on connect is unchanged. +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [14aff67] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [cf07168] +- Updated dependencies [f846cc2] +- Updated dependencies [30ce56e] +- Updated dependencies [df2c551] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [6422da5] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [6e18016] +- Updated dependencies [d050a8f] +- Updated dependencies [4ffdebe] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [e246f06] +- Updated dependencies [a65a59b] +- Updated dependencies [077b75f] +- Updated dependencies [d6bd98a] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/metadata-core@18.0.0-next.3 + - @objectstack/metadata-protocol@18.0.0-next.3 + - @objectstack/metadata@18.0.0-next.3 + - @objectstack/core@18.0.0-next.3 + - @objectstack/formula@18.0.0-next.3 + - @objectstack/types@18.0.0-next.3 + ## 18.0.0-next.2 ### Minor Changes diff --git a/packages/objectql/package.json b/packages/objectql/package.json index a4a6d2ead5f..a219b723657 100644 --- a/packages/objectql/package.json +++ b/packages/objectql/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/objectql", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "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 66538af5e19..7c18404a0fc 100644 --- a/packages/observability/CHANGELOG.md +++ b/packages/observability/CHANGELOG.md @@ -1,5 +1,49 @@ # @objectstack/observability +## 18.0.0-next.3 + +### Patch Changes + +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [6422da5] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [a65a59b] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + ## 18.0.0-next.2 ### Patch Changes diff --git a/packages/observability/package.json b/packages/observability/package.json index 0fc5967ccc7..92f17de4afc 100644 --- a/packages/observability/package.json +++ b/packages/observability/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/observability", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "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 1b1529509cb..35a8256bff6 100644 --- a/packages/platform-objects/CHANGELOG.md +++ b/packages/platform-objects/CHANGELOG.md @@ -1,5 +1,256 @@ # @objectstack/platform-objects +## 18.0.0-next.3 + +### Minor Changes + +- 77efc66: feat(platform-objects)!: Setup's organization, team, business-unit and user records carry no Discussion feed: `sys_organization`, `sys_team`, `sys_business_unit` and `sys_user` declare `enable.feeds: false`, so a comment create on one of their records answers `403 FEEDS_DISABLED` + + Clause-②: no (narrowing: the comment create door refuses on four platform objects that accept it today) + + + + **BREAKING** (an accept-set narrowing), shipped as `minor` under the launch-window convention for breaking changes. + + Setup's identity records are directory entries, not discussion surfaces. The four objects declared no `enable.feeds`, so they took the default (`true`), and the console drew "Discussion (0)" with a comment composer on every organization, team, business unit and user record. + + What changes: + + - **The record page.** The console reads the flag (`enable.feeds !== false`), so it no longer draws the Discussion feed or its composer on these four objects' records. The record's activity history is unchanged. + - **The data API.** A `sys_comment` create whose `thread_id` names one of these objects (`sys_user:USER_ID`, `sys_organization:ORG_ID`, `sys_team:TEAM_ID` or `sys_business_unit:UNIT_ID`) answers `403 FEEDS_DISABLED` and writes nothing, through REST, the SDK and every other caller of the data engine. An update that moves an existing comment into one of these threads is refused the same way. + - **Comments already stored** on these records are kept. They stay readable through the data API, and an edit that does not change `thread_id`, such as a body edit, still saves. + + Unchanged: every other platform object and every application object keeps its own `enable.feeds` setting, and an object that declares none still accepts comments. + + Remedy: stop creating comments on these four objects' records. A discussion about a user, a team or an organization belongs on a record of an object that keeps its feed on. +- e246f06: feat(plugin-auth,platform-objects,core)!: an API key an administrator's forced password reset revoked is restored by a platform administrator only; its owner creates a new key (#23993) + + **BREAKING** (an accept-set narrowing at the `sys_api_key` write door), shipped as `minor` under the launch-window convention for breaking changes. For an operator: a key revoked by `POST /api/v1/auth/admin/set-user-password` (the forced reset, which revokes the user's keys by default) can no longer be restored by its owner; a platform administrator restores it with the Restore API Key action, or the owner creates a new key. For an integrator: `PATCH /api/v1/data/sys_api_key/{id}` with `{ revoked: false }` on such a key now answers `403 PERMISSION_DENIED` for its owner, where it answered `200`; and `sys_api_key` gains a readonly `revoked_reason` column. + + Clause-②: yes (narrowing) + + What widens: `sys_api_key` declares one more field, `revoked_reason`. What narrows: the owner's restore of a key whose `revoked_reason` is `admin_password_reset` is refused, where it was admitted. + + + + #23287 ruling C′ makes the forced reset revoke the user's API keys by default, for a suspected compromise: a key that outlives the reset is the hole the reset was meant to close. Its changeset names who acts afterwards: a platform administrator restores such a key, or the owner mints a replacement. The owner could nevertheless restore it through the same door as any revoked key, because the row recorded no reason. Now it does. + + - **`sys_api_key.revoked_reason` (`@objectstack/platform-objects`).** A readonly `select` whose one value is `admin_password_reset`, set when the forced reset revoked the key and cleared when the key is restored. Empty for a key revoked from the API Keys list. Translated in `zh-CN`, `ja-JP` and `es-ES`. + - **The stamp (`@objectstack/plugin-auth`).** The forced reset's revocation writes `revoked_reason: admin_password_reset` beside `revoked: true`, the one place the reason is set. A key already revoked before the reset is not written, so it keeps no reason. + - **The restore guard (`@objectstack/plugin-auth`).** One `beforeUpdate` hook on `sys_api_key`, registered after the ADR-0092 D2 identity write guard. A user-context restore of a key carrying the reason is refused `403 PERMISSION_DENIED` unless the caller has platform-administrator standing (the ADR-0068 D2 rung); the refusal names both roads. Every restore it admits, the administrator's included, clears the reason. Revoking an already-revoked key leaves the reason in place, and the column is not on the D2 whitelist, so no data-API write can clear it. On a multi-row update every row is judged by its own stored state. System-context writes (the mint, the last-used stamp, the forced reset) are never refused. + - **The key door's message (`@objectstack/core`).** `API_KEY_REVOKED` for a key the forced reset revoked now says only a platform administrator can restore it; any other revoked key keeps today's sentence. Code, status and every carried field are unchanged. + + What changes for you: + + - **Refused now:** the owner restoring a key the forced reset revoked: `403 PERMISSION_DENIED`, naming the platform administrator and the new-key road. The administrator's restore works as before and clears the reason. + - **Unchanged:** revoking and restoring a key from the API Keys list, by its owner or an administrator; a forced reset with `revokeSessionsAndKeys: false`, which revokes nothing. + - **Keys revoked by a forced reset before this release** carry no reason, so their owner can still restore them. Such keys exist only where `18.0.0-next.2` or a `main` build since 2026-10-14 ran a forced reset; nothing backfills them, because the reset's audit row records counts, not key ids. + +### Patch Changes + +- 8776119: **Shipped in 18.0.0-next.2**, whose CHANGELOG missed this entry (#23818); listed here one prerelease late. + + feat(spec): `referenceDeletedWhen` beside `referenceVia` marks a polymorphic pointer's target as platform-deleted + + Clause-②: yes + + `FieldSchema` gains one optional key beside `referenceVia`: `referenceDeletedWhen: { field, value }`. On rows where the sibling `select` field `field` holds the option `value`, the record the pointer names was deleted by the platform's own assertion, and a renderer draws a tombstone from that option's own label instead of a link. Example: `record_id: Field.text({ referenceVia: 'object_name', referenceDeletedWhen: { field: 'cancel_reason', value: 'record_deleted' } })`. + + Any other shape is refused at parse, naming the field and the option: + + - the key on a field that declares no `referenceVia` (`FieldSchema`), or an unknown key inside it; + - a `field` that is the declaring field itself, is not declared on the object, is not a `select`, is a `multiple: true` select, or declares no inline `options` (a `picklist`-backed select); a `value` that is not one of the sibling's option values (`ObjectSchema`, at every door: `ObjectSchema.create()`, `defineStack`, raw `.parse()` and the metadata save door). + + Nothing that parsed before is refused: the key is new. Its liveness row is `planned` until the renderer that reads it ships. + + The other packages classify the new key where every field key is classified: + + - **`@objectstack/metadata-core`.** `FIELD_REFERENCE_POSITIONS` deletes a served field's `referenceDeletedWhen` when its `field` names a field the caller cannot read (field-level security); `value` is an option value and never a field name. + - **`@objectstack/driver-sql`.** `FIELD_KEY_STORAGE_CLASS` classifies it `presentation`: no DDL reads it. + - **`@objectstack/platform-objects`.** The field form's new `referenceDeletedWhen` row carries its labels and help text in zh-CN, ja-JP and es-ES. +- 30ce56e: Clause-②: no + + Setup pages describe their records in plain admin sentences. Business Units now reads "Your + company structure — divisions, departments, regions and offices — used to scope data and + reports.", Notification Events "Every notification the platform sent, one row per event.", + Notification Preferences "Which topics and channels each user receives; admins set the + defaults.", Notification Templates "The subject and body each notification topic sends, per + channel and language." and HTTP Deliveries "Outgoing HTTP calls made by webhooks and flows, one + row per call, with its delivery status." Field help on Users, Organizations, Invitations and SSO + Providers, and the metadata-storage and notification-delivery object descriptions, no longer + carry design-record numbers; those references stay in the code comments. The zh-CN, ja-JP and + es-ES translations follow. Wording only: no field, permission or behaviour changes. +- 2e0e451: feat(platform-objects): Setup's menu no longer lists "User Preferences" or "Identity Links". You reach both from the user they belong to: open a user in Setup › Users, and the user's page has a "Preferences" tab and its existing "Linked Accounts" tab. + + Clause-②: no + + Both lists held rows that belong to one user each: identity links are written when a user signs in with a provider, and preferences are what the console remembers as a user works (recent items, favourites, list settings). A menu entry listing them for every user at once was not a page an administrator works from. + + - **Where they are now.** On a user's page, "Preferences" lists that user's preference rows (key, created, updated) and "Linked Accounts" lists their identity links, with the same Unlink action. New, Edit and Delete on the Preferences tab follow the same rules as the old list did. Search finds the user, and the tabs are on the user's page. + - **What did not change.** The two objects, their list views and their addresses are unchanged, so a bookmarked list still opens, and a preference row's breadcrumb still leads to the full list. + - **Nothing to migrate.** No data moves and no setting changes. +- 1ecb08a: Clause-②: no + + The `description` of four metadata types in `DEFAULT_METADATA_TYPE_REGISTRY`, which Setup shows under the + type's title, is now one plain sentence for an administrator instead of ADR vocabulary: + + - `api`: "A stable HTTP URL that other systems call to run a flow or work with records in the app" + - `doc`: "A help page written in Markdown that a package ships or you write yourself" + - `book`: "A table of contents that groups help pages into ordered sections" + - `capability`: "A permission a package defines; you grant it through permission sets" + + The ADR references (ADR-0121, ADR-0046, ADR-0046 §6, ADR-0066 D1) moved to the comments beside the + registry rows. The generated metadata-forms translations carry the new sentences in en, zh-CN, ja-JP + and es-ES. No key, type or accept/refuse result changes. +- adad73f: feat(spec)!: a select option declares its lifecycle `kind` (`open` / `won` / `lost`), and every option of an object's `stageField` must declare it + + Clause-②: yes (narrowing) + + `SelectOptionSchema` gains one optional key, `kind`, a closed vocabulary of three lifecycle categories that means the same on every object: `open` (a step on the way), `won` (the successful end: a deal's Closed Won, a lead's Converted, a task's Done, a case's Resolved) and `lost` (the unsuccessful end: Closed Lost, Unqualified, Cancelled). It is the spelling `record:path` `stages[].terminal` already takes, so the data layer and the page say one thing one way. Example: `{ label: 'Closed Lost', value: 'closed_lost', kind: 'lost' }`. The exported type `SelectOptionKind` names the three values. + + - On a plain single-value `select` / `radio` the key is optional, and an absent kind reads as `open`. Nothing is written in at parse: an option without `kind` parses without it. + - On any other field's options (`multiselect`, `checkboxes`, `tags`, a `multiple: true` select, a lookup's static options) `FieldSchema` refuses it at `options..kind`: a lifecycle is one value per record. + - A shared `picklist`'s options accept it (the same option shape). A form view's option list does not (`FormSelectOptionSchema`): it is refused there as an unknown key whose message points at the object field's own option. + + **BREAKING (authoring):** when an object's `stageField` names a field, every option of that field must declare `kind`. `ObjectSchema` refuses the object otherwise, at every door that parses one (`ObjectSchema.create()`, `defineStack`, `os validate`, `os build`, the metadata save door), with one issue per option at `fields..options..kind` naming the field and the option. There is no default under `stageField`. Also refused, at `stageField`: a stage field that takes its options from a `picklist` (the object cannot see them to check), and a stage field carrying options that holds no single option code per record (`multiselect`, a `multiple: true` select). Unchanged: `stageField: false`, a `stageField` naming no declared field, and one naming a field with no options. + + Migration: add `kind` to each option of the stage field, marking each terminal state `won` or `lost` and every other state `open`: + + - `status: Field.select({ options: [{ label: 'New', value: 'new' }, { label: 'Converted', value: 'converted' }, { label: 'Unqualified', value: 'unqualified' }] })` with `stageField: 'status'` → the same options with `kind: 'open'`, `kind: 'won'` and `kind: 'lost'`. + - A picklist-backed stage field → declare its options inline on the field. + - An object with no linear lifecycle → `stageField: false`. + + No conversion makes this edit, because which option ends a record's life successfully is the author's to say. `os migrate meta` lists the D3 entry `stage-field-option-kind-required` with the prescription. + + The Studio object designer's options repeater offers `kind`; the field form's options repeater lists it. + + The other packages follow the new key: + + - **`@objectstack/platform-objects`.** The metadata-form translation bundles (`./metadata-translations`) carry the options repeater's new `kind` row, with its label and help text in zh-CN, ja-JP and es-ES. + - **`@objectstack/metadata-core`.** The published contract fixture `FLS_CONTRACT_OBJECT` (`@objectstack/metadata-core/testing`) names `salary_grade` as its `stageField`, so its two options now declare `kind: 'open'`, and the fixture meets the stage-field rule: `ObjectSchema` raises no issue at `stageField` or under `fields.salary_grade.options`. The fixture is not otherwise a spec-valid object (its `script` actions carry no `body` or `target`), and this change leaves that as it was. + + +- 8f4260f: Setup's Invitations, API Keys, OAuth Applications and Notification Preferences, Subscriptions and Templates lists say what belongs on them when they are empty, and API Keys gains a Create Key button (#23852) + + Clause-②: no + + **What an admin saw.** An empty Invitations, API Keys or OAuth Applications list read "No identity records — these records are created by the authentication provider … not added by hand here", which is false for all three. The three notification lists read the generic "Nothing here yet". API Keys had no way to create a key at all. + + **What changes.** + + - Every list view of `sys_invitation`, `sys_api_key` and `sys_oauth_application` declares its own `emptyState` (a title and one sentence), and a filtered view names itself: Invitations opens on "No pending invitations", the Revoked keys view reads "No revoked keys". + - `sys_notification_preference`, `sys_notification_subscription` and `sys_notification_template` each ship one list view, `all`, declared only for its `emptyState`: it sets no columns (`columns: []`, so the grid keeps the columns it derived before), no filter, no sort and no page size. + - `sys_api_key` gains `create_api_key` ("Create Key"), a primary list-toolbar action on every API Keys list, Setup's and the Account app's. It calls the existing `POST /api/v1/keys` route with the name you give it; the key is created for you, in the organization you are working in, and the raw key is shown once in a dialog you close after copying it. Nothing about the route changes, and the data API still does not create keys. + - The new copy ships in `en`, `zh-CN`, `ja-JP` and `es-ES`. + + Unchanged: every field, index, permission and the `revoke_api_key` / `restore_api_key` row actions. +- d0fe050: Clause-②: no + + Six help texts an administrator reads in Setup no longer carry ADR numbers. On the Permission Sets edit form, + `isDefault`, `managedBy` and `packageId` are rewritten as plain sentences that say what the field does, and + `adminScope` drops its "(ADR-0090 D12)" prefix. On the Positions edit form, `delegatable` drops "(ADR-0091 D3)". + The view form's End-user controls description drops "(ADR-0047, Airtable Interface parity)". + + The ADR references moved to the comments beside the form rows. The generated metadata-forms translations carry + the new wording in en, zh-CN, ja-JP and es-ES. No key, type or accept/refuse result changes. +- bbdfc05: feat(spec)!: retire the permission set's authored `managedBy` — a provenance key nothing read (#23891) + + **BREAKING** — an author can no longer write `managedBy` (`'package' | 'platform' | 'user'`) on a permission set (`PermissionSetSchema`, ADR-0086 D3): it is refused at parse. Nothing ever read the authored key. No seeder, package upgrade or lock consulted it, so `package` never made a set upgrade-owned, and `platform` or `user` never kept an upgrade off a set. Delete the key. A set's provenance is the package that ships it, decided from that package's artifact and recorded as `packageId`; a set created in this environment carries neither. The `sys_permission_set.managed_by` column is a different surface and is untouched, as is `ObjectSchema.managedBy`. + + Clause-②: yes (narrowing) + + Measured before removal, at `6d1660dc3b`: no shipped permission set authored the key (the example apps, `plugin-security`'s default sets, and HotCRM 4.0.1's seven sets at `1d7148bf2d`: zero, against seven `objects:` controls). Its one writer was Setup's Permission Sets form row, which wrote it into stored rows. The liveness ledger had ruled it `dead` on 2026-10-11. + + ## FROM → TO + + | you wrote (17.7 and earlier) | write instead | + | --- | --- | + | `definePermissionSet({ …, managedBy: 'package' })` | nothing: delete the key; a package-shipped set is the package's because the package ships it (`packageId`) | + | `managedBy: 'platform'` or `managedBy: 'user'` | nothing: delete the key; a set created in this environment carries neither `managedBy` nor `packageId` | + | relying on `managedBy` to keep a package upgrade off a set | clone the packaged set and edit the clone (the packaged-set lock refuses in-place edits) | + + **The one-line fix:** delete `managedBy` from every permission set. + + **What an author who still writes it sees.** `tsc` fails at the authoring site (`PermissionSet` types the key as retired), and the parse (`definePermissionSet()`, `defineStack({ permissions })`, `PUT /api/v1/meta/permission/:name`) refuses it at its own path with the prescription: + + > `permission.managedBy` was removed in @objectstack/spec 18 (ADR-0049 enforce-or-remove, ADR-0086 D3) — nothing ever read the authored key: no seeder, package upgrade or lock consulted it, so `package` never made a set upgrade-owned and `platform` or `user` never shielded one from an upgrade. Delete the key; a set's provenance is the package that ships it, decided from that package's artifact and recorded as `packageId`, and a set created in this environment carries neither. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. + + **What does not stop on it.** A stored `permission` row that Setup's form wrote, and an installed package's built artifact, still load: the rehydration seams (`applyConversionsToStoredItem`, `applyArtifactForwardConversions`) replay the D2 conversion below and strip the key, with a notice naming the set. Only the authoring, save and publish doors refuse it. + + ## The retirement kit + + - **A `retiredKey()` tombstone** on `PermissionSetSchema`, a `strictObject`, so the refusal carries its prescription and `tsc` fails first. `RETIRED_KEYS_BY_MAJOR[18]`: `security/PermissionSet:managedBy`. + - **The D2 conversion `permission-set-managed-by-removed`** (protocol 18, retired from the load path) deletes the key from every `permissions[]` item, whatever it holds, one notice per set. + - **The D3 entry `permission-set-managed-by-retired`** records the judgement the strip cannot make: any runbook that relied on the value to keep an upgrade off a set needs another path. + - **`@objectstack/platform-objects`:** the Permission Sets form's `managedBy` row, its help text and its three option labels (Package / Platform / User) leave Setup in all four locales. + - **No deprecation window**, per the project's startup-stage posture. + + ⚠️ **The out-of-repo consumer population is NOT MEASURED** beyond HotCRM 4.0.1. `@objectstack/spec` is published, so this is breaking for consumers no telemetry was consulted for. Studio's provenance badge reads the value as one of three inputs; it retires in an objectui follow-up. + + +- d731b89: fix(identity): the Invite User dialog offers only the roles the caller may grant, read from the same table the invitation role cap enforces + + Clause-②: yes (widening) + + **What was wrong.** A `delegated_admin` who opened Invite User was offered Owner, Admin and Delegated Admin. The server's invitation role cap refuses all three for that issuer. The `role` param inherited one static option list from the field, and nothing told the client which roles this caller may grant. + + **What is new in `@objectstack/spec` (`identity`).** One table, `INVITATION_ROLE_GRANTORS`, says which issuer grades may invite as each membership role: + + | role | issuers that may grant it | + |---|---| + | `owner` | `owner` | + | `admin`, `delegated_admin` | `owner`, `admin` | + | `member` | any issuer (`INVITATION_ROLE_ANY_ISSUER`) | + + New exports beside it: `invitationRoleGrantors(role)` (a role the closed vocabulary does not list takes the `owner`, `admin` row), `invitationRoleGrantPredicate(role)` (the CEL gate, one `'' in current_user.positions` term per grade, `undefined` for an any-issuer row), `INVITATION_ROLE_OPTIONS` (the role picker's options, each carrying its gate as `visibleWhen`), and the types `InvitationRoleGrantors` and `InvitationRoleOption`. + + **`@objectstack/plugin-auth`.** The invitation role cap now reads that table to decide which invitations it admits. It refuses exactly what it refused before, with the same refusal text. A pin compares it with the previous implementation on 1,517 request and issuer pairs. + + **`@objectstack/platform-objects`.** The `role` param of the three `invite_user` actions (`sys_user`, `sys_member`, `sys_invitation`) declares `INVITATION_ROLE_OPTIONS` itself instead of inheriting the field's list. The option values and English labels are unchanged. A `delegated_admin` is now offered Member only, an admin Admin, Delegated Admin and Member, and an owner all four. The per-option gate is a UI courtesy; the server cap remains the authority. Because the options are declared on the param, their labels now translate under `_actions.invite_user.params.role.options.`, and the bundles carry those keys in every locale. +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [30ce56e] +- Updated dependencies [df2c551] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [6422da5] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [a65a59b] +- Updated dependencies [077b75f] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/metadata-core@18.0.0-next.3 + ## 18.0.0-next.2 ### Minor Changes diff --git a/packages/platform-objects/package.json b/packages/platform-objects/package.json index 47e22daaca2..a0b23bf6167 100644 --- a/packages/platform-objects/package.json +++ b/packages/platform-objects/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/platform-objects", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "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/embedder-openai/CHANGELOG.md b/packages/plugins/embedder-openai/CHANGELOG.md index a83db559430..94903b2399d 100644 --- a/packages/plugins/embedder-openai/CHANGELOG.md +++ b/packages/plugins/embedder-openai/CHANGELOG.md @@ -1,5 +1,49 @@ # @objectstack/embedder-openai +## 18.0.0-next.3 + +### Patch Changes + +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [6422da5] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [a65a59b] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + ## 18.0.0-next.2 ### Patch Changes diff --git a/packages/plugins/embedder-openai/package.json b/packages/plugins/embedder-openai/package.json index 8745e178de1..39b2fdfc66f 100644 --- a/packages/plugins/embedder-openai/package.json +++ b/packages/plugins/embedder-openai/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/embedder-openai", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "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 794cc3af53d..1bc97975bcb 100644 --- a/packages/plugins/knowledge-memory/CHANGELOG.md +++ b/packages/plugins/knowledge-memory/CHANGELOG.md @@ -1,5 +1,53 @@ # @objectstack/knowledge-memory +## 18.0.0-next.3 + +### Patch Changes + +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [6422da5] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [4ffdebe] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [e246f06] +- Updated dependencies [a65a59b] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/core@18.0.0-next.3 + - @objectstack/service-knowledge@18.0.0-next.3 + ## 18.0.0-next.2 ### Patch Changes diff --git a/packages/plugins/knowledge-memory/package.json b/packages/plugins/knowledge-memory/package.json index 68d0bf15926..bfe4eaebda0 100644 --- a/packages/plugins/knowledge-memory/package.json +++ b/packages/plugins/knowledge-memory/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/knowledge-memory", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "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 a750b3ca0cf..05fee12761c 100644 --- a/packages/plugins/knowledge-ragflow/CHANGELOG.md +++ b/packages/plugins/knowledge-ragflow/CHANGELOG.md @@ -1,5 +1,53 @@ # @objectstack/knowledge-ragflow +## 18.0.0-next.3 + +### Patch Changes + +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [6422da5] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [4ffdebe] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [e246f06] +- Updated dependencies [a65a59b] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/core@18.0.0-next.3 + - @objectstack/service-knowledge@18.0.0-next.3 + ## 18.0.0-next.2 ### Patch Changes diff --git a/packages/plugins/knowledge-ragflow/package.json b/packages/plugins/knowledge-ragflow/package.json index 00d70f3505a..7c608462f26 100644 --- a/packages/plugins/knowledge-ragflow/package.json +++ b/packages/plugins/knowledge-ragflow/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/knowledge-ragflow", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "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/organizations/CHANGELOG.md b/packages/plugins/organizations/CHANGELOG.md index 5ef6c7ac077..dc44a0fa927 100644 --- a/packages/plugins/organizations/CHANGELOG.md +++ b/packages/plugins/organizations/CHANGELOG.md @@ -1,5 +1,56 @@ # @objectstack/organizations +## 18.0.0-next.3 + +### Patch Changes + +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [0cfd0c4] +- Updated dependencies [6422da5] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [4ffdebe] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [e246f06] +- Updated dependencies [a65a59b] +- Updated dependencies [ab3ba2c] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/plugin-auth@18.0.0-next.3 + - @objectstack/core@18.0.0-next.3 + - @objectstack/types@18.0.0-next.3 + ## 18.0.0-next.2 ### Patch Changes diff --git a/packages/plugins/organizations/package.json b/packages/plugins/organizations/package.json index a5a4e3dc257..2de7aaf975c 100644 --- a/packages/plugins/organizations/package.json +++ b/packages/plugins/organizations/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/organizations", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "license": "Apache-2.0", "description": "Multi-organization runtime for ObjectStack — registers the `org-scoping` service that turns single-database row-level Organization isolation on: `organization_id` auto-stamp on insert, per-org seed replay, default-organization bootstrap, and the walled-posture membership-policy gate.", "main": "dist/index.js", diff --git a/packages/plugins/plugin-approvals/CHANGELOG.md b/packages/plugins/plugin-approvals/CHANGELOG.md index ddd9ff6f215..4004058c387 100644 --- a/packages/plugins/plugin-approvals/CHANGELOG.md +++ b/packages/plugins/plugin-approvals/CHANGELOG.md @@ -1,5 +1,142 @@ # @objectstack/plugin-approvals +## 18.0.0-next.3 + +### Minor Changes + +- b74434c: Clause-②: yes (widening) + + feat(plugin-approvals): the approval lists show engine-written display labels, which are the step and flow labels, whom a pending request waits on, the record an action is about, and the slot it was taken as (#23889) + + The Requests and Action History lists are generic list views. They show stored columns only, so admins used to read node ids (`manager_review`), `flow:` api names, slot addresses and raw user ids there. The approval engine now writes display columns beside the machine ones, and the list views show those columns. Nothing is removed or renamed. The machine columns (`process_name`, `current_step`, `pending_approvers`, `step_name`, `acted_as`) keep their values, and every query and decision still reads them. + + **`sys_approval_request`**, written by `ApprovalService`: + + - `process_label`: the flow's label, and `current_step_label`: the Approval node's label. Both are snapshots taken when the request opens, from the labels the flow engine already hands to `openNodeRequest`. Renaming the flow later does not rewrite them. + - `pending_approver_labels`: whom the request waits on, one label per pending slot, joined with `, `. A slot that names a person (a user id, or an email an account carries) gets that account's name. Any other slot (a `position:` or `team:` literal) shows its address. The column is rewritten whenever the slate is written (open, a partial tally, reassign, SLA reassign) and cleared with the slate on every terminal write. + - The list views: `process_label` replaces `process_name` in every view. `current_step_label` replaces `current_step` in `all_requests`, `my_pending` and `submitted_by_me`. `pending_approver_labels` is added to `all_requests` and `my_pending`. + + **`sys_approval_action`**: each new row copies these columns from its request: + + - `step_label`: the request's step label. + - `object_name` + `record_id`: the request's record pointer (`record_id` declares `referenceVia: 'object_name'`), so a list can name the record. + - `acted_as_label`: `acted_as` labelled the same way as the waiting-on column. It is null when `acted_as` is null, and also while a person slot's name could not be read (see below). + - The list views: `step_label` replaces `step_name` in `recent`, `all_actions` and `by_actor`. `recent` and `all_actions` add the record pointer and show `acted_as_label` in place of `acted_as`. + + The inbox door's `process_label` / `step_label` now read the new columns first. They fall back to the config snapshot for rows opened earlier, so what they serve is unchanged. + + **Read security.** Every new value is either a column of the request the row's readers can already read, or a name the approvals door already serves those readers for that request: `pending_approver_names`, and the timeline's names. The names come from the same resolver, under the same system read. The generic data door serves both objects only to the callers the approvals door would serve. + + **Rows written before this release are filled at boot.** A new boot-time pass runs on `kernel:ready`, after the action-slot repair. It fills the new columns on rows that hold null. Each request is re-read just before it is written, and the write names only the columns that are still null on that re-read row. Whom a request waits on is written only while the request is still pending on the slate its names were read for. A value a live writer stored, such as the boot sweep or another node, is therefore kept. The only window left is the single engine call between the re-read and the write. Every row it fills keeps its own `updated_at`: the write passes the stored value back under `preserveAudit`, so the request lists, which sort on `updated_at`, keep their order. The action-slot repair's writes keep it the same way. Where `plugin-audit` is composed, the first boot after the upgrade writes one system entry in the audit log and one in the record's activity timeline for each row it fills. A second boot writes nothing. + + - On every request, the flow and step labels come from the chain the approvals door serves: the authored labels the config snapshot carries, else the humanized machine name. The list and the inbox therefore show the same label for the same row, and no request is left blank. + - On a pending request, whom it waits on is filled. + - On every action, the step label and the record pointer are copied from its request, and its acting-as label is filled. An action whose request no longer exists keeps a null pointer. + - The action-slot repair now labels every `acted_as` it writes. + + **Two details.** + + - An empty authored label (the spec allows `label: ''`) is stored as the humanized machine name, never `''`. + - A name that cannot be read because the `sys_user` read failed is stored as null, never as the raw slot address. The failure is logged once at `warn`, and the boot pass fills it on the next boot. A slot that names no person still shows its address. + +### Patch Changes + +- 74b4362: fix(plugin-approvals): `sys_approval_request.record_id` declares that the record it names was deleted when `cancel_reason` is `record_deleted` + + Clause-②: no + + - **The pointer carries its own tombstone predicate.** `record_id` declares `referenceDeletedWhen: { field: 'cancel_reason', value: 'record_deleted' }` beside its `referenceVia: 'object_name'`. A request the platform cancelled because its record was deleted (`status: cancelled`, `cancel_reason: record_deleted`) therefore says, in the served object definition, that the pointer names a record that no longer exists. A renderer draws that option's label (`Related record deleted`) in place of a link, and reads nothing. + - **Nothing is accepted or answered differently.** The sibling is the existing `cancel_reason` select, which already holds `record_deleted`, so the spec's parse-time refusals of the key are satisfied by the shipped object. No export, type, route, stored value or request answer moves. + + A patch: one key on one platform object's served field definition. +- 099cc9d: Clause-②: no + + The approval, security, automation and sharing objects describe their records in plain admin + sentences. Approval Delegations now reads "While someone is away, approvals assigned to them + personally go to their delegate.", Record Shares "Records shared beyond their owner, one row per + grant.", User Positions "Which positions each user holds, one row per assignment.", Audience + Binding Suggestions "A package's suggestion to give one of its permission sets to everyone who + signs in, and the decision taken on it." and Automation Runs "Paused automation runs waiting to + resume, and the recent history of finished runs." Field help on Approval Requests, Approval + Delegations, User Positions, User Permission Sets and Audience Binding Suggestions, the + descriptions of the `organization_admin_no_bypass`, `mcp_agent_data_read` and + `mcp_agent_data_write` permission sets, the flow-dispatch ledger's description and the + reserved-position refusal no longer carry design-record numbers or runtime vocabulary; those + references stay in the code comments. The zh-CN, ja-JP and es-ES translations follow. Wording + only: no field, permission or behaviour changes. +- 44fe210: Clause-②: no + + fix(plugin-approvals): every approval notification body names its record by the object's label and the record id + + - **What changed.** The reminder, SLA-breach (to approvers and to the submitter), escalation, hand-off, revision-limit, send-back (with no note) and both out-of-office notification bodies named the record as `/`, for example `crm_deal/D_123`. They now name it the way `approval.requested` already did: the object's label and the record id, for example `Deal D_123`. An object whose schema declares no label is named by its API name and the id, for example `crm_memo D_123`. + - **No title.** No body carries the record's title, from the live record or from the submit snapshot. The body is stored and may be e-mailed, and it has no single reader whose field access it could be narrowed for. + - **Unchanged.** Each sentence's other wording, every topic, title, `actionUrl`, action link and audience, and the `DUPLICATE_REQUEST` refusal text. +- b75129f: Clause-②: no + + fix(plugin-approvals): an approval request's record title reads the flow's label, not `flow:NAME` + + - **`sys_approval_request.display_title` reads `process_label`.** The title is now the flow's label and the record id (`Discount Sign-off · opp_45`), the same label the request lists show. A row with no label stored falls back to `process_name` (`flow:deal_desk · opp_42`), so the title never reads `null · …`. Every surface that draws the request's title through `nameField` shows it: the record page's heading and breadcrumb, and the request cell of the action history list. + - **`titleFormat` still names `{process_name}`.** A placeholder template cannot fall back, and `process_label` can be null after the boot fill (a row it could not label, or a failed fill run). A renderer that reads `titleFormat` ahead of `nameField`, such as the global search palette, still shows the machine source. + + A patch: one formula on one platform object's computed title. No export, type, accepted value or stored column changes. +- 544b8d7: Clause-②: no + + The two out-of-office approval notifications name people by their display name instead of their raw user id. The delegate's `approval.ooo_substituted` body used to read `You are covering an approval on Deal D_123 while u_ooo is out of office.`, and now names the absent approver: `… while Olive Away is out of office.` The absent approver's `approval.ooo_skipped` body used to read `… was routed to u_bob while you are out of office.`, and now names the delegate: `… was routed to Bob …`. + + The names come from the same user-name lookup the approvals inbox uses for pending approvers and the timeline, read once per opening for every person the out-of-office routing touched. An account row with no name is named by its email, as the inbox names it. A person with no readable name (no account row for the id, or a failed read) is still named by the user id, as before. The `ooo_substitute` entry on the request's audit trail is unchanged and still records the two user ids (`u_ooo → u_bob — reason`). +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [30ce56e] +- Updated dependencies [2e0e451] +- Updated dependencies [df2c551] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [77efc66] +- Updated dependencies [6422da5] +- Updated dependencies [8f4260f] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [4ffdebe] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [e246f06] +- Updated dependencies [a65a59b] +- Updated dependencies [077b75f] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/metadata-core@18.0.0-next.3 + - @objectstack/platform-objects@18.0.0-next.3 + - @objectstack/core@18.0.0-next.3 + - @objectstack/formula@18.0.0-next.3 + - @objectstack/types@18.0.0-next.3 + ## 18.0.0-next.2 ### Patch Changes diff --git a/packages/plugins/plugin-approvals/package.json b/packages/plugins/plugin-approvals/package.json index d345bcdd185..90c6ef2d207 100644 --- a/packages/plugins/plugin-approvals/package.json +++ b/packages/plugins/plugin-approvals/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/plugin-approvals", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "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 79ac3cc9dce..e3fb327ba69 100644 --- a/packages/plugins/plugin-audit/CHANGELOG.md +++ b/packages/plugins/plugin-audit/CHANGELOG.md @@ -1,5 +1,119 @@ # @objectstack/plugin-audit +## 18.0.0-next.3 + +### Minor Changes + +- 5b6c257: feat(plugin-audit): `sys_activity` rows carry two writer-stamped read-rule columns, and the withheld-row rule is a predicate over them, with no SYSTEM pre-scan (ADR-0139 E6) + + **Run `os migrate activity-changed-fields --apply` at once after upgrading.** After the upgrade, and until it runs, the `sys_activity` rows written before the upgrade carry no stamp and are withheld from every non-system reader: activity feeds, the header bell and record timelines show only the rows written since the upgrade. The door is `os migrate activity-changed-fields`, dry run by default, `--apply` to write, idempotent, and it stamps both columns in one run. Its flags are `os migrate audit-metadata-bodies`'s, one for one (`--database-url`, `--apply`, `--yes`, `--force`, `--json`). The columns arrive with the upgraded deployment's schema sync, which an `--apply` run performs itself; a dry run against a database no upgraded process has opened cannot read them and exits `1`. + + Clause-②: yes + + `sys_activity` gains two columns, stamped at write time by one derivation (`planActivityStamp`): the CRUD mirror stamps its own rows, and a `sys_activity` before-insert hook stamps every row any other writer inserts (an app's own server action, the seed loader), over whatever value that writer sent. + + - `changed_fields` (`tags`) — the key set of the recorded change: for an update, every field its recorded `old` / `new` values name; `[]` for a create, a delete, a note, or an update that touched only `internal` fields. It is served narrowed to the fields the reader is served. + - `milestone_key` (`text`) — for a fired activity milestone, one sorted, comma-joined key of every field the object's `activityMilestones` declaration watched when it fired; `null` for every other row. It is never served. + + The withheld-row rule is now a WHERE over those two columns, one branch per parent object, on `find`, `findOne`, `count` and `aggregate`. It reads no row, so the 2,000-row SYSTEM pre-scan and its window are gone: a broad read of the stream is served every row the caller may read, with a true `total`. What the rule withholds: + + - an update row whose `changed_fields` is non-empty and names no field the reader is served (unchanged); + - a fired milestone row, unless its key equals the key of the object's CURRENT `activityMilestones` declaration and the reader is served every field that declaration watches. **The cost:** a milestone row written under an earlier declaration, or naming a since-removed field, is withheld from every non-exempt reader until retention ages it out (14 days declared; `lifecycle.retention_overrides` may lengthen it); + - an unstamped row, from every non-system reader: one at rest from before the upgrade until the backfill stamps it, and one the planner cannot judge (a milestone row naming no readable watched-field list), which stays unstamped. + + The parent-field query guard covers both columns as it covers `metadata`: a reader withheld any field of the parent is refused a filter, sort, group or aggregate over `changed_fields` or `milestone_key` (`PERMISSION_DENIED` / 403). + + `@objectstack/plugin-audit` exports the backfill, `backfillActivityChangedFields` (with `ACTIVITY_BACKFILL_PAGE_SIZE` and its report and engine types), and the planner, `planActivityStamp` (with `ACTIVITY_CHANGED_FIELDS_COLUMN`, `ACTIVITY_MILESTONE_KEY_COLUMN` and `ActivityStamp`). Inside the module (none of it is exported from the package), `computeWithheldUpdateFilter` keeps its name and now builds the predicate, `isWithheldOnlyUpdate` keeps its name as the update half over `changed_fields`, and `isWithheldMilestone` / `isWithheldRow` are gone with the pre-scan they judged for. +- 67e8792: feat(plugin-audit): `sys_comment` rows carry a writer-stamped parent pair, `object_name` + `record_id`, and the comment-thread read gate narrows by it with one `ReadableIds` leaf per parent object, with no pre-scan (ADR-0139 E7) + + **Run `os migrate comment-parent-pair --apply` at once after upgrading.** After the upgrade, and until it runs, the `sys_comment` rows written before the upgrade carry no pair, and the read gate answers them as it answered every comment before: by a SYSTEM pre-scan of at most 2,000 candidate rows per read, fail closed. So a broad comment list — one not scoped to a single record's thread — can omit comments the reader may see, and its `total` agrees with the short list; a record's own comment thread is unaffected. The door is `os migrate comment-parent-pair`, dry run by default, `--apply` to write, idempotent, and it stamps both columns in one run, keeping each row's `updated_at`. Its flags are `os migrate activity-changed-fields`'s, one for one (`--database-url`, `--apply`, `--yes`, `--force`, `--json`). The columns arrive with the upgraded deployment's schema sync, which an `--apply` run performs itself; a dry run against a database no upgraded process has opened cannot read them and exits `1`. + + Clause-②: yes + + `sys_comment` gains two `readonly` text columns and an index over them. They are derived from `thread_id` (`{object}:{record_id}`, split on the first colon) by one planner, `planCommentParentPair`, and `thread_id` stays the key authors write: nothing changes for a writer. A `sys_comment` before-insert hook stamps every inserted row, whoever writes it, over whatever value the writer sent; an update that names `thread_id` (a re-point) is re-stamped from it; and an update that names either column without a thread has the pair re-derived from the row's stored thread, for every caller — a historical import (`preserveAudit`) included, where the engine's update-side `readonly` strip keeps a value — so no writer's value for either column lands. A multi-row update that names either column without a thread is refused whole (`VALIDATION_ERROR` / 400, nothing written) when its rows' threads name different records, since one write cannot store each row's own pair; over rows sharing one thread it stores that thread's pair. A thread that names no record stamps `null` / `null`, and such a comment stays hidden from every non-system reader, as before. + + The comment-thread read gate (`installCommentReadVisibility`) is now the parent-record gate the activity stream and the compliance ledger use, over the comment's own pair: one branch `{ object_name: P, record_id: { $in: ReadableIds.of(P) } }` per registered object other than `sys_comment`, ORed and ANDed into the read's `where`, on `find`, `findOne`, `count` and `aggregate`. A broad comment read is served every comment the reader may read, on any table size, with a true `total`. What keeps the bounded 2,000-row fail-closed probe, chosen up front by name, never by catching a refusal: + + - ADR-0139 D5's three classes: a delegated (on-behalf-of) caller (the whole read); a parent narrowed by an object-scoped read middleware (`sys_attachment`, `sys_comment_reaction`, `sys_approval_request`, …); a parent bound to another datasource; + - a security service that answers no `canReadObject`, or none at all (no leaf is written without it), and an engine that cannot classify a parent — each sends the whole read to the probe and is said once; + - a row with no stamped pair (until the backfill reaches it): probed by its `thread_id`, restricted to rows with no pair, never leafed; + - a composition whose registered `sys_comment` declares no pair: the thread probe over every row, said once. + + **The cost:** a deployment whose driver compiles no `ReadableIds` leaf — `driver-mongodb`, until ADR-0139's O3 — now fails a non-system comment read with `INTERNAL_ERROR` / 500 naming the driver in the log, as the activity, ledger and attachment reads already do (ADR-0139 D3); the gate does not catch that refusal into an empty list. + + Comment reactions are unchanged: their only parent is `sys_comment`, which this gate narrows by an object-scoped middleware, so the reaction gate keeps the bounded probe (ADR-0139 D5.2). + + `installCommentReadVisibility` takes an optional third argument, the security source whose `canReadObject` admits a leaf (`ParentObjectReadAdmissionSource`); without it every comment read is the bounded probe, as before. Its first parameter widens from `CommentAccessEngine` to `ParentRecordGateEngine` (`CommentAccessEngine` plus three optional members: a registry view, `hasObjectMiddleware` and `resolveEffectiveDatasource`); every existing caller's engine still satisfies it. Both types reach the published declaration through that signature. `CommentAccessEngine` gains an optional `getSchema`. `@objectstack/plugin-audit` exports the backfill, `backfillCommentParentPair` (with `COMMENT_PARENT_PAIR_BACKFILL_PAGE_SIZE` and its report and engine types), the planner `planCommentParentPair` (with `COMMENT_PARENT_OBJECT_COLUMN`, `COMMENT_PARENT_RECORD_COLUMN` and the `CommentParentPair` type). + +### Patch Changes + +- 94114c1: fix(plugin-audit): the audit ledger no longer records a user's own writes to their own UI-state row (`sys_user_preference`). A deployment that exports `sys_audit_log` to a SIEM or a compliance archive stops receiving those rows from this release on, and nothing else tells that consumer. + + Clause-②: no + + The console saves a user's list preferences, recent items, favourites and palette recents as that user's own `sys_user_preference` rows, one write per click. Each write landed an `update` row in `sys_audit_log`, so Setup's Audit Logs opened on a run of one user's clicks, and the System Overview's "Events by User" chart counted them. + + - **What changed.** When the acting user is the row's owner (`user_id`) on both sides of the write, the audit writer records neither the `sys_audit_log` row nor the `sys_activity` row. It already skipped the activity row for such a write; it now skips the ledger row by the same test. A create, an update and a delete are all covered. + - **What is still recorded.** Every other actor's write to the same rows: an administrator editing someone's preferences from Setup, a system write with no user, and a write that moves a row to another owner. `sys_user_preference` is not an excluded object, and every other object is unchanged. + - **Nothing to migrate.** Rows already in `sys_audit_log` stay. + + The list of per-user UI-state objects is closed and declared inside the plugin, and today holds one object, `sys_user_preference`. No metadata key adds an object to it. +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [abb6df0] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [30ce56e] +- Updated dependencies [2e0e451] +- Updated dependencies [df2c551] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [77efc66] +- Updated dependencies [6422da5] +- Updated dependencies [8f4260f] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [a33f3d5] +- Updated dependencies [d050a8f] +- Updated dependencies [b25d98a] +- Updated dependencies [4ffdebe] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [e246f06] +- Updated dependencies [a65a59b] +- Updated dependencies [077b75f] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/metadata-core@18.0.0-next.3 + - @objectstack/objectql@18.0.0-next.3 + - @objectstack/platform-objects@18.0.0-next.3 + - @objectstack/core@18.0.0-next.3 + - @objectstack/types@18.0.0-next.3 + ## 18.0.0-next.2 ### Minor Changes diff --git a/packages/plugins/plugin-audit/package.json b/packages/plugins/plugin-audit/package.json index 538b75eac55..e904349919c 100644 --- a/packages/plugins/plugin-audit/package.json +++ b/packages/plugins/plugin-audit/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/plugin-audit", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "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 cf6909a1746..aa6daf44bfa 100644 --- a/packages/plugins/plugin-auth/CHANGELOG.md +++ b/packages/plugins/plugin-auth/CHANGELOG.md @@ -1,5 +1,172 @@ # Changelog +## 18.0.0-next.3 + +### Minor Changes + +- 9cb9d95: feat(spec,plugin-auth): a short-lived principal-session mint, `IAuthService.mintPrincipalSession`, implemented by `AuthManager` + + Clause-②: yes (widening) + + An in-process service can now act as the requesting user through a door that only accepts a session, such as a headless browser rendering a page the user may see, under the user's own principal and never a system one. Nothing is removed and no HTTP route is added. + + - **`@objectstack/spec`** (`contracts`): an optional `IAuthService` member, `mintPrincipalSession(context, options?)`, its types `PrincipalSession`, `PrincipalSessionCookie` and `PrincipalSessionMintOptions`, and the constant `PRINCIPAL_SESSION_MAX_TTL_SECONDS` (`120`). The member is optional, so every existing `IAuthService` implementation compiles unchanged. + - **`@objectstack/plugin-auth`**: `AuthManager.mintPrincipalSession(context, options?)`. + + What the mint guarantees: + + - **Human principals only, refused before anything is minted.** A context with no user, a system context, or a `principalKind` other than `human` gets `PERMISSION_DENIED` / `403`. So does a user row that is missing, the system user or banned. A user an authentication policy holds gets `403` with the policy's own code (`MFA_REQUIRED`, `PASSWORD_EXPIRED`, `PASSWORD_RESET_REQUIRED`), whether the posture rides on the context or is computed at the call. A `ttlSeconds` outside 1 to 120 gets `VALIDATION_ERROR` / `400`: it is refused, never clamped. + - **Short and never renewed.** The session lives at most 120 seconds. Reading it never extends it, on the cookie lane, the bearer lane or the in-process session read alike. + - **Revocable.** `revoke()` ends the session. Afterwards neither the token nor the cookie resolves one. It is bound to that session alone and safe to call twice, so call it in a `finally`. + - **The deployment's own cookie.** The cookie carries the name and attributes of the plugin's session cookie, the cookie prefix and, on a secure deployment, `__Secure-` included. The session's active organization is the context's `tenantId`. + + ```ts + const auth = ctx.getService('auth'); + const session = await auth.mintPrincipalSession?.(context, { ttlSeconds: 60 }); + try { + // send session.token as `Authorization: Bearer`, or set session.cookie in a browser + } finally { + await session?.revoke(); + } + ``` +- e246f06: feat(plugin-auth,platform-objects,core)!: an API key an administrator's forced password reset revoked is restored by a platform administrator only; its owner creates a new key (#23993) + + **BREAKING** (an accept-set narrowing at the `sys_api_key` write door), shipped as `minor` under the launch-window convention for breaking changes. For an operator: a key revoked by `POST /api/v1/auth/admin/set-user-password` (the forced reset, which revokes the user's keys by default) can no longer be restored by its owner; a platform administrator restores it with the Restore API Key action, or the owner creates a new key. For an integrator: `PATCH /api/v1/data/sys_api_key/{id}` with `{ revoked: false }` on such a key now answers `403 PERMISSION_DENIED` for its owner, where it answered `200`; and `sys_api_key` gains a readonly `revoked_reason` column. + + Clause-②: yes (narrowing) + + What widens: `sys_api_key` declares one more field, `revoked_reason`. What narrows: the owner's restore of a key whose `revoked_reason` is `admin_password_reset` is refused, where it was admitted. + + + + #23287 ruling C′ makes the forced reset revoke the user's API keys by default, for a suspected compromise: a key that outlives the reset is the hole the reset was meant to close. Its changeset names who acts afterwards: a platform administrator restores such a key, or the owner mints a replacement. The owner could nevertheless restore it through the same door as any revoked key, because the row recorded no reason. Now it does. + + - **`sys_api_key.revoked_reason` (`@objectstack/platform-objects`).** A readonly `select` whose one value is `admin_password_reset`, set when the forced reset revoked the key and cleared when the key is restored. Empty for a key revoked from the API Keys list. Translated in `zh-CN`, `ja-JP` and `es-ES`. + - **The stamp (`@objectstack/plugin-auth`).** The forced reset's revocation writes `revoked_reason: admin_password_reset` beside `revoked: true`, the one place the reason is set. A key already revoked before the reset is not written, so it keeps no reason. + - **The restore guard (`@objectstack/plugin-auth`).** One `beforeUpdate` hook on `sys_api_key`, registered after the ADR-0092 D2 identity write guard. A user-context restore of a key carrying the reason is refused `403 PERMISSION_DENIED` unless the caller has platform-administrator standing (the ADR-0068 D2 rung); the refusal names both roads. Every restore it admits, the administrator's included, clears the reason. Revoking an already-revoked key leaves the reason in place, and the column is not on the D2 whitelist, so no data-API write can clear it. On a multi-row update every row is judged by its own stored state. System-context writes (the mint, the last-used stamp, the forced reset) are never refused. + - **The key door's message (`@objectstack/core`).** `API_KEY_REVOKED` for a key the forced reset revoked now says only a platform administrator can restore it; any other revoked key keeps today's sentence. Code, status and every carried field are unchanged. + + What changes for you: + + - **Refused now:** the owner restoring a key the forced reset revoked: `403 PERMISSION_DENIED`, naming the platform administrator and the new-key road. The administrator's restore works as before and clears the reason. + - **Unchanged:** revoking and restoring a key from the API Keys list, by its owner or an administrator; a forced reset with `revokeSessionsAndKeys: false`, which revokes nothing. + - **Keys revoked by a forced reset before this release** carry no reason, so their owner can still restore them. Such keys exist only where `18.0.0-next.2` or a `main` build since 2026-10-14 ran a forced reset; nothing backfills them, because the reset's audit row records counts, not key ids. +- ab3ba2c: Clause-②: no (narrowing) + + fix(plugin-auth)!: sixteen of this package's own routes refuse a declared JSON body that does not parse with `400 INVALID_REQUEST`, instead of answering as if no body was sent + + + + **BREAKING** (an accept-set narrowing), shipped as `minor` under the launch-window convention for breaking changes. + + These routes read a body that does not parse as no body, through `try { body = await request.json() } catch { body = {} }` or a module-private `parseJson` that answered `{}` from its `catch`, so a truncated body was answered as if the client had sent none: + + - `POST /api/v1/auth/admin/oauth2/toggle-disabled`, `/admin/unlock-user` and `/sys-oauth-application/register`; + - `POST /api/v1/auth/admin/import-users`; + - `POST /api/v1/auth/admin/sso/register`, `/admin/sso/register-saml`, `/admin/sso/request-domain-verification` and `/admin/sso/verify-domain`; + - `POST /api/v1/auth/send-verification-email`; + - `POST /api/v1/auth/admin/ban-user` and `/admin/unban-user`; + - `POST /api/v1/auth/admin/set-user-manager`; + - `POST /api/v1/auth/admin/create-user` and `/admin/set-user-password`; + - `POST /api/v1/auth/organization/add-member`; + - `POST /api/v1/auth/set-initial-password`. + + `/admin/unlock-user` answered `400 "userId is required"` for a body that carried `userId` and lacked its closing brace. `/send-verification-email` read the same input as the one-click resend, and sent the link to the caller's own address for a body that named another one. + + What changes for an integrator. A body sent as `application/json` (matched case-insensitively) that is not blank and does not parse: FROM the route's answer for an empty body, TO `400` with `{ "success": false, "error": { "code": "INVALID_REQUEST", "message": "The request body could not be parsed as JSON (it was sent as application/json). It was refused rather than read as an empty body: send a well-formed JSON document, or no body at all." } }`, the answer the HTTP transports give the same body. A declared JSON body whose stream cannot be read is refused with the same code and "The request body could not be read. It was refused rather than read as an empty body." Each route refuses where it reads its body. The import, the four SSO bridges, ban/unban, set-user-manager, create-user, set-user-password and add-member read it after their platform-admin gate (and after the admin-plugin or organization-plugin check where the route has one). toggle-disabled, unlock-user, sys-oauth-application/register, the resend and set-initial-password read it before anything else runs. Nothing is looked up, written, sent or forwarded to better-auth. + + The exported bridges eleven of these routes mount (`runAdminImportUsers`, `runRegisterSsoProviderFromForm`, `runRegisterSamlProviderFromForm`, `runRequestDomainVerification`, `runVerifyDomain`, `runResendVerificationEmail`, `runSetInitialPassword`, `runAdminCreateUser`, `runAdminSetUserPassword`, `runAdminBanUser` and `runAdminUnbanUser`) answer the same refusal, so a host that mounts them itself refuses it too. Their signatures and result types are unchanged. + + **Remedy, in one line.** Send a JSON body as well-formed JSON with `Content-Type: application/json`, or send no body. + + **Unchanged.** An empty body (no body at all, a zero-byte one, or whitespace only) is still no body, and every route answers it as before: `/admin/unlock-user` with `400 "userId is required"`, and `/send-verification-email` as the one-click resend to the caller's own address. A body that parses is read as before, whatever its `Content-Type`, and a body that does not declare JSON and does not parse is still read as no body. + +### Patch Changes + +- 0cfd0c4: Clause-②: no + + A session minted by `IAuthService.mintPrincipalSession` is no longer refreshed by a read. Before, + `get-session` (and every endpoint that reads the session) still renewed it on each request: #23772's + guard held its expiry, but the row's `updatedAt` was rewritten, the session cookie was re-set with a + 7-day `Max-Age`, and `bearer()` answered `set-auth-token`. The console reads that header as a token + rotation, so a browser carrying a minted cookie stayed on "Opening your console…" and the print + service could not render under the requesting user. A request that carries a minted session now + sets better-auth's own per-request skip-refresh switch, on the cookie lane, the bearer lane and + in-process. The mint's contract, TTL, refusals and revoke are unchanged. +- d731b89: fix(identity): the Invite User dialog offers only the roles the caller may grant, read from the same table the invitation role cap enforces + + Clause-②: yes (widening) + + **What was wrong.** A `delegated_admin` who opened Invite User was offered Owner, Admin and Delegated Admin. The server's invitation role cap refuses all three for that issuer. The `role` param inherited one static option list from the field, and nothing told the client which roles this caller may grant. + + **What is new in `@objectstack/spec` (`identity`).** One table, `INVITATION_ROLE_GRANTORS`, says which issuer grades may invite as each membership role: + + | role | issuers that may grant it | + |---|---| + | `owner` | `owner` | + | `admin`, `delegated_admin` | `owner`, `admin` | + | `member` | any issuer (`INVITATION_ROLE_ANY_ISSUER`) | + + New exports beside it: `invitationRoleGrantors(role)` (a role the closed vocabulary does not list takes the `owner`, `admin` row), `invitationRoleGrantPredicate(role)` (the CEL gate, one `'' in current_user.positions` term per grade, `undefined` for an any-issuer row), `INVITATION_ROLE_OPTIONS` (the role picker's options, each carrying its gate as `visibleWhen`), and the types `InvitationRoleGrantors` and `InvitationRoleOption`. + + **`@objectstack/plugin-auth`.** The invitation role cap now reads that table to decide which invitations it admits. It refuses exactly what it refused before, with the same refusal text. A pin compares it with the previous implementation on 1,517 request and issuer pairs. + + **`@objectstack/platform-objects`.** The `role` param of the three `invite_user` actions (`sys_user`, `sys_member`, `sys_invitation`) declares `INVITATION_ROLE_OPTIONS` itself instead of inheriting the field's list. The option values and English labels are unchanged. A `delegated_admin` is now offered Member only, an admin Admin, Delegated Admin and Member, and an owner all four. The per-option gate is a UI courtesy; the server cap remains the authority. Because the options are declared on the param, their labels now translate under `_actions.invite_user.params.role.options.`, and the bundles carry those keys in every locale. +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [1eac7ad] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [30ce56e] +- Updated dependencies [2e0e451] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [77efc66] +- Updated dependencies [6422da5] +- Updated dependencies [8f4260f] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [5c2ac83] +- Updated dependencies [4ffdebe] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [aefab7c] +- Updated dependencies [66d2757] +- Updated dependencies [e246f06] +- Updated dependencies [a65a59b] +- Updated dependencies [c1c0a90] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/rest@18.0.0-next.3 + - @objectstack/platform-objects@18.0.0-next.3 + - @objectstack/service-messaging@18.0.0-next.3 + - @objectstack/core@18.0.0-next.3 + - @objectstack/types@18.0.0-next.3 + ## 18.0.0-next.2 ### Minor Changes diff --git a/packages/plugins/plugin-auth/package.json b/packages/plugins/plugin-auth/package.json index 6a093a2b797..8c77a5dfb6b 100644 --- a/packages/plugins/plugin-auth/package.json +++ b/packages/plugins/plugin-auth/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/plugin-auth", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "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 f6ea6e5c269..c14ffbf36ef 100644 --- a/packages/plugins/plugin-dev/CHANGELOG.md +++ b/packages/plugins/plugin-dev/CHANGELOG.md @@ -1,5 +1,94 @@ # @objectstack/plugin-dev +## 18.0.0-next.3 + +### Patch Changes + +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [b9dbc3d] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [1eac7ad] +- Updated dependencies [9fcea20] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [7428da0] +- Updated dependencies [b55be42] +- Updated dependencies [abb6df0] +- Updated dependencies [0b1179f] +- Updated dependencies [8776119] +- Updated dependencies [f7a5ebe] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9d4fb3c] +- Updated dependencies [c5cf546] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [34b740f] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [0cfd0c4] +- Updated dependencies [ae4dfaf] +- Updated dependencies [099cc9d] +- Updated dependencies [6422da5] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2e15620] +- Updated dependencies [0cb45e9] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [605aa01] +- Updated dependencies [a33f3d5] +- Updated dependencies [d050a8f] +- Updated dependencies [9e797a8] +- Updated dependencies [b25d98a] +- Updated dependencies [75ced06] +- Updated dependencies [5fb8d15] +- Updated dependencies [5c2ac83] +- Updated dependencies [4ffdebe] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [aefab7c] +- Updated dependencies [66d2757] +- Updated dependencies [e246f06] +- Updated dependencies [a65a59b] +- Updated dependencies [6dede7a] +- Updated dependencies [62f959d] +- Updated dependencies [c1c0a90] +- Updated dependencies [ab3ba2c] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] +- Updated dependencies [cdf3777] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/service-storage@18.0.0-next.3 + - @objectstack/plugin-security@18.0.0-next.3 + - @objectstack/rest@18.0.0-next.3 + - @objectstack/runtime@18.0.0-next.3 + - @objectstack/objectql@18.0.0-next.3 + - @objectstack/plugin-hono-server@18.0.0-next.3 + - @objectstack/plugin-auth@18.0.0-next.3 + - @objectstack/core@18.0.0-next.3 + - @objectstack/account@18.0.0-next.3 + - @objectstack/setup@18.0.0-next.3 + - @objectstack/driver-memory@18.0.0-next.3 + - @objectstack/service-i18n@18.0.0-next.3 + - @objectstack/service-realtime@18.0.0-next.3 + - @objectstack/types@18.0.0-next.3 + ## 18.0.0-next.2 ### Minor Changes diff --git a/packages/plugins/plugin-dev/package.json b/packages/plugins/plugin-dev/package.json index baec5225471..de2b92e1dfb 100644 --- a/packages/plugins/plugin-dev/package.json +++ b/packages/plugins/plugin-dev/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/plugin-dev", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "license": "Apache-2.0", "description": "Development Assembly Plugin for ObjectStack — wires the real platform stack for zero-config local development", "main": "dist/index.js", diff --git a/packages/plugins/plugin-email/CHANGELOG.md b/packages/plugins/plugin-email/CHANGELOG.md index e1b54b2582b..1c81a6f978b 100644 --- a/packages/plugins/plugin-email/CHANGELOG.md +++ b/packages/plugins/plugin-email/CHANGELOG.md @@ -1,5 +1,63 @@ # @objectstack/plugin-email +## 18.0.0-next.3 + +### Patch Changes + +- 33228c5: Clause-②: no + + fix(plugin-email): a template send issued before `EmailServicePlugin`'s own `kernel:ready` renders, persists its `sys_email` row and delivers, instead of throwing `TEMPLATE_NOT_FOUND: no templateLoader configured on EmailService` + + `EmailServicePlugin` now wires `sys_email` persistence and the registry template loader in `init`, before it registers the `email` service. Before this, both were wired in the plugin's own `kernel:ready` hook, and a plugin that starts earlier runs its `kernel:ready` hooks first. The dev admin seed (`objectstack dev --seed-admin`, the default) signs its account up from one of those hooks, so on a boot with email verification required the seed's verification mail failed. Each such boot logged `ERROR [Better Auth]: Failed to run background task: Error: TEMPLATE_NOT_FOUND …` and left no `sys_email` row. That send now goes through the transport and default sender the boot was configured with. The `mail` settings namespace is still applied at `kernel:ready`. Sends made after boot behave as before. +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [30ce56e] +- Updated dependencies [2e0e451] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [77efc66] +- Updated dependencies [6422da5] +- Updated dependencies [8f4260f] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [4ffdebe] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [e246f06] +- Updated dependencies [a65a59b] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/platform-objects@18.0.0-next.3 + - @objectstack/core@18.0.0-next.3 + - @objectstack/formula@18.0.0-next.3 + ## 18.0.0-next.2 ### Minor Changes diff --git a/packages/plugins/plugin-email/package.json b/packages/plugins/plugin-email/package.json index 021c0d3c967..1e14da93585 100644 --- a/packages/plugins/plugin-email/package.json +++ b/packages/plugins/plugin-email/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/plugin-email", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "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 46d314104bf..7e06b21d28d 100644 --- a/packages/plugins/plugin-hono-server/CHANGELOG.md +++ b/packages/plugins/plugin-hono-server/CHANGELOG.md @@ -1,5 +1,91 @@ # @objectstack/plugin-hono-server +## 18.0.0-next.3 + +### Minor Changes + +- 75ced06: fix(plugin-hono-server)!: a request body the adapter cannot parse is refused `400 INVALID_REQUEST` when a route handler reads it, never handed to the handler as `{}` + + Clause-②: no (narrowing) + + + + **BREAKING** (an accept-set narrowing), shipped as `minor` under the launch-window convention for breaking changes. + + The adapter used to build a route's `req.body` as `{}` and overwrite it only when a parse succeeded, so a body it could not parse reached the handler as an empty object. Measured on a fresh `objectstack dev` showcase boot: `POST /api/v1/data/showcase_account/query` with its closing brace missing, with `{ not json`, or with a valid body sent as `text/plain` answered `200` with all 14 rows where the filter matched 2; a truncated `PATCH` answered `200`, changed no field and moved `updated_at`; and a truncated create answered `400 VALIDATION_FAILED` "Account Name is required", for a body that carried a name. + + What changes for an integrator. Each refusal is `400` with `{ "success": false, "error": { "code": "INVALID_REQUEST", "message": "…" } }`, raised when the handler reads `req.body`, so the handler acts on nothing past that read and nothing is written. The message names the failure and never echoes the body. + + - A body sent as `application/json` (matched case-insensitively) that is not blank and does not parse: FROM the handler reading `{}`, TO the refusal "The request body could not be parsed as JSON (it was sent as application/json). It was refused rather than read as an empty body: send a well-formed JSON document, or no body at all." + - A body in a media type the adapter does not parse (`text/plain`, `application/octet-stream`, any other type, or no `Content-Type` at all) sent to a route that reads `req.body`: FROM `{}`, TO the refusal "The request body was sent as text/plain, which this server does not read into a request body. It was refused rather than read as an empty body: send it as application/json." + - A `multipart/form-data` or `application/x-www-form-urlencoded` body that does not parse: FROM `{}`, TO the refusal naming form data. + + **Remedy, in one line.** Send a JSON body as well-formed JSON with `Content-Type: application/json`, or send no body. + + **Unchanged.** + + - An empty or whitespace-only JSON body is still `{}`, which the REST data doors read as an empty query; a request with no body is still `{}`, whatever it declares. + - Well-formed JSON and well-formed forms reach the handler parsed, as before. + - A route that reads `req.rawBody()` and never `req.body` receives any media type byte for byte, as before: the storage raw upload stores a JSON-with-comments file sent as `application/json` (the type its presign tells the client to send for a `.json` file) and a text file sent as `text/plain`. + - A route, or the unmatched-request fallback, that never reads `req.body` answers as before. + - A body is judged present by its framing headers (`Content-Length` other than `0`, or `Transfer-Encoding`), as RFC 9112 defines it; nothing is read to find out. + +### Patch Changes + +- 7428da0: **Shipped in 18.0.0-next.2**, whose CHANGELOG missed this entry (#23818); listed here one prerelease late. + + refactor(plugin-hono-server,runtime): the Hono doors and the HTTP dispatcher read the duplicate-record wire code off the shared error classification instead of spelling it themselves (#23714) + + **No behaviour change.** A repeated unique value still answers `409 UNIQUE_VIOLATION` at both Hono doors (a `HonoHttpServer` route or raw mount, and `createHonoApp`'s catch arms through `declaredEnvelopeForThrow`) and at the dispatcher's doors (`HttpDispatcher`'s thrown-error envelope), with the same `message`, the same `details` (`developerMessage`, `object`, and `field` when the driver named the column) and the same envelope keys. The answers were compared byte for byte at those doors before and after. A client or an app author has nothing to change. + + **What changed.** Both translations used to spell the wire code as a string literal. They now read it off the classification the REST server answers with (`structuredCodeAnswer` in `@objectstack/types`), as `@objectstack/mcp` already does. So the code has one source, and neither package stamps a code that the error-code ledger does not list under it. The engine's in-process code is unchanged: `DuplicateRecordError.code` is still `DUPLICATE_RECORD`. + + Clause-②: no +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [6422da5] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [4ffdebe] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [e246f06] +- Updated dependencies [a65a59b] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/core@18.0.0-next.3 + - @objectstack/observability@18.0.0-next.3 + - @objectstack/types@18.0.0-next.3 + ## 18.0.0-next.2 ### Minor Changes diff --git a/packages/plugins/plugin-hono-server/package.json b/packages/plugins/plugin-hono-server/package.json index ea0f1907947..56b2dd51096 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": "18.0.0-next.2", + "version": "18.0.0-next.3", "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 7d9c77035d6..e7c0d09ee71 100644 --- a/packages/plugins/plugin-pinyin-search/CHANGELOG.md +++ b/packages/plugins/plugin-pinyin-search/CHANGELOG.md @@ -1,5 +1,21 @@ # @objectstack/plugin-pinyin-search +## 18.0.0-next.3 + +### Patch Changes + +- Updated dependencies [a69a40a] +- Updated dependencies [abb6df0] +- Updated dependencies [a33f3d5] +- Updated dependencies [d050a8f] +- Updated dependencies [b25d98a] +- Updated dependencies [4ffdebe] +- Updated dependencies [66d2757] +- Updated dependencies [e246f06] + - @objectstack/objectql@18.0.0-next.3 + - @objectstack/core@18.0.0-next.3 + - @objectstack/types@18.0.0-next.3 + ## 18.0.0-next.2 ### Patch Changes diff --git a/packages/plugins/plugin-pinyin-search/package.json b/packages/plugins/plugin-pinyin-search/package.json index af8ad1c78ad..085c0794f9b 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": "18.0.0-next.2", + "version": "18.0.0-next.3", "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-security/CHANGELOG.md b/packages/plugins/plugin-security/CHANGELOG.md index 0e848860620..11335153fc5 100644 --- a/packages/plugins/plugin-security/CHANGELOG.md +++ b/packages/plugins/plugin-security/CHANGELOG.md @@ -1,5 +1,113 @@ # @objectstack/plugin-security +## 18.0.0-next.3 + +### Minor Changes + +- b9dbc3d: feat(plugin-security): under a walled posture, the deployment-level acceptance of package defaults can be host configuration (`acceptedDefaultPermissionSets`, ADR-0131 D5) (#23385) + + Clause-②: yes (widening) + + **What was wrong.** On a walled (`group` / `isolated`) deployment with several organizations and none of slug `default`, the audience-binding suggestion record has no organization to own it. Every boot logged `[security] audience-binding suggestion sync failed (non-fatal)`, and the accept door answered `409 SUGGESTION_STATE` with the remedy "create the Default Organization (slug 'default')". That is a reserved organization for deployment-level rows, the shape ADR-0131 §1.5 rejects. + + **What is new.** `SecurityPluginOptions.acceptedDefaultPermissionSets?: readonly string[]` is read only under a walled posture. When it is declared, possibly as `[]`: + + - the `everyone` anchor derives from the baseline plus the declared sets. Each name passes the same checks an accepted suggestion does: the security catalog serves it under some package, it declares `isDefault: true`, and it carries no bit the `everyone` anchor forbids (ADR-0090 D7). A name that fails a check grants nothing and is reported, at `error` for a forbidden bit; + - the `sys_audience_binding_suggestion` record is neither read nor written, so the boot no longer warns. Existing rows are counted once and reported, not deleted; + - the record's list, accept and dismiss doors answer `409 SUGGESTION_STATE` naming the option. + + ```ts + new SecurityPlugin({ acceptedDefaultPermissionSets: [] }); // nothing beyond the baseline + new SecurityPlugin({ acceptedDefaultPermissionSets: ['crm_readonly'] }); // every member also holds crm_readonly + ``` + + A walled deployment that declares nothing keeps today's behaviour. When it has no organization to own the record, the refusal now names `acceptedDefaultPermissionSets` instead of "create the Default Organization". + + **Refused loudly.** A value that is not a list of non-empty names is refused at construction. A declaration under the `single` posture is refused at `start()`: there the record owned by the Default Organization is the acceptance. The `single` posture is otherwise unchanged, including its refusal text. + + **Exported for callers of the module's functions.** `readAcceptedEveryoneSets`, `syncAudienceBindingSuggestions` and `reconcileAudienceBindingSuggestions` take an optional trailing `DeploymentAcceptance` (`{ posture, acceptedDefaultPermissionSets }`), and `SuggestionDeps` gains `acceptedDefaultPermissionSets`. Omitted, they read the record as before. `WithheldAcceptance` gains `source: 'record' | 'host-configuration'`. + +### Patch Changes + +- 099cc9d: Clause-②: no + + The approval, security, automation and sharing objects describe their records in plain admin + sentences. Approval Delegations now reads "While someone is away, approvals assigned to them + personally go to their delegate.", Record Shares "Records shared beyond their owner, one row per + grant.", User Positions "Which positions each user holds, one row per assignment.", Audience + Binding Suggestions "A package's suggestion to give one of its permission sets to everyone who + signs in, and the decision taken on it." and Automation Runs "Paused automation runs waiting to + resume, and the recent history of finished runs." Field help on Approval Requests, Approval + Delegations, User Positions, User Permission Sets and Audience Binding Suggestions, the + descriptions of the `organization_admin_no_bypass`, `mcp_agent_data_read` and + `mcp_agent_data_write` permission sets, the flow-dispatch ledger's description and the + reserved-position refusal no longer carry design-record numbers or runtime vocabulary; those + references stay in the code comments. The zh-CN, ja-JP and es-ES translations follow. Wording + only: no field, permission or behaviour changes. +- 5fb8d15: Clause-②: no + + The Everyone and Guest built-in positions describe themselves in plain admin sentences. Setup's + Positions page now reads "Built-in audience anchor: every authenticated member holds this position + implicitly. Permission sets bound to it are the default grants for the tenant. High-privilege sets + cannot be bound here." for Everyone, and "Built-in audience anchor: unauthenticated principals hold + this position implicitly and exclusively. Bindings face the strictest checks — named objects only, + read-mostly, never a wildcard." for Guest; the design-record references stay in the code comments. + An upgraded deployment shows the same text: the page lists the declared positions, and a stored + position row an earlier release wrote is neither read nor rewritten. Wording only: no permission or + behaviour change. +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [30ce56e] +- Updated dependencies [2e0e451] +- Updated dependencies [df2c551] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [77efc66] +- Updated dependencies [6422da5] +- Updated dependencies [8f4260f] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [4ffdebe] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [e246f06] +- Updated dependencies [a65a59b] +- Updated dependencies [077b75f] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/metadata-core@18.0.0-next.3 + - @objectstack/platform-objects@18.0.0-next.3 + - @objectstack/core@18.0.0-next.3 + - @objectstack/formula@18.0.0-next.3 + - @objectstack/types@18.0.0-next.3 + ## 18.0.0-next.2 ### Minor Changes diff --git a/packages/plugins/plugin-security/package.json b/packages/plugins/plugin-security/package.json index 640e60427a9..a73cb353523 100644 --- a/packages/plugins/plugin-security/package.json +++ b/packages/plugins/plugin-security/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/plugin-security", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "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 e2ee4da1fc2..0d0d80361ba 100644 --- a/packages/plugins/plugin-sharing/CHANGELOG.md +++ b/packages/plugins/plugin-sharing/CHANGELOG.md @@ -1,5 +1,96 @@ # @objectstack/plugin-sharing +## 18.0.0-next.3 + +### Patch Changes + +- 099cc9d: Clause-②: no + + The approval, security, automation and sharing objects describe their records in plain admin + sentences. Approval Delegations now reads "While someone is away, approvals assigned to them + personally go to their delegate.", Record Shares "Records shared beyond their owner, one row per + grant.", User Positions "Which positions each user holds, one row per assignment.", Audience + Binding Suggestions "A package's suggestion to give one of its permission sets to everyone who + signs in, and the decision taken on it." and Automation Runs "Paused automation runs waiting to + resume, and the recent history of finished runs." Field help on Approval Requests, Approval + Delegations, User Positions, User Permission Sets and Audience Binding Suggestions, the + descriptions of the `organization_admin_no_bypass`, `mcp_agent_data_read` and + `mcp_agent_data_write` permission sets, the flow-dispatch ledger's description and the + reserved-position refusal no longer carry design-record numbers or runtime vocabulary; those + references stay in the code comments. The zh-CN, ja-JP and es-ES translations follow. Wording + only: no field, permission or behaviour changes. +- 6dede7a: fix(runtime, plugin-sharing): on the standalone server, an anonymous caller of any `/api/v1/notifications` verb or any `/api/v1/share-links` path off the two token faces is answered `401 UNAUTHENTICATED` in the dispatcher's standard envelope + + Clause-②: no + + The v18 text for these two doors reads "`/notifications`: `401 UNAUTHENTICATED` on every face" and "`/share-links`: `401 UNAUTHENTICATED` on the create, list and revoke faces and on every path the domain does not serve", in "the dispatcher's standard envelope … with the shared message every other dispatcher domain answers with". A host that reaches the dispatcher through `@objectstack/hono`'s catch-all already answered that way. The standalone server (the one `os dev` and `os serve` run) did not: + + - `/api/v1/notifications`, `/api/v1/notifications/read` and `/api/v1/notifications/read/all` were each mounted for the one verb they serve, so any other verb never reached the `notifications` domain and the HTTP adapter answered `405`, naming the allowed methods, to an anonymous caller as well. + - `/api/v1/share-links` is served there by `@objectstack/plugin-sharing`'s routes, not by the dispatcher. They registered five routes and nothing else, so another verb or path got the adapter's `405` or `404`, and the create, list and revoke faces answered `401` with a sentence of their own ("Sign in to list share links") and no `error.httpStatus`. + + What the standalone server answers now: + + - **Anonymous caller.** `401 UNAUTHENTICATED`, `{ success: false, error: { code: 'UNAUTHENTICATED', message: 'Authentication is required to access this endpoint.', httpStatus: 401 } }`, on every verb of the three notifications paths, and on every verb and path under `/api/v1/share-links` except `GET /share-links/:token/resolve` and `GET /share-links/:token/messages`, which the capability token still admits. A body the server cannot parse does not outrank this answer anywhere under `/api/v1/share-links`, nor on the notifications verbs no face serves. + - **Signed-in caller of a verb or path that is not served.** Still refused. On the notifications paths the domain answers it: `404 ROUTE_NOT_FOUND` from the dispatcher (or the `501` capability answer on a deployment with no inbox), where the adapter used to answer `405`. Under `/api/v1/share-links` the share-link routes answer it themselves: `404 ENDPOINT_NOT_FOUND`, naming the request, where the adapter used to answer `405` or `404`. No `Allow` header is sent on either. + + Every request that was served is served as before. A notifications sub-path no face names, such as `/api/v1/notifications/`, is still not mounted on the standalone server and is answered `404`. +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [abb6df0] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [30ce56e] +- Updated dependencies [2e0e451] +- Updated dependencies [df2c551] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [77efc66] +- Updated dependencies [6422da5] +- Updated dependencies [8f4260f] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [a33f3d5] +- Updated dependencies [d050a8f] +- Updated dependencies [b25d98a] +- Updated dependencies [4ffdebe] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [e246f06] +- Updated dependencies [a65a59b] +- Updated dependencies [077b75f] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/metadata-core@18.0.0-next.3 + - @objectstack/objectql@18.0.0-next.3 + - @objectstack/platform-objects@18.0.0-next.3 + - @objectstack/core@18.0.0-next.3 + - @objectstack/formula@18.0.0-next.3 + - @objectstack/types@18.0.0-next.3 + ## 18.0.0-next.2 ### Minor Changes diff --git a/packages/plugins/plugin-sharing/package.json b/packages/plugins/plugin-sharing/package.json index f49cd8f7ad0..02e01253f97 100644 --- a/packages/plugins/plugin-sharing/package.json +++ b/packages/plugins/plugin-sharing/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/plugin-sharing", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "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 27399df5263..1fd98b873b5 100644 --- a/packages/plugins/plugin-webhooks/CHANGELOG.md +++ b/packages/plugins/plugin-webhooks/CHANGELOG.md @@ -1,5 +1,59 @@ # @objectstack/plugin-webhooks +## 18.0.0-next.3 + +### Patch Changes + +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [30ce56e] +- Updated dependencies [2e0e451] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [77efc66] +- Updated dependencies [6422da5] +- Updated dependencies [8f4260f] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [4ffdebe] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [e246f06] +- Updated dependencies [a65a59b] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/platform-objects@18.0.0-next.3 + - @objectstack/service-messaging@18.0.0-next.3 + - @objectstack/core@18.0.0-next.3 + - @objectstack/types@18.0.0-next.3 + ## 18.0.0-next.2 ### Patch Changes diff --git a/packages/plugins/plugin-webhooks/package.json b/packages/plugins/plugin-webhooks/package.json index 500cd8f01a8..879d0500c7a 100644 --- a/packages/plugins/plugin-webhooks/package.json +++ b/packages/plugins/plugin-webhooks/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/plugin-webhooks", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "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 8b66fb53e33..3d021a485b6 100644 --- a/packages/qa/dogfood/CHANGELOG.md +++ b/packages/qa/dogfood/CHANGELOG.md @@ -1,5 +1,118 @@ # @objectstack/dogfood +## 0.0.48-next.3 + +### Patch Changes + +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [b9dbc3d] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [9fcea20] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [7428da0] +- Updated dependencies [b55be42] +- Updated dependencies [abb6df0] +- Updated dependencies [5b6c257] +- Updated dependencies [0b1179f] +- Updated dependencies [8776119] +- Updated dependencies [74b4362] +- Updated dependencies [f7a5ebe] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9d4fb3c] +- Updated dependencies [c5cf546] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [34b740f] +- Updated dependencies [30ce56e] +- Updated dependencies [2e0e451] +- Updated dependencies [94114c1] +- Updated dependencies [df2c551] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [0cfd0c4] +- Updated dependencies [ae4dfaf] +- Updated dependencies [099cc9d] +- Updated dependencies [77efc66] +- Updated dependencies [6422da5] +- Updated dependencies [8f4260f] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [67e8792] +- Updated dependencies [2e15620] +- Updated dependencies [0cb45e9] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [b74434c] +- Updated dependencies [bbdfc05] +- Updated dependencies [605aa01] +- Updated dependencies [a33f3d5] +- Updated dependencies [d050a8f] +- Updated dependencies [9e797a8] +- Updated dependencies [b25d98a] +- Updated dependencies [5fb8d15] +- Updated dependencies [33228c5] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [44fe210] +- Updated dependencies [e246f06] +- Updated dependencies [a65a59b] +- Updated dependencies [b75129f] +- Updated dependencies [6dede7a] +- Updated dependencies [077b75f] +- Updated dependencies [62f959d] +- Updated dependencies [ab3ba2c] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [544b8d7] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] +- Updated dependencies [cdf3777] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/service-storage@18.0.0-next.3 + - @objectstack/plugin-security@18.0.0-next.3 + - @objectstack/runtime@18.0.0-next.3 + - @objectstack/metadata-core@18.0.0-next.3 + - @objectstack/metadata@18.0.0-next.3 + - @objectstack/objectql@18.0.0-next.3 + - @objectstack/plugin-audit@18.0.0-next.3 + - @objectstack/platform-objects@18.0.0-next.3 + - @objectstack/plugin-approvals@18.0.0-next.3 + - @objectstack/plugin-auth@18.0.0-next.3 + - @objectstack/service-messaging@18.0.0-next.3 + - @objectstack/plugin-sharing@18.0.0-next.3 + - @objectstack/plugin-email@18.0.0-next.3 + - @objectstack/example-showcase@0.3.22-next.3 + - @objectstack/mcp@18.0.0-next.3 + - @objectstack/example-crm@4.0.100-next.3 + - @objectstack/example-multi-package@0.0.7-next.3 + - @objectstack/connector-mcp@18.0.0-next.3 + - @objectstack/connector-openapi@18.0.0-next.3 + - @objectstack/connector-rest@18.0.0-next.3 + - @objectstack/formula@18.0.0-next.3 + - @objectstack/plugin-webhooks@18.0.0-next.3 + - @objectstack/service-analytics@18.0.0-next.3 + - @objectstack/service-datasource@18.0.0-next.3 + - @objectstack/trigger-record-change@18.0.0-next.3 + - @objectstack/trigger-schedule@18.0.0-next.3 + - @objectstack/types@18.0.0-next.3 + - @objectstack/verify@18.0.0-next.3 + - @objectstack/plugin-pinyin-search@18.0.0-next.3 + ## 0.0.48-next.2 ### Patch Changes diff --git a/packages/qa/dogfood/package.json b/packages/qa/dogfood/package.json index 3750eeee2ae..cf0d6c920f0 100644 --- a/packages/qa/dogfood/package.json +++ b/packages/qa/dogfood/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/dogfood", - "version": "0.0.48-next.2", + "version": "0.0.48-next.3", "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 d035869c9af..95179673d63 100644 --- a/packages/qa/downstream-contract/CHANGELOG.md +++ b/packages/qa/downstream-contract/CHANGELOG.md @@ -1,5 +1,49 @@ # @objectstack/downstream-contract +## 0.0.46-next.3 + +### Patch Changes + +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [6422da5] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [a65a59b] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + ## 0.0.46-next.2 ### Patch Changes diff --git a/packages/qa/downstream-contract/package.json b/packages/qa/downstream-contract/package.json index 51001ce5293..61a5f0e0953 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.46-next.2", + "version": "0.0.46-next.3", "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 93b032dbe0f..0a6524fd803 100644 --- a/packages/qa/http-conformance/CHANGELOG.md +++ b/packages/qa/http-conformance/CHANGELOG.md @@ -1,5 +1,13 @@ # @objectstack/http-conformance +## 0.1.8-next.3 + +### Patch Changes + +- Updated dependencies [4ffdebe] +- Updated dependencies [e246f06] + - @objectstack/core@18.0.0-next.3 + ## 0.1.8-next.2 ### Patch Changes diff --git a/packages/qa/http-conformance/package.json b/packages/qa/http-conformance/package.json index c54c85939bd..a4b8d788cca 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.1.8-next.2", + "version": "0.1.8-next.3", "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 6ab6c3babbc..152c73489f6 100644 --- a/packages/rest/CHANGELOG.md +++ b/packages/rest/CHANGELOG.md @@ -1,5 +1,116 @@ # @objectstack/rest +## 18.0.0-next.3 + +### Minor Changes + +- aefab7c: fix(rest)!: the `/meta` item and list reads serve a view stored with no `label` without the `label` the translator invents from its name + + Clause-②: no (narrowing) + + + + **BREAKING** (a `/meta` read stops serving a value nobody authored), shipped as `minor` under the launch-window convention for breaking changes. + + A view's `label` is optional in the spec, with no default. When the translation catalog carried no string for a view, the read-side translator answered the view's own name, so a view stored without a `label` was served one: `GET /api/v1/meta/view/NAME` and `GET /api/v1/meta/view` returned `label: "OBJECT.KEY"`, on the standalone server and on the runtime dispatcher alike. The console drew that machine name as the view tab. The view-container read already dropped this value; the item read and the list read now drop it too. + + What a client reads now: + + - A view stored with no `label` is served with no `label` key, in every locale, with or without a translation catalog. + - A view with an authored `label` is served that label, unchanged, including a label that happens to equal the view's name. + - A catalog translation (`objects.OBJECT._views.KEY.label`) is still served, for a view with a `label` and for one without. + - A read that resolves no locale is served the stored document as before, and the layered read (`?layers=true`) never carried the invented value. + + Who reads differently: a client that displays a served view's `label` now finds the key absent for a view stored without one, where it used to find the qualified name; it shows its own wording for such a view (the object's plural label, for example). The console's view tab falls back to the view's name on its own, so it keeps showing a machine name for such a view until its own fallback changes. + +### Patch Changes + +- 1eac7ad: fix(rest): `GET /forms/:slug` serves a public form's section headings in the visitor's language, beside the field labels it already translated + + Clause-②: no + + An anonymous visitor of a published form cannot load the translation bundle in the browser, so the server is the only place its section titles can be translated. The route translated `objectSchema` (the field labels) under `Accept-Language` and returned each form section as authored, so a zh-CN visitor read English section titles over Chinese field labels. It now localizes each section's `label` and `description` through the same `objects.OBJECT._sections.NAME` keys the signed-in console reads, with the request's locale, the deployment's fallback chain and default locale, and the packaged view as the override base. + + With `Accept-Language: zh-CN` over a form whose section `about` is labelled `Tell us about yourself`, and a zh-CN bundle carrying `objects.crm_lead._sections.about.label: 介绍一下你自己`: + + - FROM: `form.sections[0].label` is `Tell us about yourself`, and `objectSchema.fields.first_name.label` is `名`. + - TO: `form.sections[0].label` is `介绍一下你自己`; the field label is unchanged (`名`). A section `description` follows the same key's `description`. + + What does not change: a section whose key the bundle lacks keeps its authored text; a section with no `name` is served as authored; a section that authors no heading is not given one; a heading edited on top of the packaged view keeps the edit. The response shape (`slug`, `object`, `label`, `form`, `objectSchema`), its top-level `label`, `Vary: Accept-Language`, the declared-field narrowing, the lookup / master-detail / user strip and `POST /forms/:slug/submit` are unchanged. +- 5c2ac83: fix(rest): a client refusal its producer declared is no longer logged as `[REST] Unhandled error` with a stack trace + + Clause-②: no + + - **What the server log stops printing.** A refused request whose thrown error declares both a 4xx `status` (or `statusCode`) and a registered ADR-0112 `code`, and whose answer carries that same status and code, now prints nothing on the `[REST] Unhandled error` channel every REST route catch shares. Before, each one printed an error-level `[REST] Unhandled error:` line plus the error's full stack, once per request. Two measured cases: a record write whose file violates a field's declared `accept` / `maxSize` (`400 ERR_FILE_CONSTRAINT` on `POST /api/v1/data/:object` and `PATCH /api/v1/data/:object/:id`), and a metadata save that fails spec validation (`422 INVALID_METADATA` on `PUT /api/v1/meta/:type/:name`). Other registered codes thrown with a declared 4xx, such as `FILE_FIELD_BULK_WRITE_REFUSED`, go quiet too, with no per-code list to extend. + - **What still logs, unchanged:** a handler bug or any other undeclared fault (answered `500`); a 4xx that carries no `code` (for example a sandboxed hook's business-rule `400`); a 4xx whose `code` is not registered (it reaches the wire as `declaredCode`); a registered `code` on a status the producer did not declare; a declared `5xx` without `refusal: true`; and a sandboxed body that crashed, whatever it declared. The anonymous public-form submit route keeps its own `[REST] Public form submit error` line as before. + - **Unchanged:** every response's status, `code` and body. Only the server log line moves. +- c1c0a90: fix(rest): the API-exposure check on every `/data` request reads only the object it checks, not the whole object list + + Clause-②: no + + - **Faster `/data` requests.** Before each `/data` read or write, the REST layer checks the object's `enable.apiEnabled` and `enable.apiMethods`. To read that one object's flags, the check used to load every object's metadata and run a full schema diagnostic on each one. It now reads only the object named in the URL. Measured on the showcase app on a shared machine: 33 `GET /api/v1/data/showcase_account` calls took 647 ms instead of 2421 ms (-73 %), and the metadata read behind the check fell from 56-61 ms to about 4.5 ms per request. + - **Same answers.** Code-registered objects, objects stored in the environment and objects that another package extends get the same `404 OBJECT_API_DISABLED` and `405 OBJECT_API_METHOD_NOT_ALLOWED` as before. An unknown object is still answered by the data path's `404 OBJECT_NOT_FOUND`. When the metadata store cannot be read, the check still lets the request through and logs its `api-exposure gate` warning. Nothing is cached, so a metadata change applies from the next request. + - **One refusal changes, for an object whose package is disabled.** A denied operation on such an object now gets the exposure answer instead of the disabled-package one: + - an operation outside `apiMethods`: FROM `404 OBJECT_PACKAGE_DISABLED` TO `405 OBJECT_API_METHOD_NOT_ALLOWED`; + - any operation on an object with `apiEnabled: false`: FROM `404 OBJECT_PACKAGE_DISABLED` TO `404 OBJECT_API_DISABLED`. + + An allowed operation still answers `404 OBJECT_PACKAGE_DISABLED`. No accepted request is now refused, and no refused request is now accepted. +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [30ce56e] +- Updated dependencies [2e0e451] +- Updated dependencies [df2c551] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [77efc66] +- Updated dependencies [6422da5] +- Updated dependencies [8f4260f] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [4ffdebe] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [c7168ac] +- Updated dependencies [e246f06] +- Updated dependencies [a65a59b] +- Updated dependencies [077b75f] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/metadata-core@18.0.0-next.3 + - @objectstack/metadata@18.0.0-next.3 + - @objectstack/platform-objects@18.0.0-next.3 + - @objectstack/core@18.0.0-next.3 + - @objectstack/service-package@18.0.0-next.3 + - @objectstack/observability@18.0.0-next.3 + - @objectstack/types@18.0.0-next.3 + ## 18.0.0-next.2 ### Minor Changes diff --git a/packages/rest/package.json b/packages/rest/package.json index 18b1c921fa8..7b01cfd0b8c 100644 --- a/packages/rest/package.json +++ b/packages/rest/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/rest", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "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 6addbd9e53d..3ccd98b1978 100644 --- a/packages/runtime/CHANGELOG.md +++ b/packages/runtime/CHANGELOG.md @@ -1,5 +1,365 @@ # @objectstack/runtime +## 18.0.0-next.3 + +### Minor Changes + +- 34b740f: fix(runtime,cli): an upper-case `FILE:` database URL opens the file its lower-case spelling opens, `file://NAME` opens `NAME` on both hosts, and the URL-to-filename rule is exported as `sqliteFilenameFromUrl` + + Clause-②: yes (widening) + + - **`FILE:` is stripped case-insensitively.** The standalone host classified `FILE:./app.db` as SQLite case-insensitively but stripped the prefix case-sensitively, so the prefix stayed in the file name: `OS_DATABASE_URL=FILE:./app.db` created a directory literally named `FILE:.` and put the database inside it, and `File:///tmp/app.db` was refused as an unsupported `File://` scheme. Both now open the file the lower-case spelling opens (`./app.db`, `/tmp/app.db`), as the `sqlite-wasm` path already did. Lower-case `file:` URLs and bare paths are unchanged. + - **New export: `sqliteFilenameFromUrl(url, kind)`** from `@objectstack/runtime`, `kind` being `'sqlite'` or `'sqlite-wasm'`. It is the one rule that turns a SQLite database URL (after `normalizeDatabaseUrl`) into a file name: `file:` / `file://` stripped case-insensitively, plus `wasm-sqlite:` for the WASM kind. For `'sqlite'` it throws a plain `Error` on a URL that is not a file path, naming the URL's scheme and never the URL. Nothing is removed. + - **`os serve`'s legacy storage path (`OS_MODE=off`, `bootMode: 'off'`, or a host config) turns a SQLite URL into a file by that same rule**, so both hosts open one file per URL. It used to strip only a lower-case `file:`: `OS_DATABASE_URL=file://NAME` opened `//NAME` at the filesystem root, where the standalone host opens `./NAME`, and `FILE:./app.db` made a `FILE:.` directory there too. `file:///abs/app.db` opens `/abs/app.db`; `sqlite:` / `sqlite://` URLs are unchanged. + - **What that rule refuses, the legacy path now refuses too, and fatally**, with the standalone host's own sentence (it names the URL's scheme, never the URL): an explicit `OS_DATABASE_DRIVER=sqlite` over a server URL such as `postgres://user:password@host/db`, which used to create directories named after the URL, credentials included, and a `file:` URL that names no file, which used to declare a database with an empty file name. Fix: point `OS_DATABASE_URL` at a file (`file:./app.db`), or select the driver the URL is for. The undocumented `sql://` prefix that path also stripped under an explicit `sqlite` selection is refused the same way; write `file:` instead. A blank `OS_DATABASE_URL` under an explicit `sqlite` selection is read as absent (`:memory:`), as an absent one always was. +- ae4dfaf: feat(driver-turso,cli): the v18 organization-ownership ceremony runs on a Turso remote database + + Clause-②: yes (widening) + + `os migrate organization-ownership`, the plan and `--apply --backup-taken`, now completes against a Turso database reached over the remote transport (`libsql://…`), the transport hosted environments use. Before, the plan stopped at its boot with `Deferred schema DDL is not supported by the Turso REMOTE transport`, and the apply's column drops had no remote path either. + + - **`@objectstack/driver-turso`**, remote face: + - `detectManagedDrift()` answers instead of refusing with `NOT_IMPLEMENTED` / 501. The verdict is the shared differ's, the one the local face runs; the physical schema it diffs is read from the remote catalog (`pragma_table_info`, `sqlite_master`, `pragma_index_list` / `pragma_index_info`, and the ADR-0120 D4 duplicate probe). The declarations it diffs against are recorded by every remote schema door and by the DDL-free `registerObjectMetadata`, so a boot that skipped schema sync is covered too. + - `applyMigrationEntries()` applies `drop_index`, `create_index`, `recreate_index`, `replace_unique_index` and `drop_column` instead of refusing. Each entry runs as one libSQL `write` batch, so it lands whole or not at all: a `drop_column` drops the column's indexes with it in the same transaction, and a re-created index that fails to build leaves the old one in place. An entry is reported `applied` only once a read-back shows its target state. Every other column entry (nullability, width, a dropped DEFAULT) needs the SQLite table rebuild, which this transport does not run, so it is reported `skipped` and logged, as the in-place dialects report an op they cannot perform. + - The remote face declares `driftReportCaveat`: a sentence saying its drift report is for reading only and naming the reason. The OS_ARTIFACT_URL boot gate reads it (below). The local and embedded-replica faces declare `null`. + - The remote driver's `config` now carries its `url`, so the CLI labels a remote database by its URL (credential-free) instead of `better-sqlite3`. The ceremony writes that label into its plan and refuses an apply whose plan names another database; that refusal can now tell two remote databases apart. + - **`@objectstack/cli`**: the ceremony's plan and apply boots skip schema sync, instead of arming deferred DDL, when the primary datasource is a Turso remote one. No DDL is attempted, so the plan stays read-only, and every other datasource the boot connects is still deferred. The environment's own boot has already run the additive sync the ceremony reads. Every other `os migrate` command keeps refusing on that transport: their dry runs list held-back schema work, which a boot that holds nothing back cannot list. + - **`@objectstack/runtime`**: `createStandaloneStack` takes `skipSchemaSync`, passed to `ObjectQLPlugin` as the option `OS_SKIP_SCHEMA_SYNC=1` names. Unset, the plugin reads the variable as before. + + On a Turso remote primary, the `OS_ARTIFACT_URL` boot migration gate on `os serve` keeps its earlier answer: cannot judge, warn and boot. It applies no drift and refuses no boot. Only its warning's wording changes, from `Could not check the physical schema` to `Did not check the physical schema against the artifact: …`, followed by the driver's reason. The gate now decides this from a capability the driver declares (`driftReportCaveat`, a string on the remote face and `null` on the local and embedded-replica faces), not from a refusal it catches. Two follow-ups, filed separately and not closed by this change, have to land before that gate may act on a remote report: + - #23847: the remote transport's schema sync honours `storage.notNull` on create and add column, so the report stops naming a tightening its own sync produced; + - #23848: `os migrate apply` works on a Turso remote database, so the destructive drift the gate would refuse on has a remedy there. +- 62f959d: fix(runtime)!: `POST /api/v1/packages` with `overwrite` refuses a read-only package with `422 WRITABLE_PACKAGE_REQUIRED`, as disable, publish, revert and delete already do + + ADR-0070 D2 makes a package the deployment ships read-only: one booted by code, one installed from the marketplace, and one whose manifest `scope` is `system` or `cloud`. `isWritablePackage` is that rule, and the four lifecycle doors that change a package (`PATCH /packages/:id/disable`, `POST /packages/:id/publish`, `POST /packages/:id/revert`, `DELETE /packages/:id`) refuse such a package with `422 WRITABLE_PACKAGE_REQUIRED`. The install door's `overwrite` option only bypassed the `409 … already exists` answer and never asked the rule. So an overwrite over a read-only package answered `201` and replaced its registry row with an authored one, and with a `protocol` service composed it also wrote that package's `sys_packages` row, while the server went on treating the package as read-only. The install door now asks the same rule, for the manifest's id, before either install path writes anything. + + Clause-②: no (narrowing) + + + + **BREAKING** (a REST door's accept set narrows), shipped as `minor` under the launch-window convention for breaking changes. + + - **FROM:** `POST /api/v1/packages` with `overwrite: true` in the wrapped body, or `?overwrite=true`, and a manifest whose `id` names a read-only package, answered `201` and replaced that package's registry row. + - **TO:** the same request answers `422` with `error.code: 'WRITABLE_PACKAGE_REQUIRED'` and `error.details.packageId`, and writes nothing: no registry row, no `sys_packages` row. It is the refusal the four lifecycle doors give. + - **Unchanged:** an install of a new id, with or without `overwrite`, and an overwrite of a package an organization authored answer `201` as before. An install over an existing id without `overwrite` still answers `409`. + - **The fix for an integrator:** a package the deployment ships is managed by the deployment (its code release, or a new marketplace version), not over this API. Install the manifest under an id you own, or customise what the shipped package provides beside it as environment-level state (ADR-0131 D6). +- cdf3777: The v18 organization-ownership ceremony now completes on a long-lived 17.x database, and `os dev` runs it for you on a local development database (ADR-0131, amendment 2026-10-16). + + **`os dev` upgrades a v17-shape development database in place.** When the boot refuses a local SQLite development database that is still in the v17 organization-ownership shape, `os dev` first copies the database and its `telemetry` sibling (with `-wal` / `-shm`) into `/backups/organization-ownership-/`. It then runs `os migrate organization-ownership --dev` (the plan), applies that plan, and starts the server again. The plan, the apply report and the ceremony's log are kept beside the copy. If a step blocks, `os dev` stops and prints the blocker, where the copy is, and how to resume. `os serve` / `os start`, and any database that is not a local SQLite file, keep the manual ceremony and the boot refusal unchanged. + + **What the ceremony decides for you is printed as a warning before it writes anything.** The plan carries a `warnings` list. The plan prints it, `--apply` prints it again before its first write, and the apply report keeps it. + + **The `telemetry` sibling is covered.** A development boot (or an `OS_TELEMETRY_DB` deployment) keeps `sys_audit_log`, `sys_job_run`, `sys_notification` and the other lifecycle objects in a sibling database. The ceremony now plans, attributes and drops columns for each object in the database it lives in, reading anchors across databases. Before, the boot refusal read those tables in the sibling while the ceremony only read the primary, so an upgraded database could still never boot. A copy of such a table that an older boot left in the primary is listed under `staleCopies`, warned about, and left untouched; it no longer blocks the column drops. + + **The shipped permission sets an install seeded before 2026-08-15 are adopted.** Those installs store the eight shipped sets (`organization_admin`, `member_default`, …) as `managed_by: 'admin'` rows. The authored-conversion step used to refuse them with "rename the authored row". The ceremony now re-owns them as the platform seeds they are and deletes them as mirrors once every grant names its set, so the shipped definitions apply. The warning names each row; a Setup edit to one of them is not carried over, and the pre-upgrade backup keeps it. + + **`os migrate organization-ownership --dev`** composes the stack as `os serve --dev` does: the development auth secret's fallback (so the security and audit plugins), the development `.env*` files, and the `telemetry` sibling. A development boot's refusal now names the command with `--dev`, and every refusal now names an apply the ceremony accepts: `--out PLAN` first, then `--apply --plan-file PLAN --backup-taken`. A bare `--apply` is refused without a reviewed plan file. Before, running the ceremony from a shell without `NODE_ENV=development` composed neither plugin, and its apply blocked on the shipped permission sets and on `sys_audit_log`. + + `OrganizationOwnershipBootGuardPlugin` (`@objectstack/runtime`) takes `{ dev }`; `organizationOwnershipRefusalMessage` and `assertOrganizationOwnershipMigrated` take the same option. + +### Patch Changes + +- 9fcea20: **Shipped in 18.0.0-next.2**, whose CHANGELOG missed this entry (#23818); listed here one prerelease late. + + fix(runtime): `callData`'s exposure refusal for the object a request addresses carries the data door's code, `OBJECT_API_DISABLED` (404) or `OBJECT_API_METHOD_NOT_ALLOWED` (405), on the dispatcher's `/data` routes, the `/mcp` data tools and declared endpoints (#23528) + + Clause-②: no + + **What was wrong.** An object can declare `apiEnabled: false` or an `apiMethods` whitelist. The runtime dispatcher's exposure check for the object a request addresses (`refuseUnexposedDataAction`, which every `callData` asks first) refused with a status and a message but no `code`. So: + + - the dispatcher's `/data` routes derived a code from the status: `404 RESOURCE_NOT_FOUND` / `405 METHOD_NOT_ALLOWED`; + - the `/mcp` data tools rendered the message alone; + - a declared endpoint whose target is an object answered the derived code too. + + `RestServer` and the MCP stdio tools answer the same declaration `OBJECT_API_DISABLED` / `OBJECT_API_METHOD_NOT_ALLOWED`, and so did the dispatcher's own refusal of a nested-relation filter on an unexposed object. + + **What a caller reads now.** Every one of these doors answers the data door's code with the same status as before: + + | door | declaration | FROM | TO | + |:--|:--|:--|:--| + | dispatcher `/data` (and declared endpoints) | `apiEnabled: false` | `404`, `error.code` `RESOURCE_NOT_FOUND` | `404`, `error.code` `OBJECT_API_DISABLED` | + | dispatcher `/data` (and declared endpoints) | verb outside `apiMethods` | `405`, `error.code` `METHOD_NOT_ALLOWED` | `405`, `error.code` `OBJECT_API_METHOD_NOT_ALLOWED` | + | `/mcp` data tools | either | `isError: true`; the text is the message | `isError: true`; the text is `{ "error": { "code", "message", "status" } }` | + + The status and the message do not change. The thrown value is now an `Error` carrying `code`, `status`, the addressed object as `object`, and the effective operation set as `allowed` on a 405. A caller that branched on `RESOURCE_NOT_FOUND` / `METHOD_NOT_ALLOWED` for an exposure refusal on these doors should branch on the two `OBJECT_API_*` codes, as it already does for `RestServer`. +- ee0e115: docs: the default-record-page remedies name the other package's page, not the whole package + + Clause-②: no + + A second `isDefault: true` record page for one object is refused with `DEFAULT_RECORD_PAGE_CONFLICT`. The package registry's refusal already told the author to keep one default or "compose the deployment without the other package's page". Three sibling texts still told them to drop the whole package, though a deployment can keep a package and leave out the one page it ships: + + - `@objectstack/metadata-protocol`: the metadata save door's refusal now reads "a page a package ships stays the default unless the deployment is composed without that package's page." Only the remedy clause changed; `code`, `status`, `object` and `holders` are unchanged. + - `@objectstack/spec`: `PageSchema.isDefault`'s TSDoc and the error-code ledger's comment on `DEFAULT_RECORD_PAGE_CONFLICT` say the same. + - `@objectstack/runtime`: `AppPlugin`'s private `bundle` field now documents the three readers outside the class that read it structurally, so a rename is not made blind. + + No behaviour changes; nothing to migrate. +- 7428da0: **Shipped in 18.0.0-next.2**, whose CHANGELOG missed this entry (#23818); listed here one prerelease late. + + refactor(plugin-hono-server,runtime): the Hono doors and the HTTP dispatcher read the duplicate-record wire code off the shared error classification instead of spelling it themselves (#23714) + + **No behaviour change.** A repeated unique value still answers `409 UNIQUE_VIOLATION` at both Hono doors (a `HonoHttpServer` route or raw mount, and `createHonoApp`'s catch arms through `declaredEnvelopeForThrow`) and at the dispatcher's doors (`HttpDispatcher`'s thrown-error envelope), with the same `message`, the same `details` (`developerMessage`, `object`, and `field` when the driver named the column) and the same envelope keys. The answers were compared byte for byte at those doors before and after. A client or an app author has nothing to change. + + **What changed.** Both translations used to spell the wire code as a string literal. They now read it off the classification the REST server answers with (`structuredCodeAnswer` in `@objectstack/types`), as `@objectstack/mcp` already does. So the code has one source, and neither package stamps a code that the error-code ledger does not list under it. The engine's in-process code is unchanged: `DuplicateRecordError.code` is still `DUPLICATE_RECORD`. + + Clause-②: no +- b55be42: **Shipped in 18.0.0-next.2**, whose CHANGELOG missed this entry (#23818); listed here one prerelease late. + + fix(runtime): a sandboxed body that throws a value no read can flatten (`throw ''`, a null-prototype object, an object whose `message` getter throws) now ends its run at once with the sentence `the script threw a value that could not be read`, instead of waiting for the wall-clock ceiling (#23723) + + **Latency and answer change, for these values only.** The QuickJS runner's reject handler records a body's rejection as a flattened string, and the run ends only once that string is non-empty. For these values the read threw or gave `''`, so nothing was recorded: the run waited for the sandbox's wall-clock ceiling (30 s by default) and ended as a budget error with no business message, which the doors answer as a crash. The handler now records the fixed sentence above for any value it cannot read, and the run ends on the same path as any other rejection. The sentence names nothing the value carried and no stack. + + Measured at `POST /api/v1/actions/:object/:action` on a `type: 'script'` action: + + - `throw { code: 'EMAIL_TAKEN', get message() { throw new Error('boom') } }` + - FROM, after the 30 s ceiling: `500 {"success":false,"error":{"code":"INTERNAL_ERROR","message":"Internal server error","httpStatus":500}}` + - TO, in about 0.1 s: `400 {"success":false,"error":{"code":"VALIDATION_ERROR","message":"the script threw a value that could not be read","httpStatus":400,"declaredCode":"EMAIL_TAKEN"}}` + - `throw ''` and `throw Object.create(null)` + - FROM, after the 30 s ceiling: the same `500 INTERNAL_ERROR` + - TO, in about 0.1 s: `400 {"success":false,"error":{"code":"VALIDATION_ERROR","message":"the script threw a value that could not be read","httpStatus":400}}` + + The status and envelope are the ones the door already gives a body that threw; no door changed. A `code` the value carries is kept, as for any other thrown value. Two more values that could not be read move the same way: an `Error` whose `name` and `message` are both empty, and an `Error` whose `message` getter throws. To give the caller words, throw a readable `message` (or an `Error`). + + Control, unchanged byte for byte: every value the handler could already read (a thrown `Error` or subclass, a plain `{ code, message }` object and the other plain-object shapes, primitives, the engine's relayed duplicate refusal, a nested hook refusal's sentence, the `code` / `fields` / `status` / `userMessage` fields, and a capability denial). A body that never settles still ends at the wall-clock ceiling. + + In-process: the `SandboxError` a caller catches for such a throw now reads `KIND 'NAME' threw: Error: the script threw a value that could not be read`, with that sentence as `innerMessage`, instead of the wall-clock budget error. + + Clause-②: no +- f7a5ebe: fix(runtime,cli): `os start` and `os dev` refuse a SQLite database whose directory cannot be made, at once, instead of hanging + + Clause-②: no + + **What was wrong.** The standalone host (`createStandaloneStack`, which `os start`, `os dev`'s standalone boot and every embedder run) prepared the directory of a file-backed SQLite database with Node's recursive `mkdirSync`. Node's recursive mkdir never returns when a level answers `ENOENT` although its parent exists: for `OS_DATABASE_URL=file:/proc/os-x/ctl.db` it looped `mkdir("/proc/os-x")` and `mkdir("/proc")` without end, holding the event loop. `os start` printed `→ Starting server...` and hung with no timeout and no message. The `sqlite-wasm` arm (`wasm-sqlite://…`, or `OS_DATABASE_DRIVER=sqlite-wasm`) did the same. `os dev` hung the same way in its own process when its default database lived under a state directory that cannot be made (for example `OS_HOME=/proc/os-x`). + + **What an operator sees now.** The boot exits non-zero within its normal start-up time, naming the database file, the directory and the operating system's error: + + ```text + ✗ [StandaloneStack] SQLite database file '/proc/os-x/ctl.db' cannot be created: its directory '/proc/os-x' could not be made: ENOENT: no such file or directory, mkdir '/proc/os-x' + ``` + + `os dev` with an unusable state directory prints `✗ The default database '…/data/objectstack.db' cannot be created: its directory '…/data' could not be made: …`, followed by the remedy: point `OS_HOME` at a directory that can be created, or choose the database with `--database`. + + Nothing else changes. A creatable path is still made, every missing level of it. The read-only boot (`os migrate plan` over an absent database file) still creates no directory. The refusal names the path, never the database URL. +- 9d4fb3c: fix(runtime): the standalone host's database-URL refusals name the URL's scheme and never print the URL, so a password or token in `OS_DATABASE_URL` no longer reaches the console or the boot log + + Clause-②: no + + The standalone host is what `os dev`, `os start` and `os serve` boot over a plain config, and what `os migrate` boots. Its URL refusals interpolated the whole database URL, and the CLI prints that message as it exits. A URL is where postgres / mysql / mongodb credentials ride, so whenever such a URL was refused, the password and any token in its query string were printed. Each refusal now names only the scheme (the letters before `://`), which cannot hold a username, a password, a host, a path or a query. + + - **An unsupported scheme.** With `OS_DATABASE_URL=redis://PLACEHOLDER-USER:PLACEHOLDER-SECRET@127.0.0.1:6399/0?authToken=PLACEHOLDER-TOKEN`: + - FROM: `[StandaloneStack] Unsupported database URL scheme: redis://PLACEHOLDER-USER:PLACEHOLDER-SECRET@127.0.0.1:6399/0?authToken=PLACEHOLDER-TOKEN. Supported schemes: …` + - TO: `[StandaloneStack] Unsupported database URL scheme "redis://". Supported schemes: …`. The list of supported schemes is unchanged. `os migrate … --json` carries the same text in its `error` field. + - **A `memory://` or `mingo://` URL (the retired in-memory engine).** + - FROM: `The database URL "memory://…"` (or `OS_DATABASE_URL "memory://…"`) followed by the full URL. + - TO: `A database URL with the "memory://" scheme` (or `An OS_DATABASE_URL with the "memory://" scheme`). A declared default datasource reads `(driver: memory, URL scheme "memory://")`. The rest of the sentence, which names the SQLite replacements, is unchanged. + - **An explicit `sqlite` driver over a URL that is not a file path** (for example `OS_DATABASE_DRIVER=sqlite` beside a `postgres://…` URL). + - FROM: `sqlite driver was selected but the URL does not look like a file path: "postgres://PLACEHOLDER-USER:PLACEHOLDER-SECRET@…"` + - TO: `sqlite driver was selected but the URL does not look like a file path (its scheme is "postgres://")`. A bare `file:` now reads `(it names no file after "file:")`. + + What does not change: which URLs resolve, and to which driver; the URL handed to the driver, which keeps its credentials; and where each refusal is raised, with the same error class. Each message keeps the phrase that identifies it: `Unsupported database URL scheme`, `selects the in-memory (mingo) engine`, `does not look like a file path`. +- c5cf546: fix(runtime): the standalone host refuses an `OS_DATABASE_URL` with an opaque scheme (`jdbc:postgresql://…`, `redis:h`), and a `.wasm.db` ending no longer turns an unrecognised scheme into a SQLite file + + Clause-②: no + + This is the runtime's standalone host: `os dev`, `os start` and `os serve` over a plain config or an artifact, every `os migrate` subcommand, and every embedder of `createStandaloneStack`. + + - **Before, an opaque scheme (two or more characters and a `:` with no `//`).** `OS_DATABASE_URL=jdbc:postgresql://127.0.0.1:5999/db os serve --dev` printed `✓ Server is ready` and opened a SQLite file at a path made of the URL, creating the directories `./jdbc:postgresql:/127.0.0.1:5999/`. An operator who believed they were on Postgres wrote to that local file. This broke the host's own rule that an unknown URL scheme is refused and never falls back to SQLite. + - **Before, a `.wasm.db` ending.** It was tested ahead of every scheme check, so `OS_DATABASE_URL=redis://h/x.wasm.db` booted on a WASM SQLite file written under `./redis:/h/`. + - **After.** Both shapes get the refusal an unrecognised `://` already got, unchanged: `[StandaloneStack] Unsupported database URL scheme "jdbc:". Supported schemes: …`. The boot stops before any plugin starts and creates nothing on disk. The message names only the scheme, never the URL, so credentials in the URL are not printed. A `*.wasm.db` name still selects the WASM engine for a `file:` URL or a path with no scheme. The CLI's legacy `os serve` path already refuses these values, so the two hosts now give the same answer. + - **A one-letter drive is a path, not a scheme** (`C:\data\app.db`, `C:/data/app.db`). A file name whose first segment contains a colon now reads as a scheme, as it does on the CLI path: `backup:2026.db` was a SQLite file and is now refused. Write `./backup:2026.db` or `file:backup:2026.db` for that file. + + `postgres://`, `pg://`, `mysql://`, `mongodb://`, `libsql://`, `wasm-sqlite://`, `file:`, `sqlite:`, `:memory:` and paths with no scheme resolve as before. An explicit `OS_DATABASE_DRIVER` still wins over the URL, which is then not classified. +- 2e15620: fix(cli,runtime): a state, output or cache directory that can never be made is refused at once, naming the path and the operating system's code, instead of hanging + + Clause-②: no + + **What was wrong.** These commands and runtime paths created their directories with Node's recursive `mkdir`. Node's recursive mkdir never returns when a level answers `ENOENT` although its parent exists — what `/proc` and `/sys` answer for a name they do not have. It looped `mkdir("/proc/x")` and `mkdir("/proc")` without end, holding the event loop, so the command printed nothing more and no timeout could fire: + + - `os start` with `--home` / `OS_HOME` on such a path hung after its header. + - `os serve` hung the same way on the empty-boot artifact stub under `OS_HOME`, and on the cache of an `http(s):` `OS_ARTIFACT_URL` under `OS_HOME`. + - `os create --dir`, `os init `, `os compile --output`, `os generate` (`--dir`, and `--output` for types, client and migration), `os i18n extract --out` and `os plugin build --out` hung on an output path there. + - `os login` and `os cloud login` never finished writing credentials under a `HOME` there, and the package enable / disable state the packages routes write under `OS_HOME` held the event loop of the host serving them. + + **What an operator sees now.** The command exits non-zero at once with the operating system's own error, which names the level that cannot be made: + + ```text + ✗ Cannot create home directory at /proc/os-x: ENOENT: no such file or directory, mkdir '/proc/os-x' + ``` + + Not covered yet: the runtime state file `os serve` writes into `OS_HOME` once it is listening. + + Nothing else changes. Every directory these paths made before is still made, every missing level of it; every path that was refused before is still refused, with the operating system's code. The one difference is the path that was never answered at all. +- 0cb45e9: fix(runtime): an explicit `sqlite` or `sqlite-wasm` driver selection refuses a database URL that is not a file path, instead of opening a SQLite file at a path made of the URL + + Clause-②: no + + An explicit selection (`OS_DATABASE_DRIVER`, `--database-driver`, or the `databaseDriver` option) skips the URL classification, so the only check its URL meets is the SQLite file-name rule, `sqliteFilenameFromUrl`. Both hosts call that rule: the runtime's standalone host (`os dev`, `os start` and `os serve` over a plain config or an artifact, every `os migrate` subcommand, every embedder of `createStandaloneStack`) and the CLI's legacy `os serve` path, which re-raises its refusal as the fatal `UnsupportedDriverError`. + + - **Before, an opaque scheme (two or more characters and a `:` with no `//`).** `OS_DATABASE_DRIVER=sqlite OS_DATABASE_URL=jdbc:postgresql://127.0.0.1:5999/db os serve --dev` printed `✓ Server is ready` and created the directories `./jdbc:postgresql:/127.0.0.1:5999/`. `redis:h` opened a file named `redis:h`. This happened under both kinds. + - **Before, a server URL under `sqlite-wasm`.** The WASM kind refused nothing: `OS_DATABASE_DRIVER=sqlite-wasm` beside `OS_DATABASE_URL=postgres://USER:PASSWORD@127.0.0.1:5999/db` booted ready and created `./postgres:/USER:PASSWORD@127.0.0.1:5999/`, which put the credentials in a directory name. `redis://h/x.db` opened a file too. + - **After.** Under either kind, all of these get the refusal the `sqlite` kind already gave a `postgres://` URL. The kind is named in the sentence: `[StandaloneStack] sqlite-wasm driver was selected but the URL does not look like a file path (its scheme is "postgres://"). Use file:/path/to/db.sqlite, or set OS_DATABASE_DRIVER explicitly.` The message names the scheme only, never the URL, so credentials in the URL are not printed. The boot stops before it creates anything on disk. + - **One file name changes answer.** Under an explicit selection a first path segment that holds a colon now reads as a scheme, as it already does when the URL selects the driver: `backup:2026.db` was a SQLite file and is now refused as the scheme `backup:`. Write `./backup:2026.db` or `file:backup:2026.db` for that file. A one-letter drive is a path, not a scheme (`C:\data\app.db`, `C:/data/app.db`). + - **A `sqlite:` alias on a rung that does not normalize it.** `sqlite:` and `sqlite://` are aliases of `file:` in `OS_DATABASE_URL`, `--database` and the `databaseUrl` option. A declared default datasource's `url` and `TURSO_DATABASE_URL` reach the rule as written. There, under an explicit selection, `sqlite:./x.db` is now refused as the scheme `sqlite:`, where it used to open a file named `sqlite:./x.db`. `sqlite:///…` was already refused there under `sqlite` and is now refused under `sqlite-wasm` as well. + + `file:./x.db`, `./x.db`, `:memory:`, `wasm-sqlite:///path` (under `sqlite-wasm`), `sqlite:./x.db` in `OS_DATABASE_URL`, and paths with no scheme resolve to the same file as before. An empty name under `sqlite-wasm` is still `:memory:`. +- 605aa01: fix(runtime): the SQLite "does not look like a file path" refusal names what the operator can change, instead of telling an operator who set `OS_DATABASE_DRIVER` to set it explicitly + + Clause-②: no + + `sqliteFilenameFromUrl` raises this refusal, and both hosts print it: the runtime's standalone host with its `[StandaloneStack]` tag, and the CLI's legacy `os serve` path, untagged, as the fatal `UnsupportedDriverError`. The first clause is unchanged and only the advice after it changes. The same URLs are refused, with the same error class and the same exit, and the message still names the URL's scheme and never the URL. Nothing that resolved before resolves differently. + + - **A URL with a scheme.** This is how an explicit `sqlite` or `sqlite-wasm` selection (`OS_DATABASE_DRIVER`, `--database-driver`, or the `databaseDriver` option) meets a server URL, an opaque scheme such as `jdbc:`, or a `://` nothing recognises. + - FROM: `… (its scheme is "jdbc:"). Use file:/path/to/db.sqlite, or set OS_DATABASE_DRIVER explicitly.` The operator who reads this has already set the driver explicitly: that selection is what led here. + - TO: `… (its scheme is "jdbc:"). Use file:/path/to/db.sqlite to keep the sqlite driver, or use a driver that matches the URL: unset the driver selection (OS_DATABASE_DRIVER, --database-driver or the databaseDriver option) so the URL selects it, or set the selection to the URL's driver.` With the selection unset, a scheme no driver claims, such as `jdbc:`, gets the unsupported-scheme refusal, which lists the supported schemes. + - **A `file:` that names no file** (`OS_DATABASE_URL=file:`, whether the URL or an explicit `sqlite` selection chose the driver). + - FROM: `… (it names no file after "file:"). Use file:/path/to/db.sqlite, or set OS_DATABASE_DRIVER explicitly.` + - TO: `… (it names no file after "file:"). Name the file after "file:", as in file:/path/to/db.sqlite.` A `file:` URL is a SQLite URL whichever way the driver was chosen, so changing the driver never fixed it. +- 9e797a8: fix(runtime): the standalone server hands every `/api/v1/keys` verb to the `keys` domain, so an anonymous caller is answered `401 UNAUTHENTICATED` whatever the verb + + Clause-②: no + + The v18 text for this door reads "`/keys`: `401 UNAUTHENTICATED` for every verb". The `keys` domain takes that decision first, but the standalone server (the one `os dev` and `os serve` run) mounted `/api/v1/keys` for `POST` only. Any other verb never reached the domain: the HTTP adapter answered it itself, with a `405` that named `POST` as the allowed method. + + What a `GET`, `PUT`, `PATCH` or `DELETE` call to `/api/v1/keys` is answered on the standalone server: + + - **Anonymous caller.** Before: `405 METHOD_NOT_ALLOWED` with `Allow: POST`, from the adapter. Now: `401 UNAUTHENTICATED` in the dispatcher's standard envelope, `{ success: false, error: { code: 'UNAUTHENTICATED', message, httpStatus: 401 } }`. + - **Signed-in caller.** Before: `405 METHOD_NOT_ALLOWED` with `Allow: POST`, from the adapter. Now: still `405 METHOD_NOT_ALLOWED`, answered by the `keys` domain in the same standard envelope (`error.httpStatus: 405`, no `error.details`), and without an `Allow` header. + + `POST /api/v1/keys` is unchanged, and so is every answer on a host that reaches the dispatcher through `@objectstack/hono`'s catch-all, which already answered this way. A `HEAD` request follows `GET`: `401` anonymous, `405` signed in. A sub-path such as `/api/v1/keys/` is still not mounted on the standalone server and is answered `404`. +- 66d2757: feat(spec)!: every installed-package row declares where it came from, a closed `source: 'code' | 'marketplace' | 'authored'`, and the `GET /packages` rows declare the `writable` verdict they already served (#23972) + + Clause-②: yes (narrowing — the served row gains two declared keys, and the record schema, the served-row union and the registry's installPackage signature each refuse what they accepted before) + + **BREAKING** — three published accept sets narrow. `InstalledPackageSchema` refuses a row without `source`, or with a value outside `code` / `marketplace` / `authored`. The served-row union `InstalledPackageAtEitherStageSchema` refuses a row without a boolean `writable`. `SchemaRegistry.installPackage` and `EngineSchemaRegistryView.installPackage` take `source` as their second argument and refuse a call without it. The migration is below. None of it is mechanically convertible, because only the door that registers a package knows where it came from. + + **What is new on the wire.** `GET /api/v1/packages` and `GET /api/v1/packages/:id` serve two declared keys on every row: + + - `source` names the package's origin. `code` means the deployment's own code registered it (a `defineStack()` host, a plugin's bundled manifest, a platform package). `marketplace` means the marketplace install-local plugin installed it, and that holds after a restart too, when the plugin rehydrates it from its ledger. `authored` means it is a database-authored package (a Studio base, a duplicate, an HTTP install), including after the restart that rehydrates it from `sys_packages`. The door that registers the package stamps the value. No reader derives it. + - `writable` is the server's own writability verdict (ADR-0070 D2), served on these rows since #14375 and now declared. It is computed per read and never stored, so it is declared on the served row (`InstalledPackageAtEitherStageSchema`) and not on the registry record. + + FROM → TO for a reader of these rows: + + | you did | do instead | + | --- | --- | + | treated `writable: true` as authored, and looked up each non-writable row in the marketplace's installed list (the control plane's, else install-local's) to tell marketplace from code | read `row.source` | + | read the undeclared `writable` | read the declared `writable` (unchanged value) | + + The two keys are separate statements: `source` says where the package came from, and `writable` says what this server lets an org do to it. + + **`InstalledPackageSchema` requires `source`.** It is `InstalledPackageSourceSchema`, exported from `@objectstack/spec/kernel`. A row without it, or with a value outside the closed set, is refused at the `source` key. + + **API change for code that registers packages (`@objectstack/objectql`).** The registry records the origin and never infers it: + + | you wrote | write instead | + | --- | --- | + | `registry.installPackage(manifest)` | `registry.installPackage(manifest, 'code')` (or `'marketplace'` / `'authored'`, whichever is where the package came from) | + | `registry.installPackage(manifest, settings, scope)` | `registry.installPackage(manifest, source, settings, scope)` | + | `ctx.getService('manifest').register(artifact)` for a marketplace install | `ctx.getService('manifest').register(artifact, { source: 'marketplace' })` | + + `SchemaRegistry.installPackage` throws a `TypeError` naming the closed set when `source` is missing or unknown, before anything is recorded. `ObjectQL.registerApp(body, scope?, source?)` and the `manifest` service record `'code'` when no source is given. They accept `'code'` or `'marketplace'` and refuse `'authored'`. The `IObjectQLEngine` registry contract (`@objectstack/spec/contracts`) declares the same `installPackage(manifest, source, settings?)`. + + **The one-line fix:** pass the package's origin as `installPackage`'s second argument. + + Readers: objectui's Setup → Packages Source column and its `?source=` filter (it switches from the two-read join to `source` in its own follow-up), and the client SDK's `packages.list()` / `packages.get()` row types. + + +- 6dede7a: fix(runtime, plugin-sharing): on the standalone server, an anonymous caller of any `/api/v1/notifications` verb or any `/api/v1/share-links` path off the two token faces is answered `401 UNAUTHENTICATED` in the dispatcher's standard envelope + + Clause-②: no + + The v18 text for these two doors reads "`/notifications`: `401 UNAUTHENTICATED` on every face" and "`/share-links`: `401 UNAUTHENTICATED` on the create, list and revoke faces and on every path the domain does not serve", in "the dispatcher's standard envelope … with the shared message every other dispatcher domain answers with". A host that reaches the dispatcher through `@objectstack/hono`'s catch-all already answered that way. The standalone server (the one `os dev` and `os serve` run) did not: + + - `/api/v1/notifications`, `/api/v1/notifications/read` and `/api/v1/notifications/read/all` were each mounted for the one verb they serve, so any other verb never reached the `notifications` domain and the HTTP adapter answered `405`, naming the allowed methods, to an anonymous caller as well. + - `/api/v1/share-links` is served there by `@objectstack/plugin-sharing`'s routes, not by the dispatcher. They registered five routes and nothing else, so another verb or path got the adapter's `405` or `404`, and the create, list and revoke faces answered `401` with a sentence of their own ("Sign in to list share links") and no `error.httpStatus`. + + What the standalone server answers now: + + - **Anonymous caller.** `401 UNAUTHENTICATED`, `{ success: false, error: { code: 'UNAUTHENTICATED', message: 'Authentication is required to access this endpoint.', httpStatus: 401 } }`, on every verb of the three notifications paths, and on every verb and path under `/api/v1/share-links` except `GET /share-links/:token/resolve` and `GET /share-links/:token/messages`, which the capability token still admits. A body the server cannot parse does not outrank this answer anywhere under `/api/v1/share-links`, nor on the notifications verbs no face serves. + - **Signed-in caller of a verb or path that is not served.** Still refused. On the notifications paths the domain answers it: `404 ROUTE_NOT_FOUND` from the dispatcher (or the `501` capability answer on a deployment with no inbox), where the adapter used to answer `405`. Under `/api/v1/share-links` the share-link routes answer it themselves: `404 ENDPOINT_NOT_FOUND`, naming the request, where the adapter used to answer `405` or `404`. No `Allow` header is sent on either. + + Every request that was served is served as before. A notifications sub-path no face names, such as `/api/v1/notifications/`, is still not mounted on the standalone server and is answered `404`. +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [b9dbc3d] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [1eac7ad] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [14aff67] +- Updated dependencies [abb6df0] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [cf07168] +- Updated dependencies [f846cc2] +- Updated dependencies [30ce56e] +- Updated dependencies [df2c551] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [0cfd0c4] +- Updated dependencies [ae4dfaf] +- Updated dependencies [099cc9d] +- Updated dependencies [6422da5] +- Updated dependencies [2034975] +- Updated dependencies [61e5ee9] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [a33f3d5] +- Updated dependencies [6e18016] +- Updated dependencies [d050a8f] +- Updated dependencies [b25d98a] +- Updated dependencies [5fb8d15] +- Updated dependencies [c4111c6] +- Updated dependencies [5c2ac83] +- Updated dependencies [4ffdebe] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [aefab7c] +- Updated dependencies [66d2757] +- Updated dependencies [e246f06] +- Updated dependencies [a65a59b] +- Updated dependencies [077b75f] +- Updated dependencies [c1c0a90] +- Updated dependencies [ab3ba2c] +- Updated dependencies [d6bd98a] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/plugin-security@18.0.0-next.3 + - @objectstack/rest@18.0.0-next.3 + - @objectstack/metadata-core@18.0.0-next.3 + - @objectstack/metadata-protocol@18.0.0-next.3 + - @objectstack/metadata@18.0.0-next.3 + - @objectstack/objectql@18.0.0-next.3 + - @objectstack/driver-sql@18.0.0-next.3 + - @objectstack/plugin-auth@18.0.0-next.3 + - @objectstack/driver-turso@18.0.0-next.3 + - @objectstack/core@18.0.0-next.3 + - @objectstack/driver-memory@18.0.0-next.3 + - @objectstack/driver-sqlite-wasm@18.0.0-next.3 + - @objectstack/formula@18.0.0-next.3 + - @objectstack/observability@18.0.0-next.3 + - @objectstack/service-cluster@18.0.0-next.3 + - @objectstack/service-datasource@18.0.0-next.3 + - @objectstack/service-i18n@18.0.0-next.3 + - @objectstack/types@18.0.0-next.3 + ## 18.0.0-next.2 ### Minor Changes diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 7bc9cfb82ac..c3ca17cf3b9 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/runtime", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "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 2065a3a2c8f..d82ff72e87a 100644 --- a/packages/sdui-parser/CHANGELOG.md +++ b/packages/sdui-parser/CHANGELOG.md @@ -1,5 +1,9 @@ # @objectstack/sdui-parser +## 18.0.0-next.3 + +No changes in this release. + ## 18.0.0-next.2 No changes in this release. diff --git a/packages/sdui-parser/package.json b/packages/sdui-parser/package.json index 41e03442bcf..b42d223d8ed 100644 --- a/packages/sdui-parser/package.json +++ b/packages/sdui-parser/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/sdui-parser", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "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 e15f8727fed..8da0a8a73e2 100644 --- a/packages/services/service-analytics/CHANGELOG.md +++ b/packages/services/service-analytics/CHANGELOG.md @@ -1,5 +1,53 @@ # Changelog — @objectstack/service-analytics +## 18.0.0-next.3 + +### Patch Changes + +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [6422da5] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [4ffdebe] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [e246f06] +- Updated dependencies [a65a59b] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/core@18.0.0-next.3 + - @objectstack/types@18.0.0-next.3 + ## 18.0.0-next.2 ### Patch Changes diff --git a/packages/services/service-analytics/package.json b/packages/services/service-analytics/package.json index 5601b57bdfa..c5ad7aa3d0a 100644 --- a/packages/services/service-analytics/package.json +++ b/packages/services/service-analytics/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-analytics", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "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 ca1bec19d7f..06355fd03fe 100644 --- a/packages/services/service-automation/CHANGELOG.md +++ b/packages/services/service-automation/CHANGELOG.md @@ -1,5 +1,204 @@ # @objectstack/service-automation +## 18.0.0-next.3 + +### Minor Changes + +- f901746: feat(spec)!: the retired callout node types `http_request`, `http_call` and `webhook` are reserved inside the open flow-node namespace; `flow-node-http-callout-rename` leaves the load path + + Clause-②: yes (narrowing) + + **BREAKING (authoring):** ADR-0018 unified the three outbound callout node types `http_request`, `http_call` and `webhook` into the one `http` node, and through 17 the loader rewrote an authored node typed with one of them to `http` with a notice. A flow node's `type` is an open namespace (a plugin registers its own node types), so nothing refused the old names. At 18 they are reserved: the flow parse refuses each as a node's `type` by name, the automation engine refuses to register a node executor under one, and the ADR-0087 D2 conversion `flow-node-http-callout-rename` (protocol 11) is retired from the load path (`retiredAfter: '17.7.0'`). A wait node's `waitEventConfig.eventType: 'webhook'` is a different field and is not affected. + + ## FROM → TO + + | you wrote (17.7 and earlier) | write instead | + | --- | --- | + | a flow node `type: 'http_request'` | `type: 'http'` | + | a flow node `type: 'http_call'` | `type: 'http'` | + | a flow node `type: 'webhook'` | `type: 'http'` | + | a plugin's `engine.registerNodeExecutor({ type: 'webhook', … })` (or either other name) | a type of your own, e.g. `'acme_webhook'` | + + **The one-line fix:** change the node's `type` to `'http'`; the node's `id`, `label` and `config` stay as they are (the conversion never touched `config`, so the `http` contract judges it as written). + + **What an author who still writes it sees.** The parse refuses the node at its own `type` path — a top-level node and a node inside a `loop`, `parallel` or `try_catch` region alike. `defineStack` throws `STACK_SCHEMA_INVALID` (422), `os validate`, `os build` and `os lint` (whose config calls `defineStack`) print the issue, and the metadata save door answers a NEW flow with `422 INVALID_METADATA` at the node's `type`, with or without an automation service, and stores nothing: + + > Flow node `type: 'http_request'` was removed in @objectstack/spec 18 (ADR-0087 D2) — it is one of the three callout node types ADR-0018 unified into `http`, the loader rewrote it to `http` until this release, and the name stays reserved inside the open node namespace so no plugin can claim it (ADR-0018). Use `type: 'http'`. Change the value; the conversion never touched the node's `config`, so the `http` contract judges it as written. + + The refusal is a `pattern` on `FlowNodeSchema.type`, so the published JSON Schema carries it too. + + **`@objectstack/service-automation`:** `AutomationEngine.registerNodeExecutor` (and `registerNodeAlias`, which calls it) throws when the executor's `type` or its descriptor's `type` is one of the three names, and registers nothing. No node could ever reach such an executor: an authored node with the name is refused at parse, and a stored one is rewritten to `http` before it parses. A plugin that registered its own executor under one of these names fails at that call and needs a type of its own. + + **No tool makes the rename.** The conversion belongs to protocol 11, below the migration floor, so `objectstack migrate meta` applies no edit for it and `--write` writes none: it prints each such node among the refusals the migrated stack still has, at `flows.N.nodes.M.type`, and lists the D3 entry `flow-node-http-callout-types-reserved` as the hand edit. + + **Stored metadata needs no edit.** Stored-row rehydration, the automation engine's flow rehydration and the artifact-ingestion door inside its window still replay the conversion, so a `sys_metadata` flow row written with an old name keeps loading as an `http` node. The rewrite there is now unconditional: until 18 it skipped a node whose old name a live executor owned and reported a conflict, and the engine no longer lets such an executor exist. + + ## The retirement kit + + - **The reserved-name table** `FLOW_RESERVED_NODE_TYPES` and its refusal `reservedFlowNodeTypeRefusal` (`@objectstack/spec/automation`), read by the flow parse, the engine's registration refusal and the conversion itself. + - **The conversion** is `retiredFromLoadPath: true`, `retiredAfter: '17.7.0'`; its ADR-0078 conflict guard is gone. + - **The D3 entry `flow-node-http-callout-types-reserved`.** + - **No deprecation window**, per the project's startup-stage posture. + + ⚠️ **The out-of-repo consumer population is NOT MEASURED.** `@objectstack/spec` and `@objectstack/service-automation` are published, so this is breaking for consumers no telemetry was consulted for. + + +- cf07168: feat(metadata-protocol)!: the metadata save door refuses a retired flow spelling on a NEW flow, and a stored flow keeps re-saving + + Clause-②: yes (narrowing) + + **BREAKING (API save door):** a flow saved through `saveMetaItem` (`PUT /api/v1/meta/flow/:name`, the runtime dispatcher, and every caller of the protocol) with no stored row of that name at the save's address is now judged as authored input. It is canonicalized without the ADR-0087 D2 conversions retired from the authoring funnel, so a spelling the loader stopped lifting at 18 is no longer rewritten and stored: it meets the flow parse's refusal, `422 INVALID_METADATA`, with an issue located at the key and the same prescription `os validate` and `os build` print. Draft and publish mode alike. Before, the door lifted the old spelling, stored the new one and said nothing, so the client that wrote it (an AI writing flows through MCP or REST above all) was never told and wrote it again. + + What counts as a stored row: the row the save overwrites, at its state and package, and for a draft save with no draft yet the active row the draft overlays, so Studio's first draft edit of a stored flow is a re-save. + + Not changed: + + - A re-save of a stored flow keeps replaying the full chain, retired entries included, so a legacy row stays editable and is healed by the save, exactly as before. + - The server's own writes of bodies at rest keep the full chain: the stored-metadata migration (`os migrate meta --stored`), a package duplicate and an unmanaged package import. + - The automation engine's flow rehydration (`registerFlow`) and the read path, which still serves a stored flow verbatim. + + Migration, for a client that creates flows through the API: + + - `wait` node loose `config.duration` / `config.timerDuration` → `waitEventConfig.timerDuration`; `config.signal` / `config.signalName` → `waitEventConfig.signalName`; `config.eventType` → `waitEventConfig.eventType`. On the block itself, `timeoutMs` → `timerDuration` and `onTimeout` is removed. + - `connector_action` node loose `config.connectorId` / `config.actionId` / `config.input` → its `connectorConfig` block. + - CRUD node `config.filters` → `config.filter`; `config.object` → `config.objectName`. + - `map` and `subflow` node `config.flow` → `config.flowName`. + - `notify` node `config.to` → `recipients`, `subject` → `title`, `body` → `message`, `url` → `actionUrl`, `source: { object, id }` → `sourceObject` / `sourceId`. + - `script` node `config.functionName` → `function`, `input` → `inputs`; `actionType` / `template` / `recipients` / `variables` / `script` are removed. + - `errorHandling.retryDelayMs` and a `try_catch` node's `retry.retryDelayMs` → `backoffMs`; the flow keys `active` / `template`, a node's `outputSchema` and `errorHandling.fallbackNodeId` are removed. + + Each refusal names the key to write. `os migrate meta --from 16` lists the same edits for source files. Rows already stored need nothing. + + `@objectstack/service-automation`: `AutomationEngine.canonicalizeStoredFlow(name, definition, options?)` takes `{ includeRetired?: boolean }`. Omitted, it is `true` (the full chain, as every caller holding a body at rest wants). `false` is the authoring posture the save door asks for. `registerFlow` does not take the option. + + +- 6422da5: feat(automation): `getFlowRuntimeStates()` stamps `unboundCause` beside `reason` + + Clause-②: yes (widening) + + Each branch that decides why an enabled, triggered flow is not armed now answers its closed `FlowUnboundCause` together with its sentence: `deployment_policy`, `disabled_subflow`, `missing_trigger` or `binding_failure`. `GET /automation/_status` passes the rows through unchanged, so the field reaches the console with no route change. Bound, disabled and trigger-less rows carry neither `reason` nor `unboundCause`. The sentences and `getTriggerBindingAudit()` are unchanged. +- a65a59b: feat(spec)!: the ADR-0078 conversion-conflict channel is retired — `CONVERSION_CONFLICT_CODE`, `ConversionConflictNotice`, `ConversionConflictDetail`, the `onConflict` / `reservedNodeTypes` conversion options, the `conflicts` arrays and the `FLOW_CONVERSION_CONFLICT` 409 + + Clause-②: yes (narrowing) + + **BREAKING (published API):** the ADR-0087 conversion API carried a channel for a rename REFUSED because its old token was a live name in the environment: a conversion reported it through `ConversionContext.reportConflict`, `applyConversions` handed it to an `onConflict` sink as a `ConversionConflictNotice` (code `OS_METADATA_CONVERSION_CONFLICT`), the automation engine collected it in `canonicalizeStoredFlow`'s `conflicts` array, and the metadata protocol answered it with `409 FLOW_CONVERSION_CONFLICT`. Its only producer was the live-executor guard inside the flow-node HTTP callout rename. Protocol 18 reserved those three names (`FLOW_RESERVED_NODE_TYPES`), so the engine refuses an executor under one, no live owner can exist, and the guard went. Nothing can raise a conversion conflict any more, so the channel leaves the published surface rather than staying as a live-looking failure mode nothing triggers. + + ## FROM → TO + + | you wrote | write instead | + | --- | --- | + | `import { CONVERSION_CONFLICT_CODE, type ConversionConflictNotice, type ConversionConflictDetail } from '@objectstack/spec'` | delete the import — no replacement | + | `applyConversions(stack, { onConflict, reservedNodeTypes, … })` (likewise `applyConversionsToFlow`, `applyConversionsToStoredItem`, `collectConversionNotices`) | delete both options; keep `onNotice` / `onTodo` | + | a conversion's `apply` reading `context.reservedNodeTypes` or calling `context.reportConflict` | a rename over an open namespace reserves its old names instead (the `FLOW_RESERVED_NODE_TYPES` pattern), so the refusal happens at registration; a site with no lossless rewrite goes to `context.reportTodo` | + | an `IAutomationService.canonicalizeStoredFlow` implementation returning `conflicts: […]` (or a `StoredFlowCanonicalization` from `@objectstack/metadata-protocol` carrying one) | drop the member; return `parsed`, `storable`, `notices` and optionally `todos` | + | a client branch on `error.code === 'FLOW_CONVERSION_CONFLICT'` / HTTP 409 from a flow save, a package duplication or an unmanaged package import | delete the branch — no request can produce it | + + **The one-line fix:** delete every reference; the branch it guarded was already dead, so deleting it changes no behaviour. + + **What an upgrader sees.** Importing a removed name is `TS2305`; passing a removed option, or returning a `conflicts` member from a typed canonicalizer, is an excess-property `tsc` error. `ErrorCode` no longer parses `FLOW_CONVERSION_CONFLICT`. + + **Runtime behaviour is unchanged.** The removed branches could not fire: a stored flow carrying a reserved callout name still loads as `http` (the rewrite has been unconditional since the names were reserved), and `saveMetaItem`, `duplicatePackage`, `importUnmanagedPackage` and `os migrate meta --stored` answer exactly as before. No metadata document needs editing. + + ## The retirement kit + + - **`@objectstack/spec`:** the three exports leave the root; `ApplyConversionsOptions` loses `onConflict` and `reservedNodeTypes`, `ConversionContext` loses `reservedNodeTypes` and `reportConflict`, the `IAutomationService.canonicalizeStoredFlow` result loses `conflicts`; the error-code ledger drops `FLOW_CONVERSION_CONFLICT` by its own "Retiring a code" rule (a row comes out with its last emitter). + - **`@objectstack/service-automation`:** `AutomationEngine.canonicalizeStoredFlow` no longer returns `conflicts`, and the engine no longer builds a live-name set for the conversion pass. + - **`@objectstack/metadata-protocol`:** `StoredFlowCanonicalization` loses `conflicts`; the flow save door's 409, the stored-migration pass's `failed` row for a refused rename, `duplicatePackage`'s failed item and `importUnmanagedPackage`'s 409 are gone with it. + - **The D3 entry `conversion-conflict-channel-removed`.** No D2 conversion and no tombstone: no metadata document ever carried any of it. + - **No deprecation window**, per the project's startup-stage posture. + + ⚠️ **The out-of-repo consumer population is NOT MEASURED.** The three packages are published, so this is breaking for consumers no telemetry was consulted for. + + + +### Patch Changes + +- 099cc9d: Clause-②: no + + The approval, security, automation and sharing objects describe their records in plain admin + sentences. Approval Delegations now reads "While someone is away, approvals assigned to them + personally go to their delegate.", Record Shares "Records shared beyond their owner, one row per + grant.", User Positions "Which positions each user holds, one row per assignment.", Audience + Binding Suggestions "A package's suggestion to give one of its permission sets to everyone who + signs in, and the decision taken on it." and Automation Runs "Paused automation runs waiting to + resume, and the recent history of finished runs." Field help on Approval Requests, Approval + Delegations, User Positions, User Permission Sets and Audience Binding Suggestions, the + descriptions of the `organization_admin_no_bypass`, `mcp_agent_data_read` and + `mcp_agent_data_write` permission sets, the flow-dispatch ledger's description and the + reserved-position refusal no longer carry design-record numbers or runtime vocabulary; those + references stay in the code comments. The zh-CN, ja-JP and es-ES translations follow. Wording + only: no field, permission or behaviour changes. +- 2054a08: Clause-②: no + + A failed `parallel` run now shows what its branches did. Under the block's own `failure` step, the run's step log lists every branch's steps, each tagged with `parentNodeId`, `regionKind: 'parallel-branch'` and its `branch` index: a branch that completed, with its `success` step, and the failing branch's steps up to and including the one that failed, with its error. The run summary counts what those steps did. A fan-out whose notify branch delivered and whose http branch failed now reports `acted: 1` and `failed: 2` (the http step and the block), the way a failed `loop` already does. Before, the log held only the block's failure step and the summary read `acted: 0`. That reads as "nothing happened, safe to re-run" over a notification that had already been sent. + + The block now reports its failure once every branch has settled, not at the first branch to fail, so a branch that finishes later keeps its step and its effect is counted. Routing is unchanged. The failure is still the block's returned failure, so a `fault` edge on the `parallel` routes it with the same step code, error text and `$error`. A durable pause refused inside a branch still fails the run. +- 19955fb: Clause-②: no + + fix(service-automation): a resume refused because the run's flow or parked node is gone now settles the run's `sys_automation_run` row + + - **What changed.** A resume refused with `RUN_NOT_FOUND` because the run's flow is no longer registered, or because the node it parked on was edited away, now settles the run in the same call. The paused row is consumed and a `failed` history row records the refusal's own text in `error`. Before, the row kept `status: 'paused'` through every later resume, an uninstall and a cold restart. Run lists and paused-run counts kept showing a run the engine already knew could never continue, and retention never aged the row out, because it prunes terminal statuses only. + - **Unchanged:** the answer. It has the same code and the same text, and a transport still answers 404. A second resume answers `RUN_NOT_FOUND` again (`No suspended run`) and writes nothing. Retention is unchanged: `failed` was already in its scope, and paused rows are still never pruned. + - **When nothing is recorded.** Before the host seals the boot, a missing flow may be one that is not registered yet. `AutomationServicePlugin` re-arms overdue wait timers in `start()`, before flows bound at `kernel:ready` arrive. So before the seal the refusal records nothing, as before. The host seals with `sealNodeTypeVocabulary()`, which the plugin calls at `kernel:bootstrapped`. An embedded host that never calls it keeps the previous behaviour. + - **Concurrency.** The settle consumes the row through the same compare-and-set a resume takes. If a concurrent resume or cancel consumed the row first, the call writes nothing. If the store cannot answer the compare-and-set, the resume answers `STORE_UNAVAILABLE` (503), and a retry settles the run. +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [14aff67] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [cf07168] +- Updated dependencies [f846cc2] +- Updated dependencies [30ce56e] +- Updated dependencies [2e0e451] +- Updated dependencies [df2c551] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [77efc66] +- Updated dependencies [6422da5] +- Updated dependencies [8f4260f] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [6e18016] +- Updated dependencies [d050a8f] +- Updated dependencies [4ffdebe] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [e246f06] +- Updated dependencies [a65a59b] +- Updated dependencies [077b75f] +- Updated dependencies [d6bd98a] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/metadata-core@18.0.0-next.3 + - @objectstack/metadata-protocol@18.0.0-next.3 + - @objectstack/platform-objects@18.0.0-next.3 + - @objectstack/core@18.0.0-next.3 + - @objectstack/formula@18.0.0-next.3 + - @objectstack/types@18.0.0-next.3 + ## 18.0.0-next.2 ### Minor Changes diff --git a/packages/services/service-automation/package.json b/packages/services/service-automation/package.json index 2b458015dd0..b9b71cfa6d0 100644 --- a/packages/services/service-automation/package.json +++ b/packages/services/service-automation/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-automation", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "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 7c6f28ccf84..0f401f85360 100644 --- a/packages/services/service-cache/CHANGELOG.md +++ b/packages/services/service-cache/CHANGELOG.md @@ -1,5 +1,53 @@ # @objectstack/service-cache +## 18.0.0-next.3 + +### Patch Changes + +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [6422da5] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [4ffdebe] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [e246f06] +- Updated dependencies [a65a59b] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/core@18.0.0-next.3 + - @objectstack/observability@18.0.0-next.3 + ## 18.0.0-next.2 ### Patch Changes diff --git a/packages/services/service-cache/package.json b/packages/services/service-cache/package.json index ef997773995..899431b0624 100644 --- a/packages/services/service-cache/package.json +++ b/packages/services/service-cache/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-cache", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "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 08a9c20d400..e64635045bb 100644 --- a/packages/services/service-cluster-redis/CHANGELOG.md +++ b/packages/services/service-cluster-redis/CHANGELOG.md @@ -1,5 +1,50 @@ # @objectstack/service-cluster-redis +## 18.0.0-next.3 + +### Patch Changes + +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [6422da5] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [a65a59b] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/service-cluster@18.0.0-next.3 + ## 18.0.0-next.2 ### Patch Changes diff --git a/packages/services/service-cluster-redis/package.json b/packages/services/service-cluster-redis/package.json index e89454b7483..a137648e35a 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": "18.0.0-next.2", + "version": "18.0.0-next.3", "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 0bdf9ec3f0a..301f4d5a4a2 100644 --- a/packages/services/service-cluster/CHANGELOG.md +++ b/packages/services/service-cluster/CHANGELOG.md @@ -1,5 +1,52 @@ # @objectstack/service-cluster +## 18.0.0-next.3 + +### Patch Changes + +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [6422da5] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [4ffdebe] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [e246f06] +- Updated dependencies [a65a59b] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/core@18.0.0-next.3 + ## 18.0.0-next.2 ### Patch Changes diff --git a/packages/services/service-cluster/package.json b/packages/services/service-cluster/package.json index dbc94aad8ee..6b25cd49ba1 100644 --- a/packages/services/service-cluster/package.json +++ b/packages/services/service-cluster/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-cluster", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "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 f0e6fda1eaf..540232feb44 100644 --- a/packages/services/service-datasource/CHANGELOG.md +++ b/packages/services/service-datasource/CHANGELOG.md @@ -1,5 +1,66 @@ # @objectstack/service-external-datasource +## 18.0.0-next.3 + +### Patch Changes + +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [30ce56e] +- Updated dependencies [df2c551] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [ae4dfaf] +- Updated dependencies [6422da5] +- Updated dependencies [2034975] +- Updated dependencies [61e5ee9] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [c4111c6] +- Updated dependencies [4ffdebe] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [e246f06] +- Updated dependencies [a65a59b] +- Updated dependencies [077b75f] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/metadata-core@18.0.0-next.3 + - @objectstack/driver-sql@18.0.0-next.3 + - @objectstack/driver-turso@18.0.0-next.3 + - @objectstack/core@18.0.0-next.3 + - @objectstack/driver-memory@18.0.0-next.3 + - @objectstack/driver-mongodb@18.0.0-next.3 + - @objectstack/driver-sqlite-wasm@18.0.0-next.3 + - @objectstack/types@18.0.0-next.3 + ## 18.0.0-next.2 ### Minor Changes diff --git a/packages/services/service-datasource/package.json b/packages/services/service-datasource/package.json index b3cba7995e3..9cccc890327 100644 --- a/packages/services/service-datasource/package.json +++ b/packages/services/service-datasource/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-datasource", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "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 f4bc05bdc81..b03a6a3168c 100644 --- a/packages/services/service-i18n/CHANGELOG.md +++ b/packages/services/service-i18n/CHANGELOG.md @@ -1,5 +1,53 @@ # @objectstack/service-i18n +## 18.0.0-next.3 + +### Patch Changes + +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [6422da5] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [4ffdebe] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [e246f06] +- Updated dependencies [a65a59b] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/core@18.0.0-next.3 + - @objectstack/types@18.0.0-next.3 + ## 18.0.0-next.2 ### Minor Changes diff --git a/packages/services/service-i18n/package.json b/packages/services/service-i18n/package.json index 74e43488f07..52fb2e48a24 100644 --- a/packages/services/service-i18n/package.json +++ b/packages/services/service-i18n/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-i18n", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "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 f047560414d..0c156a9d799 100644 --- a/packages/services/service-job/CHANGELOG.md +++ b/packages/services/service-job/CHANGELOG.md @@ -1,5 +1,81 @@ # @objectstack/service-job +## 18.0.0-next.3 + +### Patch Changes + +- f606ba2: feat(spec)!: a job `retryPolicy` block whose `maxRetries` is omitted or 0 is refused, and `retry-policy-converged` leaves the load path + + Clause-②: yes (narrowing) + + **BREAKING (authoring):** `maxRetries` has defaulted to 0 since 17, so a `job.retryPolicy` block that omits it, or writes 0, buys no retry: the job runs once. Through 17 the loader hid that by writing the pre-17 `maxRetries: 3` and `backoffMultiplier: 2` into such a block with a notice. At 18 the ADR-0087 D2 conversion `retry-policy-converged` is retired from the load path (`retiredAfter: '17.7.0'`), and the job parse refuses the block at `retryPolicy.maxRetries`, at every door that parses an authored job (`defineStack`, `os validate`, `os build`, and `os lint` when the config calls it): "A retry block with no attempts is no retry block: state `maxRetries` ≥ 1, for example 3, or delete the block." The rule is the job's, the same one `flow.errorHandling` applies to `strategy: 'retry'`; the shared `RetryPolicySchema` is unchanged, so a `try_catch` node's `retry` region is judged as before. + + Migration: + + - `retryPolicy: {}` or `retryPolicy: { backoffMs: 5000 }` → `retryPolicy: { maxRetries: 3, backoffMs: 5000 }` (3 is the count such a block ran with before 17), or delete the block if the job should run once. + - `retryPolicy: { maxRetries: 0, … }` → delete the block: a job with no `retryPolicy` is a single attempt. + + What an author who still writes it sees: `defineStack` throws `STACK_SCHEMA_INVALID` (422) with one issue at `jobs.N.retryPolicy.maxRetries` carrying the prescription above, and `os validate` prints the same issue. Nothing is rewritten silently any more. + + Not refused: a block that states `maxRetries` and omits `backoffMultiplier`. It keeps the declared default of 1, a flat delay, where the loader used to write 2. State `backoffMultiplier: 2` to keep the old exponential curve. + + Run `os migrate meta --from 16` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. For such a block the edit writes both pre-17 numbers (`maxRetries: 3`, `backoffMultiplier: 2`). The D3 entry `job-retry-policy-without-attempts-refused` states the decision left to you: keep the written count, or delete the block. + + The flow half of the conversion (`retryDelayMs` → `backoffMs` on `flow.errorHandling` and a `try_catch` node's `retry`) needed nothing more: both surfaces already refuse `retryDelayMs` with the rename to `backoffMs`. + + Stored metadata needs no edit. Stored-row rehydration, the automation engine's flow rehydration, the metadata save door's flow fallback and the artifact-ingestion door inside its window still replay the conversion, so a job row stored without a count keeps loading with the three retries it ran with. + + `@objectstack/service-job`: a comment-only change in `runWithPolicy`, whose `maxRetries` default is now reached only by a caller that builds the options by hand. + + +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [30ce56e] +- Updated dependencies [2e0e451] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [77efc66] +- Updated dependencies [6422da5] +- Updated dependencies [8f4260f] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [4ffdebe] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [e246f06] +- Updated dependencies [a65a59b] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/platform-objects@18.0.0-next.3 + - @objectstack/core@18.0.0-next.3 + ## 18.0.0-next.2 ### Patch Changes diff --git a/packages/services/service-job/package.json b/packages/services/service-job/package.json index 68afc70053f..95d08c5257e 100644 --- a/packages/services/service-job/package.json +++ b/packages/services/service-job/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-job", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "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 efcdb103bb4..a99e4b5947c 100644 --- a/packages/services/service-knowledge/CHANGELOG.md +++ b/packages/services/service-knowledge/CHANGELOG.md @@ -1,5 +1,52 @@ # @objectstack/service-knowledge +## 18.0.0-next.3 + +### Patch Changes + +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [6422da5] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [4ffdebe] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [e246f06] +- Updated dependencies [a65a59b] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/core@18.0.0-next.3 + ## 18.0.0-next.2 ### Patch Changes diff --git a/packages/services/service-knowledge/package.json b/packages/services/service-knowledge/package.json index 32d90df611d..1beb6adfe5c 100644 --- a/packages/services/service-knowledge/package.json +++ b/packages/services/service-knowledge/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-knowledge", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "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-mailbox/CHANGELOG.md b/packages/services/service-mailbox/CHANGELOG.md index 9f00a75e584..1ad8521e061 100644 --- a/packages/services/service-mailbox/CHANGELOG.md +++ b/packages/services/service-mailbox/CHANGELOG.md @@ -1,5 +1,59 @@ # @objectstack/service-mailbox +## 18.0.0-next.3 + +### Patch Changes + +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [30ce56e] +- Updated dependencies [2e0e451] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [77efc66] +- Updated dependencies [6422da5] +- Updated dependencies [8f4260f] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [4ffdebe] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [e246f06] +- Updated dependencies [a65a59b] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/platform-objects@18.0.0-next.3 + - @objectstack/core@18.0.0-next.3 + - @objectstack/types@18.0.0-next.3 + - @objectstack/connector-imap@18.0.0-next.3 + ## 18.0.0-next.2 ### Minor Changes diff --git a/packages/services/service-mailbox/package.json b/packages/services/service-mailbox/package.json index b4ca59e9b3c..cc2deda50e2 100644 --- a/packages/services/service-mailbox/package.json +++ b/packages/services/service-mailbox/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-mailbox", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "license": "Apache-2.0", "description": "Mailbox Service for ObjectStack \u2014 inbound mail: the mirrored message (sys_mail_message), its record associations and their read gates.", "type": "module", diff --git a/packages/services/service-messaging/CHANGELOG.md b/packages/services/service-messaging/CHANGELOG.md index ea05f0d35dc..77d50d43013 100644 --- a/packages/services/service-messaging/CHANGELOG.md +++ b/packages/services/service-messaging/CHANGELOG.md @@ -1,5 +1,84 @@ # @objectstack/service-messaging +## 18.0.0-next.3 + +### Patch Changes + +- 30ce56e: Clause-②: no + + Setup pages describe their records in plain admin sentences. Business Units now reads "Your + company structure — divisions, departments, regions and offices — used to scope data and + reports.", Notification Events "Every notification the platform sent, one row per event.", + Notification Preferences "Which topics and channels each user receives; admins set the + defaults.", Notification Templates "The subject and body each notification topic sends, per + channel and language." and HTTP Deliveries "Outgoing HTTP calls made by webhooks and flows, one + row per call, with its delivery status." Field help on Users, Organizations, Invitations and SSO + Providers, and the metadata-storage and notification-delivery object descriptions, no longer + carry design-record numbers; those references stay in the code comments. The zh-CN, ja-JP and + es-ES translations follow. Wording only: no field, permission or behaviour changes. +- 8f4260f: Setup's Invitations, API Keys, OAuth Applications and Notification Preferences, Subscriptions and Templates lists say what belongs on them when they are empty, and API Keys gains a Create Key button (#23852) + + Clause-②: no + + **What an admin saw.** An empty Invitations, API Keys or OAuth Applications list read "No identity records — these records are created by the authentication provider … not added by hand here", which is false for all three. The three notification lists read the generic "Nothing here yet". API Keys had no way to create a key at all. + + **What changes.** + + - Every list view of `sys_invitation`, `sys_api_key` and `sys_oauth_application` declares its own `emptyState` (a title and one sentence), and a filtered view names itself: Invitations opens on "No pending invitations", the Revoked keys view reads "No revoked keys". + - `sys_notification_preference`, `sys_notification_subscription` and `sys_notification_template` each ship one list view, `all`, declared only for its `emptyState`: it sets no columns (`columns: []`, so the grid keeps the columns it derived before), no filter, no sort and no page size. + - `sys_api_key` gains `create_api_key` ("Create Key"), a primary list-toolbar action on every API Keys list, Setup's and the Account app's. It calls the existing `POST /api/v1/keys` route with the name you give it; the key is created for you, in the organization you are working in, and the raw key is shown once in a dialog you close after copying it. Nothing about the route changes, and the data API still does not create keys. + - The new copy ships in `en`, `zh-CN`, `ja-JP` and `es-ES`. + + Unchanged: every field, index, permission and the `revoke_api_key` / `restore_api_key` row actions. +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [30ce56e] +- Updated dependencies [2e0e451] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [77efc66] +- Updated dependencies [6422da5] +- Updated dependencies [8f4260f] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [4ffdebe] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [e246f06] +- Updated dependencies [a65a59b] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/platform-objects@18.0.0-next.3 + - @objectstack/core@18.0.0-next.3 + - @objectstack/types@18.0.0-next.3 + ## 18.0.0-next.2 ### Patch Changes diff --git a/packages/services/service-messaging/package.json b/packages/services/service-messaging/package.json index 8773168a82a..3dd70801cf0 100644 --- a/packages/services/service-messaging/package.json +++ b/packages/services/service-messaging/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-messaging", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "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 114cd1faa14..64f5c427c79 100644 --- a/packages/services/service-package/CHANGELOG.md +++ b/packages/services/service-package/CHANGELOG.md @@ -1,5 +1,109 @@ # @objectstack/service-package +## 18.0.0-next.3 + +### Minor Changes + +- c7168ac: Clause-②: yes (widening) + + fix(service-package,cloud-connection): a stored or installed package body converts forward under ADR-0087 D2 at the `sys_packages` boot hydration and at the marketplace install, and one refused `sys_packages` record no longer stops the hydration of the rest + + The registration door (`ObjectQL.convertPackageBodyForward`, `@objectstack/objectql`) already replays the versioned forward conversion for a package body that declares its `engines.protocol` range. Two more doors that load a body at rest now go through that same entry point, before the registry judges the body: + + - **`sys_packages` boot hydration** (`PackageServicePlugin.start`). A stored record whose range admits this runtime, built before a retirement (for example a `type: 'home'` page with `isDefault: true`), now hydrates converted, with the engine's named warning, where the registry used to refuse it. The stored row is not rewritten, so it converts again at every boot. + - A record the registry still refuses is reported **by name** at `error`, with the refusal attached, and the records after it keep hydrating. Before, the first refusal threw out of the whole loop to a `debug` line, and every durable package after it was absent from the registry with nothing saying so. + - `Hydrated N package(s) from sys_packages into registry` counts the records the registry accepted in that pass. + - The ADR-0087 D1 handshake still runs first: a record whose declared range excludes this runtime's major is refused by it, named, as before. + - **The marketplace install** (`MarketplaceInstallLocalPlugin`, install and boot rehydrate). The body is converted once per load, and that one converted body is what `manifest.register`, the handler binder, the side effects (translations, seed datasets) and the rehydrate's sample-data heal read. Before, `register` converted its own copy while the side readers read the raw ledger entry. The ledger keeps the body as installed. + + Nothing is narrowed. A body with no declared range (a manifest built in code) meets the registry's refusals exactly as before at both doors. The install primitive `protocol.installPackage` (`POST /api/v1/packages`) is authoring-side and stays strict: the registry judges the manifest as handed. + +### Patch Changes + +- 66d2757: feat(spec)!: every installed-package row declares where it came from, a closed `source: 'code' | 'marketplace' | 'authored'`, and the `GET /packages` rows declare the `writable` verdict they already served (#23972) + + Clause-②: yes (narrowing — the served row gains two declared keys, and the record schema, the served-row union and the registry's installPackage signature each refuse what they accepted before) + + **BREAKING** — three published accept sets narrow. `InstalledPackageSchema` refuses a row without `source`, or with a value outside `code` / `marketplace` / `authored`. The served-row union `InstalledPackageAtEitherStageSchema` refuses a row without a boolean `writable`. `SchemaRegistry.installPackage` and `EngineSchemaRegistryView.installPackage` take `source` as their second argument and refuse a call without it. The migration is below. None of it is mechanically convertible, because only the door that registers a package knows where it came from. + + **What is new on the wire.** `GET /api/v1/packages` and `GET /api/v1/packages/:id` serve two declared keys on every row: + + - `source` names the package's origin. `code` means the deployment's own code registered it (a `defineStack()` host, a plugin's bundled manifest, a platform package). `marketplace` means the marketplace install-local plugin installed it, and that holds after a restart too, when the plugin rehydrates it from its ledger. `authored` means it is a database-authored package (a Studio base, a duplicate, an HTTP install), including after the restart that rehydrates it from `sys_packages`. The door that registers the package stamps the value. No reader derives it. + - `writable` is the server's own writability verdict (ADR-0070 D2), served on these rows since #14375 and now declared. It is computed per read and never stored, so it is declared on the served row (`InstalledPackageAtEitherStageSchema`) and not on the registry record. + + FROM → TO for a reader of these rows: + + | you did | do instead | + | --- | --- | + | treated `writable: true` as authored, and looked up each non-writable row in the marketplace's installed list (the control plane's, else install-local's) to tell marketplace from code | read `row.source` | + | read the undeclared `writable` | read the declared `writable` (unchanged value) | + + The two keys are separate statements: `source` says where the package came from, and `writable` says what this server lets an org do to it. + + **`InstalledPackageSchema` requires `source`.** It is `InstalledPackageSourceSchema`, exported from `@objectstack/spec/kernel`. A row without it, or with a value outside the closed set, is refused at the `source` key. + + **API change for code that registers packages (`@objectstack/objectql`).** The registry records the origin and never infers it: + + | you wrote | write instead | + | --- | --- | + | `registry.installPackage(manifest)` | `registry.installPackage(manifest, 'code')` (or `'marketplace'` / `'authored'`, whichever is where the package came from) | + | `registry.installPackage(manifest, settings, scope)` | `registry.installPackage(manifest, source, settings, scope)` | + | `ctx.getService('manifest').register(artifact)` for a marketplace install | `ctx.getService('manifest').register(artifact, { source: 'marketplace' })` | + + `SchemaRegistry.installPackage` throws a `TypeError` naming the closed set when `source` is missing or unknown, before anything is recorded. `ObjectQL.registerApp(body, scope?, source?)` and the `manifest` service record `'code'` when no source is given. They accept `'code'` or `'marketplace'` and refuse `'authored'`. The `IObjectQLEngine` registry contract (`@objectstack/spec/contracts`) declares the same `installPackage(manifest, source, settings?)`. + + **The one-line fix:** pass the package's origin as `installPackage`'s second argument. + + Readers: objectui's Setup → Packages Source column and its `?source=` filter (it switches from the two-read join to `source` in its own follow-up), and the client SDK's `packages.list()` / `packages.get()` row types. + + +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [30ce56e] +- Updated dependencies [df2c551] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [6422da5] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [4ffdebe] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [e246f06] +- Updated dependencies [a65a59b] +- Updated dependencies [077b75f] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/metadata-core@18.0.0-next.3 + - @objectstack/core@18.0.0-next.3 + ## 18.0.0-next.2 ### Patch Changes diff --git a/packages/services/service-package/package.json b/packages/services/service-package/package.json index 3a406e6d992..7ffc5813ad2 100644 --- a/packages/services/service-package/package.json +++ b/packages/services/service-package/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-package", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "license": "Apache-2.0", "description": "Package management service for ObjectStack — publish, install, and manage packages", "type": "module", diff --git a/packages/services/service-print/CHANGELOG.md b/packages/services/service-print/CHANGELOG.md new file mode 100644 index 00000000000..a2ed3bf4852 --- /dev/null +++ b/packages/services/service-print/CHANGELOG.md @@ -0,0 +1,72 @@ +# @objectstack/service-print + +## 18.0.0-next.3 + +### Minor Changes + +- b15f8a1: feat(service-print): print a record's print page to a PDF in headless Chromium, as the requesting user, and archive it as a file attached to the record + + Clause-②: yes (widening) + + A new package, `@objectstack/service-print`. `PrintServicePlugin` registers the in-process `print` service (`IPrintService`): `status()` and `printRecordToFile({ app, object, recordId, page, filename? }, context)`. It adds no route, no metadata key and no error code. Compose it explicitly: + + ```ts + import { PrintServicePlugin } from '@objectstack/service-print'; + plugins: [new PrintServicePlugin()] + ``` + + - **One engine, headless Chromium, in one of two placements.** A local executable (`OS_PRINT_CHROMIUM_PATH`, option `chromiumPath`), or a remote browser's DevTools WebSocket endpoint (`OS_PRINT_BROWSER_ENDPOINT`, option `browserEndpoint`). Either one needs `OS_PRINT_CONSOLE_URL` (option `consoleUrl`): the console's URL as that browser reaches it, `/_console` included. Configuring both placements is refused. `playwright-core` is pinned exact on this package only. + - **Loud at startup.** `start()` probes the engine and logs its placement and Chromium version. An engine that is not configured, is misconfigured or does not answer is reported once, with the setting to fix, and `status()` says why. A remote endpoint is never logged beyond its scheme and host. + - **As the requesting user.** The page is rendered through a short-lived session minted for the caller's own principal by the auth service (`mintPrincipalSession`), and revoked in a `finally`. There is no system principal. + - **Refusals first, each with `code` and `status`, before any session is minted or browser started** (an engine that failed its last probe is re-probed only after them all): a caller that is not a human user acting for themselves (`PERMISSION_DENIED` / 403); an engine that is not ready (`SERVICE_UNAVAILABLE` / 503); an unknown page (`RESOURCE_NOT_FOUND` / 404); a page that is not the object's default record page or declares no `print` (`VALIDATION_ERROR` / 400); a record the caller cannot read (`RECORD_NOT_FOUND` / 404); an object without `enable: { files: true }` (`FILES_DISABLED` / 403). + - **The archive runs as the requesting user.** `@objectstack/service-storage`'s `file-attachments` service gains a caller-scoped face, `ICallerFileAttachmentService` (exported type), on the same slot and the same writer: `assertMayAttach(parentObject, parentId, context)` throws the insert's own refusal without writing (`PERMISSION_DENIED` / 403 when the caller may not create attachments, `ATTACHMENT_PARENT_ACCESS` / 403 when the attach rule refuses), and `attachFileAs(input, context)` writes one `sys_file` owned by the caller and one `sys_attachment` inserted under the caller's context, so every insert gate judges the caller and the row is stamped `uploaded_by` the caller. The print service asks `assertMayAttach` in its files step, before any session is minted or browser started. `attachFileAs` acts only in the caller's own organization: an `organizationId` other than the context's `tenantId` (or `null` when it names none) is refused `PERMISSION_DENIED` / 403. `attachFile` (the system verb) is unchanged. + + `@objectstack/spec`'s `llms.txt` counts the new package: the workspace publishes 72 packages. + +### Patch Changes + +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [abb6df0] +- Updated dependencies [0b1179f] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [6422da5] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [4ffdebe] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [e246f06] +- Updated dependencies [a65a59b] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/service-storage@18.0.0-next.3 + - @objectstack/core@18.0.0-next.3 diff --git a/packages/services/service-print/package.json b/packages/services/service-print/package.json index c7ac03373c2..317e5b6116d 100644 --- a/packages/services/service-print/package.json +++ b/packages/services/service-print/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-print", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "license": "Apache-2.0", "description": "Print Service for ObjectStack — renders a record's print page in headless Chromium under the requesting user's own principal and archives the PDF as a file attached to the record.", "type": "module", diff --git a/packages/services/service-queue/CHANGELOG.md b/packages/services/service-queue/CHANGELOG.md index 8a98324ead2..968038c0650 100644 --- a/packages/services/service-queue/CHANGELOG.md +++ b/packages/services/service-queue/CHANGELOG.md @@ -1,5 +1,57 @@ # @objectstack/service-queue +## 18.0.0-next.3 + +### Patch Changes + +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [30ce56e] +- Updated dependencies [2e0e451] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [77efc66] +- Updated dependencies [6422da5] +- Updated dependencies [8f4260f] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [4ffdebe] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [e246f06] +- Updated dependencies [a65a59b] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/platform-objects@18.0.0-next.3 + - @objectstack/core@18.0.0-next.3 + ## 18.0.0-next.2 ### Patch Changes diff --git a/packages/services/service-queue/package.json b/packages/services/service-queue/package.json index 69de4c14324..6677eb54473 100644 --- a/packages/services/service-queue/package.json +++ b/packages/services/service-queue/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-queue", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "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 3e989e5d2df..0b3372602e3 100644 --- a/packages/services/service-realtime/CHANGELOG.md +++ b/packages/services/service-realtime/CHANGELOG.md @@ -1,5 +1,57 @@ # @objectstack/service-realtime +## 18.0.0-next.3 + +### Patch Changes + +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [30ce56e] +- Updated dependencies [2e0e451] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [77efc66] +- Updated dependencies [6422da5] +- Updated dependencies [8f4260f] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [4ffdebe] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [e246f06] +- Updated dependencies [a65a59b] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/platform-objects@18.0.0-next.3 + - @objectstack/core@18.0.0-next.3 + ## 18.0.0-next.2 ### Patch Changes diff --git a/packages/services/service-realtime/package.json b/packages/services/service-realtime/package.json index da37dc7e4e7..abf54b2f2c8 100644 --- a/packages/services/service-realtime/package.json +++ b/packages/services/service-realtime/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-realtime", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "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 3afaebe9489..862c2d45576 100644 --- a/packages/services/service-settings/CHANGELOG.md +++ b/packages/services/service-settings/CHANGELOG.md @@ -1,5 +1,72 @@ # @objectstack/service-settings +## 18.0.0-next.3 + +### Patch Changes + +- 747c8e9: Clause-②: no + + fix(service-settings): the File Storage, Knowledge and AI settings pages show their warnings as `info_banner` callouts with `bannerSeverity: 'warning'`, not as a sentence inside a subtitle or a field's help text + + Four warnings were written into ordinary strings with a hand-typed warning glyph, so the admin read them as one more sentence of the page subtitle or of a toggle's help. Each one is now an `info_banner` row in the section it concerns, directly under the field it warns about: + + - `storage` — `adapter_switch_warning`, under the Adapter select: switching adapter does not migrate existing files. + - `knowledge` — `adapter_switch_warning`, under the Adapter select: switching adapter does not migrate existing indices. + - `knowledge` — `enforce_rls_warning`, under the Enforce RLS on search toggle: disabling it skips the vector-store leakage safeguard. + - `ai` — `log_prompts_warning`, under the Log full prompts toggle: full prompts may leak PII. + + The namespace description or field help of each keeps its non-warning text. The translations moved with them: in the `en`, `zh-CN`, `ja-JP` and `es-ES` bundles each warning sentence left the `description` / `help` copy and sits under `settings.NAMESPACE.banners.BANNER_ID.{title,text}`, with a translated title per banner. The existing Feature Flags `beta_notice` banner gained the same copies, so no shipped locale reads it in English any more. + + No setting key, value, default or save behaviour changes. A host that renders `info_banner` rows from the manifest literals draws the English title and text; translated copy is read through `resolveSettingsBannerTitle` / `resolveSettingsBannerText` from `@objectstack/spec/system`. +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [30ce56e] +- Updated dependencies [2e0e451] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [77efc66] +- Updated dependencies [6422da5] +- Updated dependencies [8f4260f] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [4ffdebe] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [e246f06] +- Updated dependencies [a65a59b] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/platform-objects@18.0.0-next.3 + - @objectstack/core@18.0.0-next.3 + - @objectstack/types@18.0.0-next.3 + ## 18.0.0-next.2 ### Minor Changes diff --git a/packages/services/service-settings/package.json b/packages/services/service-settings/package.json index c28e559c77b..1caa2682e75 100644 --- a/packages/services/service-settings/package.json +++ b/packages/services/service-settings/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-settings", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "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 cce0716fd12..1a7a247a2e2 100644 --- a/packages/services/service-sms/CHANGELOG.md +++ b/packages/services/service-sms/CHANGELOG.md @@ -1,5 +1,55 @@ # @objectstack/service-sms +## 18.0.0-next.3 + +### Patch Changes + +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [0cfd0c4] +- Updated dependencies [6422da5] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [4ffdebe] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [e246f06] +- Updated dependencies [a65a59b] +- Updated dependencies [ab3ba2c] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/plugin-auth@18.0.0-next.3 + - @objectstack/core@18.0.0-next.3 + ## 18.0.0-next.2 ### Patch Changes diff --git a/packages/services/service-sms/package.json b/packages/services/service-sms/package.json index f70d537d07a..9b1c6e05fa9 100644 --- a/packages/services/service-sms/package.json +++ b/packages/services/service-sms/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-sms", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "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 2c69b36ec1f..2c9faa2d31a 100644 --- a/packages/services/service-storage/CHANGELOG.md +++ b/packages/services/service-storage/CHANGELOG.md @@ -1,5 +1,111 @@ # @objectstack/service-storage +## 18.0.0-next.3 + +### Minor Changes + +- b15f8a1: feat(service-print): print a record's print page to a PDF in headless Chromium, as the requesting user, and archive it as a file attached to the record + + Clause-②: yes (widening) + + A new package, `@objectstack/service-print`. `PrintServicePlugin` registers the in-process `print` service (`IPrintService`): `status()` and `printRecordToFile({ app, object, recordId, page, filename? }, context)`. It adds no route, no metadata key and no error code. Compose it explicitly: + + ```ts + import { PrintServicePlugin } from '@objectstack/service-print'; + plugins: [new PrintServicePlugin()] + ``` + + - **One engine, headless Chromium, in one of two placements.** A local executable (`OS_PRINT_CHROMIUM_PATH`, option `chromiumPath`), or a remote browser's DevTools WebSocket endpoint (`OS_PRINT_BROWSER_ENDPOINT`, option `browserEndpoint`). Either one needs `OS_PRINT_CONSOLE_URL` (option `consoleUrl`): the console's URL as that browser reaches it, `/_console` included. Configuring both placements is refused. `playwright-core` is pinned exact on this package only. + - **Loud at startup.** `start()` probes the engine and logs its placement and Chromium version. An engine that is not configured, is misconfigured or does not answer is reported once, with the setting to fix, and `status()` says why. A remote endpoint is never logged beyond its scheme and host. + - **As the requesting user.** The page is rendered through a short-lived session minted for the caller's own principal by the auth service (`mintPrincipalSession`), and revoked in a `finally`. There is no system principal. + - **Refusals first, each with `code` and `status`, before any session is minted or browser started** (an engine that failed its last probe is re-probed only after them all): a caller that is not a human user acting for themselves (`PERMISSION_DENIED` / 403); an engine that is not ready (`SERVICE_UNAVAILABLE` / 503); an unknown page (`RESOURCE_NOT_FOUND` / 404); a page that is not the object's default record page or declares no `print` (`VALIDATION_ERROR` / 400); a record the caller cannot read (`RECORD_NOT_FOUND` / 404); an object without `enable: { files: true }` (`FILES_DISABLED` / 403). + - **The archive runs as the requesting user.** `@objectstack/service-storage`'s `file-attachments` service gains a caller-scoped face, `ICallerFileAttachmentService` (exported type), on the same slot and the same writer: `assertMayAttach(parentObject, parentId, context)` throws the insert's own refusal without writing (`PERMISSION_DENIED` / 403 when the caller may not create attachments, `ATTACHMENT_PARENT_ACCESS` / 403 when the attach rule refuses), and `attachFileAs(input, context)` writes one `sys_file` owned by the caller and one `sys_attachment` inserted under the caller's context, so every insert gate judges the caller and the row is stamped `uploaded_by` the caller. The print service asks `assertMayAttach` in its files step, before any session is minted or browser started. `attachFileAs` acts only in the caller's own organization: an `organizationId` other than the context's `tenantId` (or `null` when it names none) is refused `PERMISSION_DENIED` / 403. `attachFile` (the system verb) is unchanged. + + `@objectstack/spec`'s `llms.txt` counts the new package: the workspace publishes 72 packages. +- 0b1179f: **Shipped in 18.0.0-next.2**, whose CHANGELOG missed this entry (#23818); listed here one prerelease late. + + feat(service-storage): the `sys_attachment` read gate writes one `ReadableIds` leaf per parent object and does no pre-scan, so a broad read is judged over every row, not a 2,000-row window (ADR-0139 D4, D5) + + Clause-②: yes + + The gate serves an attachment only when the caller may read the record it is attached to. Until now each read first scanned at most 2,000 candidate rows as the system, then asked which of those rows' parent records the caller could read. Past that window the gate failed closed: the unscanned rows were left out, the list's `total` agreed with the short answer, and a warning said so. + + The gate now writes one branch per object the metadata registry holds (except `sys_attachment` itself): `{ parent_object: P, parent_id: { $in: ReadableIds.of(P) } }`. The engine composes the leaf from the security service's `canReadObject` and `getReadFilter` for `P`, and the SQL drivers evaluate it as one subquery against `P`'s table, inside their own tenant wall. A list, its `total` and a grouped count are each one statement over every row the caller may read. A parent object the caller may not read is answered with no rows, without reading it as the caller, so no denial is logged for it. + + What a reader sees: + + - A broad read of `sys_attachment` is served every attachment whose parent record the caller may read, with a true `total`, whatever the table size. An attachment whose parent the caller may not read, whose parent no longer exists, or that names no parent is still never served. A read scoped to one record (the attachments panel) is unchanged. + - Three classes still read through the bounded probe, chosen before any leaf is written: a delegated (on-behalf-of) caller; a parent an object-scoped read middleware narrows (for example `sys_comment`, `sys_comment_reaction`, `sys_activity` and `sys_audit_log`); and a parent bound to another datasource than `sys_attachment`. The whole read also stays on the probe when the security service offers no `canReadObject`, or when none is registered. + - A read whose probe finds no candidate row is now narrowed to no rows rather than left unfiltered. It served nothing before either. + - On a driver that does not compile the leaf (`driver-mongodb`), a read of `sys_attachment` now fails with `INTERNAL_ERROR` / 500 naming the driver and the leaf, instead of serving a capped list. The gate does not turn that refusal into an empty answer. This is ADR-0139's ruled behaviour until a pipeline form is measured for that driver. + + For a host that installs the gate itself: `installAttachmentReadVisibility(engine, logger, getAdmission?)` takes an optional third argument that resolves the security service on every read. `StorageServicePlugin` passes it. A call without it keeps every read on the bounded probe and says so once. + +### Patch Changes + +- abb6df0: Clause-②: no + + fix(cli): `os storage orphans`, the `os secret rewrap` dry run and the `os migrate summary-nulls` dry run say they write no row or table, and that opening a SQLite file can still switch its journal mode, instead of saying nothing was written + + **What was wrong.** Each of these runs boots through the SQL driver before it prints, and the driver's connect switches a SQLite file to the journal mode it asks for (WAL, unless `OS_DATABASE_SQLITE_JOURNAL_MODE=delete` is set in the command's own environment or the project's `.env`). Measured on a file still on a rollback journal, each run moved the header's journal-mode bytes (18 and 19, from 1,1 to 2,2) and its change counter, and changed no row or table, while printing "nothing was written". Nothing was lost. + + **What changed.** Each sentence below now claims no row or table. Where the run opened a SQLite file, the report's line is followed by the note the other read-only arms already carry: "Opening a SQLite file can still switch its journal mode (to WAL by default), which changes its header only." On PostgreSQL or MySQL the line ends without it. + + - `os storage orphans` (`formatStrandedOrphanInventory`, `@objectstack/service-storage`). FROM: "This pass is READ-ONLY: nothing was written, tombstoned or deleted." TO: "This pass is READ-ONLY: no row or table was written, tombstoned or deleted." and the note. + - `os secret rewrap` (the dry run's note, `REWRAP_NOTES.dryRun`, also in `--json` `report.notes`). FROM: "Dry run: every attributed row was opened, re-sealed and verified in memory, and nothing was written. Re-run with --apply to write." TO: "… and no row or table was written. Re-run with --apply to write." The note already follows the dry run's closing line on a SQLite file, so this sentence carries none. + - `os migrate summary-nulls` (the dry run that found NULL roll-ups, `formatSummaryBackfillReport`, `@objectstack/objectql`). FROM: "Dry run — nothing was written. Re-run with --apply to backfill." TO: "Dry run — no row or table was written. Re-run with --apply to backfill." and the note. + + **Unchanged.** No exit code, flag, prompt, refusal or export signature changes, and no `--json` document gains a field; the note is printed in the text output only. What the driver does on connect is unchanged: once a file is in the mode the driver asks for, these runs leave it byte-identical. +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [30ce56e] +- Updated dependencies [2e0e451] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [77efc66] +- Updated dependencies [6422da5] +- Updated dependencies [8f4260f] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [4ffdebe] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [e246f06] +- Updated dependencies [a65a59b] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/platform-objects@18.0.0-next.3 + - @objectstack/core@18.0.0-next.3 + - @objectstack/observability@18.0.0-next.3 + - @objectstack/types@18.0.0-next.3 + ## 18.0.0-next.2 ### Minor Changes diff --git a/packages/services/service-storage/package.json b/packages/services/service-storage/package.json index 5a6fe2edd04..ca22efe04e6 100644 --- a/packages/services/service-storage/package.json +++ b/packages/services/service-storage/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-storage", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "license": "Apache-2.0", "description": "Storage Service for ObjectStack — implements IStorageService with local filesystem and S3 adapter skeleton", "type": "module", diff --git a/packages/skills/CHANGELOG.md b/packages/skills/CHANGELOG.md index c21ae8ef49b..6018458aa4b 100644 --- a/packages/skills/CHANGELOG.md +++ b/packages/skills/CHANGELOG.md @@ -1,5 +1,30 @@ # @objectstack/skills +## 18.0.0-next.3 + +### Patch Changes + +- 46ca793: docs(skills): the `objectstack-ui` pages rule says what a page type, template and region name run (#23610) + + Clause-②: no + + `skills/objectstack-ui/rules/pages.md` taught `home` as the app's landing page, `utility` as a floating utility panel, templates `two-column` / `single-column` and regions `left_sidebar` / `right_sidebar`, and `kind: 'jsx'` as a live alias of `html` — none of which the console draws or the spec accepts. The rule now states the declared contract: `home` is the widest region page and not a landing (an app opens on its first navigation item; `isDefault` on it is refused), `utility` is a narrow inline region page with no floating or docked shell slot, `template` is one of `default`, `header-sidebar-main`, `three-column` or `dashboard` with the regions each one places (`sidebar` left, `aside` right; `full-width` retired), a region the template does not place or a second region for a filled slot is refused at its `name`, and `kind: 'jsx'` is refused with the prescription `html`. Skill text only; nothing an author writes changes. +- b711056: docs(skills): the `objectstack-ui` navigation rule states the derived record surface per flow — viewing routes to a page, creating or editing opens a modal bounded on desktop and full-screen on mobile, a light record uses a drawer (#23753) + + Clause-②: no + + `skills/objectstack-ui/rules/navigation.md`'s Surface bullet said a field-heavy object opens create/edit/detail as a full page and that mobile always pages. The platform's derivation (`deriveRecordFlowSurface` in `@objectstack/spec`) routes only the `view` flow to a page; the create, edit and child task flows never route — a field-heavy record, or any record on mobile, opens them in a modal that is bounded on desktop and full-screen on mobile, and a light record on desktop opens every flow in a drawer. The bullet now states that mapping and names the override only where the spec declares it: `navigation.mode` on the list view (`navigation` is not an object key). Text only; nothing an author writes changes. +- 2fdfb5d: docs(skills): the `objectstack-data` datasources rule states that `default` is host-reserved — the host connects it from `OS_DATABASE_URL`, and a stack that declares a datasource named `default` is refused (#23768) + + Clause-②: no + + `skills/objectstack-data/rules/datasources.md`'s opening paragraph read "Most apps need only the `default` datasource; declare more to read/write a separate or external database." "Declare more" implied that `default` is declared too, and a stack whose `datasources` collection carries an entry named `default` is refused at `defineStack`, `os validate`, publish and boot: the host declares and connects `default` itself. The sentence is replaced by the rule the spec and runtime hold: `default` is host-reserved, the host connects it from `OS_DATABASE_URL`, a stack that declares a datasource named `default` is refused, and a datasource is declared under its own name only to read/write a separate or external database. The added words are paid for inside the same file by deleting two restatements of the field reference: "`external` settings are required when `schemaMode !== 'managed'`" (the schema refuses the omission itself) and "Set `autoConnect: true` to force a live connection" (the auto-connect list two lines above already names it). No behaviour changes; nothing an author writes changes. +- 49700e4: docs(skills): the `objectstack-api` skill stops teaching an app to mount routes on `http.server` — an app declares `apis:`, computes in a flow, and registers code as a stack function or a connector (#23787) + + Clause-②: no + + `skills/objectstack-api/SKILL.md`'s decision table sent an endpoint that needs code to an imperative `http.server` mount on `kernel:ready`, and its "Code routes" section taught that mount. Ruling C′ item 2 on #23128 retired the mount as an app-author surface: the `http.server` service is the platform plugins' own door-building primitive. The table now points code at a `type: 'flow'` endpoint whose `script` node runs a stack function (`defineStack({ functions })`) or whose `connector_action` node calls a declared connector, names the inbound `type: 'api'` flow hook for a webhook signed with ObjectStack's own `x-objectstack-signature`, and states the three cases that have no app surface today (a third-party callback verified by its own signature over the raw body, a streaming response, a protocol the platform does not speak) instead of inventing one. The "Code routes" section is deleted, and eval 3 expects that answer. Skill text only; nothing an author writes changes. + ## 18.0.0-next.2 ### Patch Changes diff --git a/packages/skills/package.json b/packages/skills/package.json index c9ea66c60ec..e47cdf25758 100644 --- a/packages/skills/package.json +++ b/packages/skills/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/skills", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "description": "The published ObjectStack skills catalog \u2014 the repository's skills/** tree, shipped at the version of the @objectstack/* packages it teaches.", "license": "Apache-2.0", "homepage": "https://github.com/objectstack-ai/objectstack/tree/main/packages/skills", diff --git a/packages/spec/CHANGELOG.md b/packages/spec/CHANGELOG.md index b33ceca1d0b..ad32c80d1ce 100644 --- a/packages/spec/CHANGELOG.md +++ b/packages/spec/CHANGELOG.md @@ -1,5 +1,734 @@ # @objectstack/spec +## 18.0.0-next.3 + +### Minor Changes + +- a35f7a6: feat(spec)!: `checkPageComponentViewData` is deleted; a page component's `data` prop naming the retired view-level `api` source is refused by the component-props rule alone, at the CLI doors and the metadata save door (#23447) + + **BREAKING** — `checkPageComponentViewData` is no longer exported from `@objectstack/spec/ui`, and `PageSchema` no longer carries it. Since the component-props rule (`validateComponentProps`, `@objectstack/lint`) runs on a `page` write at the metadata save door, it and the page-parse check judged the same binding at the same door: one operation with two implementations. The governed side stays. The props rule parses the component's `properties` against the type's `ComponentPropsMap` row, and that row's `data` is `ViewDataSchema`, so it raises the retirement's own prescription on the same four types (`object-grid`, `object-map`, `object-gantt`, `object-tree`), at the same positions: regions, `page:tabs` items, nested containers and slots. + + Clause-②: yes (narrowing) + + What narrows: the published export leaves `@objectstack/spec/ui`. What `PageSchema` accepts widens by this one shape, a component `data` naming the retired provider, which the props rule then refuses at every door that runs it. + + ## FROM → TO + + | you wrote | write instead | + | --- | --- | + | `import { checkPageComponentViewData } from '@objectstack/spec/ui'`, and `.superRefine(checkPageComponentViewData)` on a mirror built from `PageSchema.shape` | nothing on the mirror: a page component's `properties` is judged by `validateComponentProps` from `@objectstack/lint` (or `runAuthoringRules`), against the stack that carries the page | + | a page component's `properties.data` with `provider: 'api'` | unchanged from the step-18 retirement: `provider: 'object'` with `object`, or `provider: 'value'` with `items`; an endpoint read moves onto the component's `dataSource: { provider: 'api', read: { url: '/api/...' } }` | + + **The one-line fix:** drop the import and the re-attachment; run the props rule where you need the verdict. + + **Which doors refuse the binding now, measured:** + + | door | before | after | + | --- | --- | --- | + | `definePage()`, `defineStack({ pages })`, `PageSchema.safeParse` | refused, `custom` at `…properties.data.provider` | accepted (these doors run no lint rule) | + | `os validate`, `os build` | refused inside `defineStack` | refused by the props rule: `component-props-invalid` at `pages[0].regions[0].components[0].properties.data.provider`, with the prescription | + | metadata save door (`PUT /api/v1/meta/page/:name`, the Studio, MCP) | `422 INVALID_METADATA`, spec-parse issue `custom` | `422 INVALID_METADATA`, rule `component-props-invalid` at the same component, with the prescription; nothing stored | + | boot (`loadMetaFromDb`) | `invalid: 1`, registered under `[metadata_spec_invalid]` | `invalid: 0`, registered, no warning | + | read (`getMetaItem`) | served, `_diagnostics.valid: false` with the refusal | served as stored, `_diagnostics.valid: true` | + + **A page already stored with such a component** still loads and is served as stored, and no longer carries a read diagnostic for the binding; its next save is refused with 422 by the props rule. `os migrate meta --stored` still lists it under the step-18 entry `view-data-api-provider-retired`, and that entry's fate text now says so. + + +- f606ba2: feat(spec)!: a job `retryPolicy` block whose `maxRetries` is omitted or 0 is refused, and `retry-policy-converged` leaves the load path + + Clause-②: yes (narrowing) + + **BREAKING (authoring):** `maxRetries` has defaulted to 0 since 17, so a `job.retryPolicy` block that omits it, or writes 0, buys no retry: the job runs once. Through 17 the loader hid that by writing the pre-17 `maxRetries: 3` and `backoffMultiplier: 2` into such a block with a notice. At 18 the ADR-0087 D2 conversion `retry-policy-converged` is retired from the load path (`retiredAfter: '17.7.0'`), and the job parse refuses the block at `retryPolicy.maxRetries`, at every door that parses an authored job (`defineStack`, `os validate`, `os build`, and `os lint` when the config calls it): "A retry block with no attempts is no retry block: state `maxRetries` ≥ 1, for example 3, or delete the block." The rule is the job's, the same one `flow.errorHandling` applies to `strategy: 'retry'`; the shared `RetryPolicySchema` is unchanged, so a `try_catch` node's `retry` region is judged as before. + + Migration: + + - `retryPolicy: {}` or `retryPolicy: { backoffMs: 5000 }` → `retryPolicy: { maxRetries: 3, backoffMs: 5000 }` (3 is the count such a block ran with before 17), or delete the block if the job should run once. + - `retryPolicy: { maxRetries: 0, … }` → delete the block: a job with no `retryPolicy` is a single attempt. + + What an author who still writes it sees: `defineStack` throws `STACK_SCHEMA_INVALID` (422) with one issue at `jobs.N.retryPolicy.maxRetries` carrying the prescription above, and `os validate` prints the same issue. Nothing is rewritten silently any more. + + Not refused: a block that states `maxRetries` and omits `backoffMultiplier`. It keeps the declared default of 1, a flat delay, where the loader used to write 2. State `backoffMultiplier: 2` to keep the old exponential curve. + + Run `os migrate meta --from 16` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. For such a block the edit writes both pre-17 numbers (`maxRetries: 3`, `backoffMultiplier: 2`). The D3 entry `job-retry-policy-without-attempts-refused` states the decision left to you: keep the written count, or delete the block. + + The flow half of the conversion (`retryDelayMs` → `backoffMs` on `flow.errorHandling` and a `try_catch` node's `retry`) needed nothing more: both surfaces already refuse `retryDelayMs` with the rename to `backoffMs`. + + Stored metadata needs no edit. Stored-row rehydration, the automation engine's flow rehydration, the metadata save door's flow fallback and the artifact-ingestion door inside its window still replay the conversion, so a job row stored without a count keeps loading with the three retries it ran with. + + `@objectstack/service-job`: a comment-only change in `runWithPolicy`, whose `maxRetries` default is now reached only by a caller that builds the options by hand. + + +- 105902f: feat(spec)!: retire the view form's `visibleOn` and the page component's `visibility` as keys; their ADR-0089 renames leave the load path + + Clause-②: yes (narrowing) + + **BREAKING (authoring):** ADR-0089 made `visibleWhen` the one conditional-visibility key, and through 17 the old spellings were declared `[DEPRECATED]` keys that the parse folded onto `visibleWhen` without a word: `visibleOn` on a form view's fields and sections, and `visibility` on a page component. At 18 all three positions carry a `retiredKey()` tombstone naming `visibleWhen`, the parse-time fold is gone, and the two ADR-0087 D2 conversions `view-visibleOn-to-visibleWhen` and `page-component-visibility-to-visibleWhen` (protocol 15) are retired from the load path (`retiredAfter: '17.7.0'`). + + ## FROM → TO + + | you wrote (17.7 and earlier) | write instead | + | --- | --- | + | `visibleOn: "…"` on a form field (`view.form.sections[].fields[]`) | `visibleWhen: "…"` | + | `visibleOn: "…"` on a form section (`view.form.sections[]`) | `visibleWhen: "…"` | + | `visibility: "…"` on a page component (`page.components[]`, slots and nested containers included) | `visibleWhen: "…"` | + + **The one-line fix:** rename the key to `visibleWhen`; the CEL predicate is unchanged. Where a node wrote both keys, `visibleWhen` was the one in effect: delete the old key. + + **What an author who still writes it sees.** `tsc` fails at a typed authoring site (`FormFieldInput`, `FormSection`, `PageComponent` type the key as retired), and the parse refuses it by name at its own path — `defineStack` throws `STACK_SCHEMA_INVALID` (422), `os validate` / `os build` print the issue, and the metadata save door answers 422: + + > `page.components[].visibility` was removed in @objectstack/spec 18 (ADR-0089 D2) — it was the page component's deprecated spelling of the conditional-visibility predicate, which the parse folded into `visibleWhen` until this release. Use `visibleWhen`. Rename the key; the value (a CEL predicate) is unchanged. Where a component writes both, `visibleWhen` is the one that was in effect: delete `visibility`. + + **No tool makes the rename.** Both conversions belong to protocol 15, below the migration floor, so `os migrate meta --from 17` applies no edit for either key and `--write` writes none: it prints each written key among the refusals the migrated stack still has, and lists the D3 entry `ui-visibility-alias-keys-retired` as the hand edit. + + **Stored metadata needs no edit.** Stored-row rehydration and the artifact-ingestion door inside its window still replay both conversions, so a `sys_metadata` view or page row written with the old key keeps loading with `visibleWhen`. A script that saves a view or page through the metadata API with the old key is refused and needs the same rename. + + ## The retirement kit + + - **Tombstones** on `FormFieldSchema`, `FormSectionSchema` and `PageComponentSchema`; `RETIRED_KEYS_BY_MAJOR[18]`: `ui/FormField:visibleOn`, `ui/FormSection:visibleOn`, `ui/PageComponent:visibility`; the authorable-surface rows read `[RETIRED]`. + - **The fold is gone.** The three shapes are plain closed objects again (no `.transform` pipe): a section's group-reference rule no longer lists `visibleOn` (the tombstone answers it first), and a slotted page node's `type` refusal now arrives at its own path instead of nested in an `invalid_union`. + - **The D3 entry `ui-visibility-alias-keys-retired`**, one for both keys. + - **No deprecation window**, per the project's startup-stage posture. + + `@objectstack/lint`: the visibility-predicate rules (`visibility-root-mislayered`, `visibility-bare-identifier`, `visibility-predicate-syntax`) and the predicate-path rules read `visibleWhen` only. `os validate`, `os build`, `os verify`, `defineStack` and the metadata save door parse first and refuse either retired key by name, so a body they judge cannot carry one. `os lint` is the one door that hands these rules an unparsed stack: there a predicate written under a retired key is not judged, by design — the parsing doors refuse the key itself, and `os lint` reports no refused shape. + + `@objectstack/metadata-core`: `detectUnboundFormViewPredicateRoots` reads `visibleWhen` only. Its caller scans an artifact after `applyArtifactForwardConversions`, which renames a legacy `visibleOn` first. + + Not part of it: `normalizeVisibleWhen` and `VISIBILITY_ALIAS_KEYS` stay exported (no schema here calls them any more); objectui's renderer reads of the old keys leave on the objectui side. + + ⚠️ **The out-of-repo consumer population is NOT MEASURED.** `@objectstack/spec` is published, so this is breaking for consumers no telemetry was consulted for. + + +- f901746: feat(spec)!: the retired callout node types `http_request`, `http_call` and `webhook` are reserved inside the open flow-node namespace; `flow-node-http-callout-rename` leaves the load path + + Clause-②: yes (narrowing) + + **BREAKING (authoring):** ADR-0018 unified the three outbound callout node types `http_request`, `http_call` and `webhook` into the one `http` node, and through 17 the loader rewrote an authored node typed with one of them to `http` with a notice. A flow node's `type` is an open namespace (a plugin registers its own node types), so nothing refused the old names. At 18 they are reserved: the flow parse refuses each as a node's `type` by name, the automation engine refuses to register a node executor under one, and the ADR-0087 D2 conversion `flow-node-http-callout-rename` (protocol 11) is retired from the load path (`retiredAfter: '17.7.0'`). A wait node's `waitEventConfig.eventType: 'webhook'` is a different field and is not affected. + + ## FROM → TO + + | you wrote (17.7 and earlier) | write instead | + | --- | --- | + | a flow node `type: 'http_request'` | `type: 'http'` | + | a flow node `type: 'http_call'` | `type: 'http'` | + | a flow node `type: 'webhook'` | `type: 'http'` | + | a plugin's `engine.registerNodeExecutor({ type: 'webhook', … })` (or either other name) | a type of your own, e.g. `'acme_webhook'` | + + **The one-line fix:** change the node's `type` to `'http'`; the node's `id`, `label` and `config` stay as they are (the conversion never touched `config`, so the `http` contract judges it as written). + + **What an author who still writes it sees.** The parse refuses the node at its own `type` path — a top-level node and a node inside a `loop`, `parallel` or `try_catch` region alike. `defineStack` throws `STACK_SCHEMA_INVALID` (422), `os validate`, `os build` and `os lint` (whose config calls `defineStack`) print the issue, and the metadata save door answers a NEW flow with `422 INVALID_METADATA` at the node's `type`, with or without an automation service, and stores nothing: + + > Flow node `type: 'http_request'` was removed in @objectstack/spec 18 (ADR-0087 D2) — it is one of the three callout node types ADR-0018 unified into `http`, the loader rewrote it to `http` until this release, and the name stays reserved inside the open node namespace so no plugin can claim it (ADR-0018). Use `type: 'http'`. Change the value; the conversion never touched the node's `config`, so the `http` contract judges it as written. + + The refusal is a `pattern` on `FlowNodeSchema.type`, so the published JSON Schema carries it too. + + **`@objectstack/service-automation`:** `AutomationEngine.registerNodeExecutor` (and `registerNodeAlias`, which calls it) throws when the executor's `type` or its descriptor's `type` is one of the three names, and registers nothing. No node could ever reach such an executor: an authored node with the name is refused at parse, and a stored one is rewritten to `http` before it parses. A plugin that registered its own executor under one of these names fails at that call and needs a type of its own. + + **No tool makes the rename.** The conversion belongs to protocol 11, below the migration floor, so `objectstack migrate meta` applies no edit for it and `--write` writes none: it prints each such node among the refusals the migrated stack still has, at `flows.N.nodes.M.type`, and lists the D3 entry `flow-node-http-callout-types-reserved` as the hand edit. + + **Stored metadata needs no edit.** Stored-row rehydration, the automation engine's flow rehydration and the artifact-ingestion door inside its window still replay the conversion, so a `sys_metadata` flow row written with an old name keeps loading as an `http` node. The rewrite there is now unconditional: until 18 it skipped a node whose old name a live executor owned and reported a conflict, and the engine no longer lets such an executor exist. + + ## The retirement kit + + - **The reserved-name table** `FLOW_RESERVED_NODE_TYPES` and its refusal `reservedFlowNodeTypeRefusal` (`@objectstack/spec/automation`), read by the flow parse, the engine's registration refusal and the conversion itself. + - **The conversion** is `retiredFromLoadPath: true`, `retiredAfter: '17.7.0'`; its ADR-0078 conflict guard is gone. + - **The D3 entry `flow-node-http-callout-types-reserved`.** + - **No deprecation window**, per the project's startup-stage posture. + + ⚠️ **The out-of-repo consumer population is NOT MEASURED.** `@objectstack/spec` and `@objectstack/service-automation` are published, so this is breaking for consumers no telemetry was consulted for. + + +- a69a40a: **Shipped in 18.0.0-next.2**, whose CHANGELOG missed this entry (#23818); listed here one prerelease late. + + feat(spec)!: a page declares nothing the console does not honour — `isDefault: true` is refused off a record page bound to an object, `template` closes to the registered layouts, and a region the template does not place is refused + + Clause-②: yes (narrowing) + + **BREAKING (authoring):** two accept sets of `PageSchema` narrow to what the console runs, and the `PageTypeSchema` text stops promising what it does not. + + | Writing | Before | Now | + | :--- | :--- | :--- | + | `isDefault: true` on a `home`, `app`, `utility` or `list` page, or on a record page with no `object` | accepted, stored and served; nothing read it | refused at `isDefault` — by `definePage`, `defineStack` (so `os validate` and `os build`, `STACK_SCHEMA_INVALID` 422), the metadata save door (`422 INVALID_METADATA`) and package registration (`SchemaRegistry.installPackage` / `registerItem`, `422 INVALID_METADATA`) | + | `template: 'full-width'` | accepted; drawn exactly as `default` | refused by name: "Page `template: 'full-width'` was removed in @objectstack/spec 18 … Use `template: 'default'`" | + | any other `template` outside `default`, `header-sidebar-main`, `three-column`, `dashboard` (`two-column`, `single-column`, …) | accepted; drawn as the page type's default layout | refused at `template` | + | on a `kind: 'full'` page, a region its template does not place (`left_sidebar`, `right_sidebar`, `content`, …) | accepted; stacked under `main` at full width, its `width` ignored | refused at `regions.N.name`, naming the template's slots and the rename | + | on a `kind: 'full'` page, a second region filling a slot an earlier one fills (two `main` regions; on `dashboard`, a second `header` or `footer`) | accepted; not drawn at all — the console draws the first region of a name and leaves that name out of the rest | refused at `regions.N.name`, naming the region that already fills the slot | + + Migration: + + - **`isDefault` on a non-record page** → delete it. A page does not choose where an app opens: an app's landing page is its **first navigation item** (by `order`), so to land on the page put its entry first in the app's `navigation`; the root landing follows the app's own `isDefault`. A record page meant as an object's default record page binds that object with `object`. + - **`template: 'full-width'`** → `template: 'default'` (or delete the key — `default` is the default). The page renders exactly as before. + - **A `template` held in a widened `string`** (a shared base object, `{ template: 'default' }` spread into `definePage`) no longer type-checks against the closed set → `template: 'default' as const`. + - **An unregistered template** → one of `default` (header, sidebar, main, aside, footer), `header-sidebar-main` (header, sidebar, main), `three-column` (header, sidebar, main, aside, footer) or `dashboard` (header, footer, and every other region a grid cell). + - **An unplaced region** → name it for the slot it fills: `left_sidebar` → `sidebar`, `right_sidebar` → `aside`, `content` → `main`; or choose `dashboard` when the regions are a grid. Names match case-insensitively, as the console's do. `slotted`, `html` and `react` pages are not judged on region names. + - **A second region for a filled slot** → move its components into the first region of that name, or name it for an unfilled slot. On `dashboard`, two regions of any other one name stay two grid cells and are accepted. + + The template set and each template's slots are now published: `PageTemplateSchema`, `PAGE_TEMPLATE_SLOTS` and `PAGE_TEMPLATE_OPEN_GRID` (`@objectstack/spec/ui`), with the two checks `checkPageDefaultPlacement` and `checkPageRegionPlacement` and the one refusal sentence `misplacedDefaultPageRefusal` that the parse and the registry share. + + `os migrate meta` lists both as hand edits — the D3 entries `page-is-default-off-record-page-refused` and `page-template-closed-region-unplaced-refused`. No D2 conversion rewrites them: where an app opens, and which slot a region fills, are the author's calls. + + **Stored metadata keeps loading.** No conversion touches these shapes and the read path does not refuse them, so a `sys_metadata` page row already stored with a non-record `isDefault: true`, `template: 'full-width'` or an unplaced region is served unchanged and renders as before (a stored row hydrates into the registry without a package, so the registry does not judge it either). Saving such a row again through the metadata API is refused until it is fixed. + + The text, without a contract change: `PageTypeSchema`'s docblock and enum comments now say that `home`, `app` and `utility` are the same region layout at three content widths — `home` is not a landing declaration, and `utility` is an ordinary inline page (the console has no floating or docked shell slot for it); `utility` stays a page type, read by the print door and the page-type width. The `print`-on-`utility` refusal no longer calls a utility page "a floating panel". + + `@objectstack/objectql`: the registry refuses `isDefault: true` on a non-record page a package registers (`MisplacedDefaultPageError`, `INVALID_METADATA` 422), at the package door and at the package-bound item seam, with the page parse's sentence. + + +- 0a052e6: **Shipped in 18.0.0-next.2**, whose CHANGELOG missed this entry (#23818); listed here one prerelease late. + + feat(spec)!: a stack's `datasources` collection refuses a datasource named `default`, the name the host reserves for its primary datasource + + Clause-②: no (narrowing) + + + + **BREAKING**: an accept-set narrowing on one collection of the stack definition, shipped as `minor` under the launch-window convention for accept-set narrowings. No config that boots today is newly refused: every boot already refused this one. + + `default` is the host's primary datasource. The runtime declares and connects it itself, and the app plugin refuses a stack that declares it in every boot posture (with `OS_DATABASE_URL`, with `--dev`, and on the standalone path): + + > app 'APP_ID' declares a datasource named 'default' — that name is reserved for the host's primary datasource. + + `defineStack` and `os validate` accepted the same stack, so its author met that refusal only after the boot's earlier plugins had started. The stack's `datasources` collection now refuses the entry at authoring, located at `datasources.N.name` (the map form `datasources: { default: { … } }` is refused the same way), with the runtime's sentence and its remedy: + + > This stack declares a datasource named 'default' — that name is reserved for the host's primary datasource, and every boot refuses a stack that declares it. To give the project its own primary database, set OS_DATABASE_URL and omit this entry. To add a datasource of the stack's own, declare it under another name (e.g. 'app_primary') and route objects to it explicitly (an object's `datasource`, or a `datasourceMapping` rule). + + `DatasourceSchema` is unchanged: it also documents the host's own `default` row, which keeps parsing. + + ### FROM → TO + + | before | what to write instead | + | --- | --- | + | `datasources: [{ name: 'default', driver: 'sqlite', config: { filename } }]`, meant as the project's database | no entry; set `OS_DATABASE_URL` (e.g. `file:./data/app.db`) and the host connects it as `default` | + | the same entry, meant as a datasource of the stack's own | `datasources: [{ name: 'app_primary', … }]`, and route the objects that use it: `datasource: 'app_primary'` on the object, or a `datasourceMapping` rule naming `app_primary` | + + **The one-line fix: drop the `default` entry and set `OS_DATABASE_URL`, or rename it and route objects to it.** `os migrate meta --from 17` lists the D3 entry `stack-datasource-host-reserved-name-refused`; the rewrite is by hand, because which of the two the entry meant is the author's call. + + **Measured producers.** None in this repository (examples, `create-objectstack`, skills, docs); the one instance found was an agent-written measurement fixture. +- ae0ca66: feat(spec)!: retire `page:header` `showStar` — the follow-star switch the reference console no longer draws (#23650) + + **BREAKING** — `showStar` on a `page:header` component (`PageHeaderProps`) is now refused. Since objectui#12427 the header's record chip draws the follow star only where the host wires a favorites toggle into the record context, and the reference console wires none: the record's favorites entry is the header's ⋯ menu. So `showStar: true` (the default) drew nothing and `showStar: false` hid nothing. Delete the key; the ⋯ menu's favorites entry is unchanged, and metadata does not control it. + + Clause-②: no (narrowing) + + Measured before removal, at `140238a47`: no producer. No example app, skill, doc or package source in this repository authors the key outside the spec's own schema and tests, and neither does any app or example in objectui at the pinned `f96f771140f1`. + + ## FROM → TO + + | you wrote (17.7 and earlier) | write instead | + | --- | --- | + | `showStar: true` or `showStar: false` in a `page:header`'s `properties` | nothing — delete the key; the record's favorites entry is the header's ⋯ menu | + + **The one-line fix:** delete `showStar` from every `page:header`. + + **What an author who still writes it sees.** `tsc` fails at a typed authoring site (`PageHeaderProps` types the key as retired), and `os validate`, `os build` and `os lint` refuse it as an `error` (`component-props-invalid`) at its own path with the prescription: + + > `page:header` property `showStar` was removed in @objectstack/spec 18 (ADR-0087 D2) — the reference console draws no star beside the record title, so the key switched nothing: the record's favorites entry is the header's ⋯ menu, and metadata does not control it. Delete the key, whether it was `true` or `false`; the ⋯ menu's favorites entry is unchanged. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. + + A page's own parse keeps its component `properties` as an open bag, so an existing page carrying the key is never refused at load. + + ## The retirement kit + + - **A `retiredKey()` tombstone** on `PageHeaderProps`, a `strictObject`, beside the `icon` and `breadcrumb` this row lost before it. `RETIRED_KEYS_BY_MAJOR[18]`: `ui/PageHeaderProps:showStar`. The authorable-surface row reads `[RETIRED]`, and the `showStar = true` default leaves the defaults baseline. + - **The D2 conversion `page-header-show-star-removed`** (protocol 18, retired from the load path) deletes the key from every `page:header` — region-level, nested in a container's `children`, and in a slotted page's named slot — `true` and `false` alike, one notice per header, as a lossless delete. It is scoped by component `type`: the same key name on another component is left alone. + - **The D3 entry `page-header-show-star-retired`** records the one case the conversion cannot decide: a host of your own that wires the favorites toggle now draws its star on every record header, a header that said `false` included. + - **No deprecation window**, per the project's startup-stage posture. + + **Not part of it:** the record context's favorites hook in the published objectui component library (`onToggleFavorite`) stays — this removes the metadata switch, not the host hook. The renderer's read of the key leaves on the objectui side. + + ⚠️ **The out-of-repo consumer population is NOT MEASURED.** `@objectstack/spec` is published, so this is breaking for consumers no telemetry was consulted for. + + +- 8776119: **Shipped in 18.0.0-next.2**, whose CHANGELOG missed this entry (#23818); listed here one prerelease late. + + feat(spec): `referenceDeletedWhen` beside `referenceVia` marks a polymorphic pointer's target as platform-deleted + + Clause-②: yes + + `FieldSchema` gains one optional key beside `referenceVia`: `referenceDeletedWhen: { field, value }`. On rows where the sibling `select` field `field` holds the option `value`, the record the pointer names was deleted by the platform's own assertion, and a renderer draws a tombstone from that option's own label instead of a link. Example: `record_id: Field.text({ referenceVia: 'object_name', referenceDeletedWhen: { field: 'cancel_reason', value: 'record_deleted' } })`. + + Any other shape is refused at parse, naming the field and the option: + + - the key on a field that declares no `referenceVia` (`FieldSchema`), or an unknown key inside it; + - a `field` that is the declaring field itself, is not declared on the object, is not a `select`, is a `multiple: true` select, or declares no inline `options` (a `picklist`-backed select); a `value` that is not one of the sibling's option values (`ObjectSchema`, at every door: `ObjectSchema.create()`, `defineStack`, raw `.parse()` and the metadata save door). + + Nothing that parsed before is refused: the key is new. Its liveness row is `planned` until the renderer that reads it ships. + + The other packages classify the new key where every field key is classified: + + - **`@objectstack/metadata-core`.** `FIELD_REFERENCE_POSITIONS` deletes a served field's `referenceDeletedWhen` when its `field` names a field the caller cannot read (field-level security); `value` is an option value and never a field name. + - **`@objectstack/driver-sql`.** `FIELD_KEY_STORAGE_CLASS` classifies it `presentation`: no DDL reads it. + - **`@objectstack/platform-objects`.** The field form's new `referenceDeletedWhen` row carries its labels and help text in zh-CN, ja-JP and es-ES. +- 0a168a3: **Shipped in 18.0.0-next.2**, whose CHANGELOG missed this entry (#23818); listed here one prerelease late. + + feat(spec)!: a form section's `columns` has no default — undeclared, the density is the renderer's (by available width); declared, it is the author's ceiling + + Clause-②: no + + **BREAKING:** a parsed form section without `columns` no longer carries `columns: 1`. `FormSectionSchema.columns` drops `.default(1)`: the key stays optional with values `1`–`4`, and the string spellings `'1'`–`'4'` still parse to the number. + + | A form section authored as | Parsed before | Parsed now | + |:---|:---|:---| + | `{ fields: [...] }` (no `columns`) | `columns: 1` | no `columns` key | + | `{ columns: 2, fields: [...] }` | `columns: 2` | `columns: 2` (unchanged) | + | `{ columns: '3', fields: [...] }` | `columns: 3` | `columns: 3` (unchanged) | + + The two meanings, stated on the schema's describe and docblock: + + - **Declared** — the author's ceiling: at most that many fields per row where the container is wide enough. A renderer collapses it on narrow containers. + - **Undeclared** — the renderer's choice, by available width. After parse a renderer can now tell "the author said nothing" apart from "the author asked for one column", which the old default erased. No pixel or breakpoint value enters the protocol. + + Nothing to rewrite: every input accepted before is accepted now with the same declared value, and none refused before is accepted. A section that must stay single-column on a wide container declares `columns: 1`. A consumer that reads a parsed section's `columns` treats an absent key as "the renderer decides" rather than as `1`. `FormViewSchema.columns` (the form body) was already optional with no default and is unchanged. + + +- 97b8e7a: `record:json` is a declared page component type — one key of the bound record drawn as indented JSON behind a required, fail-closed `requiredPermissions` gate — with a strict `ComponentPropsMap` row + + Clause-②: yes + + - **New `PageComponentType` member:** `record:json`, in the reserved `record` namespace. One node draws the value under one key of the bound record as indented JSON, in a panel that starts collapsed, with a copy control. Declared spec first: objectui draws it and `@objectstack/plugin-approvals` places it on the approval request page after this release. + - **Its `ComponentPropsMap` row (`RecordJsonProps`) is strict and has three keys:** + - `field` (required) — the key to draw, in the snake_case machine-name grammar. It may be a declared field or a key the record's read route serves beside the declared fields (`payload` on a `sys_approval_request` row), so it is not checked against the object's fields: a misspelled `field` draws nothing, and no publish check reports it. + - `label` (optional) — the collapsed panel's summary line, a string or an inline locale map. + - `requiredPermissions` (required, at least one non-empty name) — capabilities the user must all hold. It fails CLOSED: a client that cannot resolve the user's capabilities draws nothing, and so does a user missing any of them. Presentation only — it authorises nothing, and the read route still serves the value. + - **The record blocks' own `requiredPermissions` is unchanged.** On `record:details`, `record:highlights`, `record:related_list` and `record:quick_actions` it stays optional and fail-open; the same key name on `record:json` is a separate declaration with its own describe. + - **Refusals.** Any other key in `properties` is refused by name (`component-props-unknown-key` at `os validate` / `os build` / `os lint`); a node with no `field` or no `requiredPermissions`, or with `requiredPermissions: []`, is refused at parse. Inside a page that declares `print`, the type is refused with its own reason (a collapsed panel behind a per-viewer gate). + - **Consumers.** A consumer that derives coverage from `PageComponentType.options` or `ComponentPropsMap` — a designer palette, a renderer registry — sees one more member to classify. +- 9cb9d95: feat(spec,plugin-auth): a short-lived principal-session mint, `IAuthService.mintPrincipalSession`, implemented by `AuthManager` + + Clause-②: yes (widening) + + An in-process service can now act as the requesting user through a door that only accepts a session, such as a headless browser rendering a page the user may see, under the user's own principal and never a system one. Nothing is removed and no HTTP route is added. + + - **`@objectstack/spec`** (`contracts`): an optional `IAuthService` member, `mintPrincipalSession(context, options?)`, its types `PrincipalSession`, `PrincipalSessionCookie` and `PrincipalSessionMintOptions`, and the constant `PRINCIPAL_SESSION_MAX_TTL_SECONDS` (`120`). The member is optional, so every existing `IAuthService` implementation compiles unchanged. + - **`@objectstack/plugin-auth`**: `AuthManager.mintPrincipalSession(context, options?)`. + + What the mint guarantees: + + - **Human principals only, refused before anything is minted.** A context with no user, a system context, or a `principalKind` other than `human` gets `PERMISSION_DENIED` / `403`. So does a user row that is missing, the system user or banned. A user an authentication policy holds gets `403` with the policy's own code (`MFA_REQUIRED`, `PASSWORD_EXPIRED`, `PASSWORD_RESET_REQUIRED`), whether the posture rides on the context or is computed at the call. A `ttlSeconds` outside 1 to 120 gets `VALIDATION_ERROR` / `400`: it is refused, never clamped. + - **Short and never renewed.** The session lives at most 120 seconds. Reading it never extends it, on the cookie lane, the bearer lane or the in-process session read alike. + - **Revocable.** `revoke()` ends the session. Afterwards neither the token nor the cookie resolves one. It is bound to that session alone and safe to call twice, so call it in a `finally`. + - **The deployment's own cookie.** The cookie carries the name and attributes of the plugin's session cookie, the cookie prefix and, on a secure deployment, `__Secure-` included. The session's active organization is the context's `tenantId`. + + ```ts + const auth = ctx.getService('auth'); + const session = await auth.mintPrincipalSession?.(context, { ttlSeconds: 60 }); + try { + // send session.token as `Authorization: Bearer`, or set session.cookie in a browser + } finally { + await session?.revoke(); + } + ``` +- adad73f: feat(spec)!: a select option declares its lifecycle `kind` (`open` / `won` / `lost`), and every option of an object's `stageField` must declare it + + Clause-②: yes (narrowing) + + `SelectOptionSchema` gains one optional key, `kind`, a closed vocabulary of three lifecycle categories that means the same on every object: `open` (a step on the way), `won` (the successful end: a deal's Closed Won, a lead's Converted, a task's Done, a case's Resolved) and `lost` (the unsuccessful end: Closed Lost, Unqualified, Cancelled). It is the spelling `record:path` `stages[].terminal` already takes, so the data layer and the page say one thing one way. Example: `{ label: 'Closed Lost', value: 'closed_lost', kind: 'lost' }`. The exported type `SelectOptionKind` names the three values. + + - On a plain single-value `select` / `radio` the key is optional, and an absent kind reads as `open`. Nothing is written in at parse: an option without `kind` parses without it. + - On any other field's options (`multiselect`, `checkboxes`, `tags`, a `multiple: true` select, a lookup's static options) `FieldSchema` refuses it at `options..kind`: a lifecycle is one value per record. + - A shared `picklist`'s options accept it (the same option shape). A form view's option list does not (`FormSelectOptionSchema`): it is refused there as an unknown key whose message points at the object field's own option. + + **BREAKING (authoring):** when an object's `stageField` names a field, every option of that field must declare `kind`. `ObjectSchema` refuses the object otherwise, at every door that parses one (`ObjectSchema.create()`, `defineStack`, `os validate`, `os build`, the metadata save door), with one issue per option at `fields..options..kind` naming the field and the option. There is no default under `stageField`. Also refused, at `stageField`: a stage field that takes its options from a `picklist` (the object cannot see them to check), and a stage field carrying options that holds no single option code per record (`multiselect`, a `multiple: true` select). Unchanged: `stageField: false`, a `stageField` naming no declared field, and one naming a field with no options. + + Migration: add `kind` to each option of the stage field, marking each terminal state `won` or `lost` and every other state `open`: + + - `status: Field.select({ options: [{ label: 'New', value: 'new' }, { label: 'Converted', value: 'converted' }, { label: 'Unqualified', value: 'unqualified' }] })` with `stageField: 'status'` → the same options with `kind: 'open'`, `kind: 'won'` and `kind: 'lost'`. + - A picklist-backed stage field → declare its options inline on the field. + - An object with no linear lifecycle → `stageField: false`. + + No conversion makes this edit, because which option ends a record's life successfully is the author's to say. `os migrate meta` lists the D3 entry `stage-field-option-kind-required` with the prescription. + + The Studio object designer's options repeater offers `kind`; the field form's options repeater lists it. + + The other packages follow the new key: + + - **`@objectstack/platform-objects`.** The metadata-form translation bundles (`./metadata-translations`) carry the options repeater's new `kind` row, with its label and help text in zh-CN, ja-JP and es-ES. + - **`@objectstack/metadata-core`.** The published contract fixture `FLS_CONTRACT_OBJECT` (`@objectstack/metadata-core/testing`) names `salary_grade` as its `stageField`, so its two options now declare `kind: 'open'`, and the fixture meets the stage-field rule: `ObjectSchema` raises no issue at `stageField` or under `fields.salary_grade.options`. The fixture is not otherwise a spec-valid object (its `script` actions carry no `body` or `target`), and this change leaves that as it was. + + +- 6422da5: feat(spec): `FlowRuntimeState` gains `unboundCause`, a closed classifier beside `reason` + + Clause-②: yes (widening) + + `FlowRuntimeState.reason` is a sentence a consumer renders and must not parse, so until now a console could not tell a flow left unarmed by deployment policy from one whose binding is broken: both arrive as `enabled: true, bound: false` with a `reason`. The row now also carries an optional `unboundCause`, present on exactly the rows that carry `reason` and absent on every other row. + + Its values are published as `FLOW_UNBOUND_CAUSES` (`@objectstack/spec/contracts`), with the derived type `FlowUnboundCause`: + + - `deployment_policy`: package-authored scheduled work is switched off on this deployment. An operator remedy, not a defect. + - `disabled_subflow`: the flow calls a packaged subflow that is disabled (ADR-0126 §7.3). Enable the subflow and the caller arms. + - `missing_trigger`: no trigger of the declared type is registered. Depend on the trigger package (`requires: ['triggers']`). + - `binding_failure`: the trigger is registered but binding the flow to it failed. The earlier boot warnings name the error. + + Additive: existing producers and consumers keep working unchanged, and `reason` keeps its text. +- dd11563: Clause-②: yes (widening) + + The platform translation bundle (`PlatformTranslationDataSchema`) gains a slot for a settings + `info_banner` row: `settings..banners..title` and `.text`, keyed by the + specifier's `id`, beside `groups`, `keys` and `actions`. `title` translates the specifier's `label` + (the heading spelling `groups` already uses) and `text` translates its `bannerText` (markdown). + Before this, a translated banner was refused with `unrecognized_keys`, so a banner reached every + locale in its manifest English. The slot is strict: a borrowed `label`, `bannerText` or + `description` leaf, or a singular `banner` group, is refused with the spelling it meant. + + Two resolvers read it, `resolveSettingsBannerTitle` and `resolveSettingsBannerText`, from the + `resolveSettings*` family: they walk the requested locale and the declared fallback chain, then + return the manifest literal. The `info_banner` specifier's own accept set is unchanged. Give the + banner an `id` to make it addressable; a banner without one keeps rendering its literals. +- 2c702fd: feat(spec)!: `@objectstack/spec/shared` no longer exports `normalizeVisibleWhen` or `VISIBILITY_ALIAS_KEYS`; read `visibleWhen` directly + + Clause-②: yes (narrowing) + + **BREAKING (TS/API surface):** two exports of `@objectstack/spec/shared` are gone — the function `normalizeVisibleWhen`, which folded the retired `visibleOn` / `visibility` keys onto the canonical `visibleWhen`, and the const `VISIBILITY_ALIAS_KEYS`, which listed those two keys. Use `visibleWhen` directly instead: it is the one conditional-visibility key, and since 18 every schema that had the two old keys (the form field, the form section and the page component) refuses them by name. No schema used the fold after those keys retired, and no caller of either export was found in this repository or in the pinned renderer. + + ## FROM → TO + + | you wrote | write instead | + | --- | --- | + | `import { normalizeVisibleWhen } from '@objectstack/spec/shared'`, then `normalizeVisibleWhen(node)` | read and write `node.visibleWhen`; there is no alias left to fold | + | `import { VISIBILITY_ALIAS_KEYS } from '@objectstack/spec/shared'` | nothing — the two keys it named are refused by the schemas; to reject them yourself, parse with the schema | + + **The one-line fix:** delete the import and the call, and read `visibleWhen`. + + This supersedes the sentence in this release's `visibleOn` / `visibility` retirement note that says the two exports "stay exported": they leave in the same release. + + **What an author who still imports them sees:** `tsc` fails with TS2305 (`Module '"@objectstack/spec/shared"' has no exported member …`); an ESM named import fails at link time with a `SyntaxError` (the module does not provide an export of that name); a CommonJS `require` reads the member as `undefined`. + + **Nothing to migrate in metadata.** No authored document ever carried either export, so `os migrate meta` has nothing to visit. The stored-row conversions `view-visibleOn-to-visibleWhen` and `page-component-visibility-to-visibleWhen` are unchanged: a pre-18 `sys_metadata` view or page row that still carries an old key keeps loading with `visibleWhen`. + + +- bbdfc05: feat(spec)!: retire the permission set's authored `managedBy` — a provenance key nothing read (#23891) + + **BREAKING** — an author can no longer write `managedBy` (`'package' | 'platform' | 'user'`) on a permission set (`PermissionSetSchema`, ADR-0086 D3): it is refused at parse. Nothing ever read the authored key. No seeder, package upgrade or lock consulted it, so `package` never made a set upgrade-owned, and `platform` or `user` never kept an upgrade off a set. Delete the key. A set's provenance is the package that ships it, decided from that package's artifact and recorded as `packageId`; a set created in this environment carries neither. The `sys_permission_set.managed_by` column is a different surface and is untouched, as is `ObjectSchema.managedBy`. + + Clause-②: yes (narrowing) + + Measured before removal, at `6d1660dc3b`: no shipped permission set authored the key (the example apps, `plugin-security`'s default sets, and HotCRM 4.0.1's seven sets at `1d7148bf2d`: zero, against seven `objects:` controls). Its one writer was Setup's Permission Sets form row, which wrote it into stored rows. The liveness ledger had ruled it `dead` on 2026-10-11. + + ## FROM → TO + + | you wrote (17.7 and earlier) | write instead | + | --- | --- | + | `definePermissionSet({ …, managedBy: 'package' })` | nothing: delete the key; a package-shipped set is the package's because the package ships it (`packageId`) | + | `managedBy: 'platform'` or `managedBy: 'user'` | nothing: delete the key; a set created in this environment carries neither `managedBy` nor `packageId` | + | relying on `managedBy` to keep a package upgrade off a set | clone the packaged set and edit the clone (the packaged-set lock refuses in-place edits) | + + **The one-line fix:** delete `managedBy` from every permission set. + + **What an author who still writes it sees.** `tsc` fails at the authoring site (`PermissionSet` types the key as retired), and the parse (`definePermissionSet()`, `defineStack({ permissions })`, `PUT /api/v1/meta/permission/:name`) refuses it at its own path with the prescription: + + > `permission.managedBy` was removed in @objectstack/spec 18 (ADR-0049 enforce-or-remove, ADR-0086 D3) — nothing ever read the authored key: no seeder, package upgrade or lock consulted it, so `package` never made a set upgrade-owned and `platform` or `user` never shielded one from an upgrade. Delete the key; a set's provenance is the package that ships it, decided from that package's artifact and recorded as `packageId`, and a set created in this environment carries neither. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. + + **What does not stop on it.** A stored `permission` row that Setup's form wrote, and an installed package's built artifact, still load: the rehydration seams (`applyConversionsToStoredItem`, `applyArtifactForwardConversions`) replay the D2 conversion below and strip the key, with a notice naming the set. Only the authoring, save and publish doors refuse it. + + ## The retirement kit + + - **A `retiredKey()` tombstone** on `PermissionSetSchema`, a `strictObject`, so the refusal carries its prescription and `tsc` fails first. `RETIRED_KEYS_BY_MAJOR[18]`: `security/PermissionSet:managedBy`. + - **The D2 conversion `permission-set-managed-by-removed`** (protocol 18, retired from the load path) deletes the key from every `permissions[]` item, whatever it holds, one notice per set. + - **The D3 entry `permission-set-managed-by-retired`** records the judgement the strip cannot make: any runbook that relied on the value to keep an upgrade off a set needs another path. + - **`@objectstack/platform-objects`:** the Permission Sets form's `managedBy` row, its help text and its three option labels (Package / Platform / User) leave Setup in all four locales. + - **No deprecation window**, per the project's startup-stage posture. + + ⚠️ **The out-of-repo consumer population is NOT MEASURED** beyond HotCRM 4.0.1. `@objectstack/spec` is published, so this is breaking for consumers no telemetry was consulted for. Studio's provenance badge reads the value as one of three inputs; it retires in an objectui follow-up. + + +- d050a8f: fix(spec,objectql)!: an installed or stored page carrying a declaration spec 18 retires converts at load instead of stopping the kernel build — `isDefault` off a bound record page is deleted and `template: 'full-width'` re-spelled `default`, each with a named notice; the authoring, save and publish doors keep refusing + + Clause-②: yes (widening) + + **What changes.** Protocol 18 refused `isDefault: true` on a page that is not a record page bound to an object, and closed the page `template` set (retiring `full-width`), at every door — the page parse (`definePage`, `defineStack`, so `os validate` / `os build`, and the metadata save door) and the kernel's package registration (`SchemaRegistry.installPackage` / `registerItem`, `MisplacedDefaultPageError`). The registration refusal also fired when a kernel was built from a package that was installed BEFORE the retirement: every hosted environment holding such a package failed its kernel build, with no remedy short of upgrading the package in each one. Data at rest now converts, and only data at rest: + + | Door | Before | Now | + | :--- | :--- | :--- | + | `definePage` / `defineStack` (`os validate`, `os build`), the metadata save door, package publish | refused at `isDefault` / `template` | **unchanged** — refused with the same sentence | + | a `sys_metadata` page row read back | served with `_diagnostics.valid: false` | converted on read: the flag deleted, `full-width` → `default`, `OS_METADATA_CONVERTED` notice | + | a compiled artifact inside the window its `engines.protocol` opens | the artifact door refused the artifact | converted before the strict parse, as every retired entry is | + | a package body registered through `manifest.register` / `ObjectQL.registerApp` that declares `engines.protocol` (an installed package, a marketplace install) | `MisplacedDefaultPageError`, kernel build stopped | converted before anything registers, with one `warn` per (package, conversion) naming the conversion id and the first site; the body is left unchanged on disk | + | the same body with NO declared range (a manifest a plugin builds in code), or declaring a floor newer than every retirement | refused | **unchanged** — refused | + + **The two ADR-0087 D2 entries**, both `retiredFromLoadPath` from the day they land (the authoring funnel never replays them): + + - `page-is-default-off-record-page-removed` (`page.isDefault`): deletes `isDefault: true` from exactly the pages `misplacedDefaultPageRefusal` refuses — a `home`, `app`, `utility` or `list` page, or a record page with no `object`. Lossless: nothing read the flag there (an app opens on its first navigation item). A default record page bound to an object keeps its flag. + - `page-template-full-width-to-default` (`page.template`): `'full-width'` → `'default'`. Lossless: the console drew both names with one template component. An unregistered template name (`centered`, `two-column`) and a region the template does not place have no lossless rewrite and are NOT converted — they stay with the parse, and the D3 entries `page-is-default-off-record-page-refused` / `page-template-closed-region-unplaced-refused` name the hand edits. + + **`@objectstack/objectql`** — `ObjectQL.registerApp` (and the `manifest` service, for an artifact carrying `packages[]`) replays the versioned forward conversion (`applyArtifactForwardConversions`, `@objectstack/metadata-core`) over a package body that declares its protocol range, before `installPackage`; the converted copy is what the package record and every registered item carry. Public: `ObjectQL.convertPackageBodyForward(body)`. + + **`@objectstack/metadata-core`** — `applyArtifactForwardConversions` reads the declared range off a FLAT package body (the manifest half beside its collections, no `manifest` key) when the definition carries no `manifest`; a definition with a `manifest` object is read there, as before. + + Migration: none for authors — the authoring doors refuse exactly what they refused. An installed package converts again at every registration until it is rebuilt (`os build`) and re-published with tooling from a release that ships the retirement; the boot log's warn names the conversion and the site. + + +- d731b89: fix(identity): the Invite User dialog offers only the roles the caller may grant, read from the same table the invitation role cap enforces + + Clause-②: yes (widening) + + **What was wrong.** A `delegated_admin` who opened Invite User was offered Owner, Admin and Delegated Admin. The server's invitation role cap refuses all three for that issuer. The `role` param inherited one static option list from the field, and nothing told the client which roles this caller may grant. + + **What is new in `@objectstack/spec` (`identity`).** One table, `INVITATION_ROLE_GRANTORS`, says which issuer grades may invite as each membership role: + + | role | issuers that may grant it | + |---|---| + | `owner` | `owner` | + | `admin`, `delegated_admin` | `owner`, `admin` | + | `member` | any issuer (`INVITATION_ROLE_ANY_ISSUER`) | + + New exports beside it: `invitationRoleGrantors(role)` (a role the closed vocabulary does not list takes the `owner`, `admin` row), `invitationRoleGrantPredicate(role)` (the CEL gate, one `'' in current_user.positions` term per grade, `undefined` for an any-issuer row), `INVITATION_ROLE_OPTIONS` (the role picker's options, each carrying its gate as `visibleWhen`), and the types `InvitationRoleGrantors` and `InvitationRoleOption`. + + **`@objectstack/plugin-auth`.** The invitation role cap now reads that table to decide which invitations it admits. It refuses exactly what it refused before, with the same refusal text. A pin compares it with the previous implementation on 1,517 request and issuer pairs. + + **`@objectstack/platform-objects`.** The `role` param of the three `invite_user` actions (`sys_user`, `sys_member`, `sys_invitation`) declares `INVITATION_ROLE_OPTIONS` itself instead of inheriting the field's list. The option values and English labels are unchanged. A `delegated_admin` is now offered Member only, an admin Admin, Delegated Admin and Member, and an owner all four. The per-option gate is a UI courtesy; the server cap remains the authority. Because the options are declared on the param, their labels now translate under `_actions.invite_user.params.role.options.`, and the bundles carry those keys in every locale. +- 66d2757: feat(spec)!: every installed-package row declares where it came from, a closed `source: 'code' | 'marketplace' | 'authored'`, and the `GET /packages` rows declare the `writable` verdict they already served (#23972) + + Clause-②: yes (narrowing — the served row gains two declared keys, and the record schema, the served-row union and the registry's installPackage signature each refuse what they accepted before) + + **BREAKING** — three published accept sets narrow. `InstalledPackageSchema` refuses a row without `source`, or with a value outside `code` / `marketplace` / `authored`. The served-row union `InstalledPackageAtEitherStageSchema` refuses a row without a boolean `writable`. `SchemaRegistry.installPackage` and `EngineSchemaRegistryView.installPackage` take `source` as their second argument and refuse a call without it. The migration is below. None of it is mechanically convertible, because only the door that registers a package knows where it came from. + + **What is new on the wire.** `GET /api/v1/packages` and `GET /api/v1/packages/:id` serve two declared keys on every row: + + - `source` names the package's origin. `code` means the deployment's own code registered it (a `defineStack()` host, a plugin's bundled manifest, a platform package). `marketplace` means the marketplace install-local plugin installed it, and that holds after a restart too, when the plugin rehydrates it from its ledger. `authored` means it is a database-authored package (a Studio base, a duplicate, an HTTP install), including after the restart that rehydrates it from `sys_packages`. The door that registers the package stamps the value. No reader derives it. + - `writable` is the server's own writability verdict (ADR-0070 D2), served on these rows since #14375 and now declared. It is computed per read and never stored, so it is declared on the served row (`InstalledPackageAtEitherStageSchema`) and not on the registry record. + + FROM → TO for a reader of these rows: + + | you did | do instead | + | --- | --- | + | treated `writable: true` as authored, and looked up each non-writable row in the marketplace's installed list (the control plane's, else install-local's) to tell marketplace from code | read `row.source` | + | read the undeclared `writable` | read the declared `writable` (unchanged value) | + + The two keys are separate statements: `source` says where the package came from, and `writable` says what this server lets an org do to it. + + **`InstalledPackageSchema` requires `source`.** It is `InstalledPackageSourceSchema`, exported from `@objectstack/spec/kernel`. A row without it, or with a value outside the closed set, is refused at the `source` key. + + **API change for code that registers packages (`@objectstack/objectql`).** The registry records the origin and never infers it: + + | you wrote | write instead | + | --- | --- | + | `registry.installPackage(manifest)` | `registry.installPackage(manifest, 'code')` (or `'marketplace'` / `'authored'`, whichever is where the package came from) | + | `registry.installPackage(manifest, settings, scope)` | `registry.installPackage(manifest, source, settings, scope)` | + | `ctx.getService('manifest').register(artifact)` for a marketplace install | `ctx.getService('manifest').register(artifact, { source: 'marketplace' })` | + + `SchemaRegistry.installPackage` throws a `TypeError` naming the closed set when `source` is missing or unknown, before anything is recorded. `ObjectQL.registerApp(body, scope?, source?)` and the `manifest` service record `'code'` when no source is given. They accept `'code'` or `'marketplace'` and refuse `'authored'`. The `IObjectQLEngine` registry contract (`@objectstack/spec/contracts`) declares the same `installPackage(manifest, source, settings?)`. + + **The one-line fix:** pass the package's origin as `installPackage`'s second argument. + + Readers: objectui's Setup → Packages Source column and its `?source=` filter (it switches from the two-read join to `source` in its own follow-up), and the client SDK's `packages.list()` / `packages.get()` row types. + + +- a65a59b: feat(spec)!: the ADR-0078 conversion-conflict channel is retired — `CONVERSION_CONFLICT_CODE`, `ConversionConflictNotice`, `ConversionConflictDetail`, the `onConflict` / `reservedNodeTypes` conversion options, the `conflicts` arrays and the `FLOW_CONVERSION_CONFLICT` 409 + + Clause-②: yes (narrowing) + + **BREAKING (published API):** the ADR-0087 conversion API carried a channel for a rename REFUSED because its old token was a live name in the environment: a conversion reported it through `ConversionContext.reportConflict`, `applyConversions` handed it to an `onConflict` sink as a `ConversionConflictNotice` (code `OS_METADATA_CONVERSION_CONFLICT`), the automation engine collected it in `canonicalizeStoredFlow`'s `conflicts` array, and the metadata protocol answered it with `409 FLOW_CONVERSION_CONFLICT`. Its only producer was the live-executor guard inside the flow-node HTTP callout rename. Protocol 18 reserved those three names (`FLOW_RESERVED_NODE_TYPES`), so the engine refuses an executor under one, no live owner can exist, and the guard went. Nothing can raise a conversion conflict any more, so the channel leaves the published surface rather than staying as a live-looking failure mode nothing triggers. + + ## FROM → TO + + | you wrote | write instead | + | --- | --- | + | `import { CONVERSION_CONFLICT_CODE, type ConversionConflictNotice, type ConversionConflictDetail } from '@objectstack/spec'` | delete the import — no replacement | + | `applyConversions(stack, { onConflict, reservedNodeTypes, … })` (likewise `applyConversionsToFlow`, `applyConversionsToStoredItem`, `collectConversionNotices`) | delete both options; keep `onNotice` / `onTodo` | + | a conversion's `apply` reading `context.reservedNodeTypes` or calling `context.reportConflict` | a rename over an open namespace reserves its old names instead (the `FLOW_RESERVED_NODE_TYPES` pattern), so the refusal happens at registration; a site with no lossless rewrite goes to `context.reportTodo` | + | an `IAutomationService.canonicalizeStoredFlow` implementation returning `conflicts: […]` (or a `StoredFlowCanonicalization` from `@objectstack/metadata-protocol` carrying one) | drop the member; return `parsed`, `storable`, `notices` and optionally `todos` | + | a client branch on `error.code === 'FLOW_CONVERSION_CONFLICT'` / HTTP 409 from a flow save, a package duplication or an unmanaged package import | delete the branch — no request can produce it | + + **The one-line fix:** delete every reference; the branch it guarded was already dead, so deleting it changes no behaviour. + + **What an upgrader sees.** Importing a removed name is `TS2305`; passing a removed option, or returning a `conflicts` member from a typed canonicalizer, is an excess-property `tsc` error. `ErrorCode` no longer parses `FLOW_CONVERSION_CONFLICT`. + + **Runtime behaviour is unchanged.** The removed branches could not fire: a stored flow carrying a reserved callout name still loads as `http` (the rewrite has been unconditional since the names were reserved), and `saveMetaItem`, `duplicatePackage`, `importUnmanagedPackage` and `os migrate meta --stored` answer exactly as before. No metadata document needs editing. + + ## The retirement kit + + - **`@objectstack/spec`:** the three exports leave the root; `ApplyConversionsOptions` loses `onConflict` and `reservedNodeTypes`, `ConversionContext` loses `reservedNodeTypes` and `reportConflict`, the `IAutomationService.canonicalizeStoredFlow` result loses `conflicts`; the error-code ledger drops `FLOW_CONVERSION_CONFLICT` by its own "Retiring a code" rule (a row comes out with its last emitter). + - **`@objectstack/service-automation`:** `AutomationEngine.canonicalizeStoredFlow` no longer returns `conflicts`, and the engine no longer builds a live-name set for the conversion pass. + - **`@objectstack/metadata-protocol`:** `StoredFlowCanonicalization` loses `conflicts`; the flow save door's 409, the stored-migration pass's `failed` row for a refused rename, `duplicatePackage`'s failed item and `importUnmanagedPackage`'s 409 are gone with it. + - **The D3 entry `conversion-conflict-channel-removed`.** No D2 conversion and no tombstone: no metadata document ever carried any of it. + - **No deprecation window**, per the project's startup-stage posture. + + ⚠️ **The out-of-repo consumer population is NOT MEASURED.** The three packages are published, so this is breaking for consumers no telemetry was consulted for. + + +- 45e6001: feat(spec): register the invitation role cap's three refusal codes under `@objectstack/plugin-auth` + + Clause-②: yes (widening) + + The error-code ledger (`ERROR_CODE_LEDGER`, and so `ErrorCode`) gains three `@objectstack/plugin-auth` codes, one per refusal the invitation role cap can return. Today the cap answers all three as a bare `403 FORBIDDEN`, so a client such as the Invite User dialog cannot tell them apart: + + - `INVITE_ROLE_ABOVE_ISSUER` — the requested role grades above the issuer's own; an invitation never confers authority above its issuer. + - `INVITE_ISSUER_MEMBER_ONLY` — an issuer below admin grade may invite only as a plain `member`; the requested role may carry capability of its own. + - `INVITE_ISSUER_UNVERIFIED` — fail-closed: the issuer's organization membership could not be established, so the invitation may confer nothing beyond `member`. + + Registration only: `@objectstack/plugin-auth` still throws `FORBIDDEN` until #23945 stamps these codes on its refusals. No existing code changes, and no standard-catalog member is shadowed (none needs a synonym waiver). +- 9f024d8: feat(spec): a solution blueprint view declares `filters` and `sort`, so a view named by a condition can carry that condition + + Clause-②: yes (widening) + + `BlueprintViewSchema` and the strict structured-output mirror `SolutionBlueprintStrictSchema.views[]` gain two keys. Before this, a blueprint could say "overdue only" in a view's name and nowhere else, and the built view showed every record. + + - **`filters`** — which records the view shows, as list-view filter rules `[{ field, operator, value }]`, ANDed. + - Lenient (`BlueprintViewSchema`): optional, and the array element IS `ViewFilterRuleSchema` (the rule `ListView.filter` declares), so it takes the same operators, folds the same legacy spellings, and refuses the same operator/value-shape mismatches (`in` without an array, `between` without `[min, max]`, `icontains` without a non-empty string). + - Strict (`SolutionBlueprintStrictSchema`): required and nullable (null = a view over every record). Each rule carries the same three keys, every one present: `operator` is the canonical vocabulary only (`VIEW_FILTER_OPERATORS`, no legacy shorthand such as `eq`), and `value` is nullable (null for the unary operators). The operator/value-shape coupling is not in the strict mirror's JSON Schema; the lenient parse refuses it. + - **`sort`** — the default record order, `[{ field, order }]` with `order` `asc` or `desc`, the first entry sorting first. Lenient: optional, the element is the shared `SortItemSchema` (the `{ field, order }` pair `ListView.sort` declares; array only, a `"field desc"` string is refused). Strict: required and nullable, `{ field, order }` with both keys present. + - Both describes tell the model that a view whose name or label states a condition ("overdue", "my open") must carry the matching filter, with worked rules using the platform tokens `{today}` and `{current_user_id}`. + - Additive only: every blueprint that parsed before still parses with the lenient schema. A producer of the STRICT shape must now send `filters` and `sort` on each view (null when it has none), as with every other key in that mirror. + +### Patch Changes + +- b15f8a1: feat(service-print): print a record's print page to a PDF in headless Chromium, as the requesting user, and archive it as a file attached to the record + + Clause-②: yes (widening) + + A new package, `@objectstack/service-print`. `PrintServicePlugin` registers the in-process `print` service (`IPrintService`): `status()` and `printRecordToFile({ app, object, recordId, page, filename? }, context)`. It adds no route, no metadata key and no error code. Compose it explicitly: + + ```ts + import { PrintServicePlugin } from '@objectstack/service-print'; + plugins: [new PrintServicePlugin()] + ``` + + - **One engine, headless Chromium, in one of two placements.** A local executable (`OS_PRINT_CHROMIUM_PATH`, option `chromiumPath`), or a remote browser's DevTools WebSocket endpoint (`OS_PRINT_BROWSER_ENDPOINT`, option `browserEndpoint`). Either one needs `OS_PRINT_CONSOLE_URL` (option `consoleUrl`): the console's URL as that browser reaches it, `/_console` included. Configuring both placements is refused. `playwright-core` is pinned exact on this package only. + - **Loud at startup.** `start()` probes the engine and logs its placement and Chromium version. An engine that is not configured, is misconfigured or does not answer is reported once, with the setting to fix, and `status()` says why. A remote endpoint is never logged beyond its scheme and host. + - **As the requesting user.** The page is rendered through a short-lived session minted for the caller's own principal by the auth service (`mintPrincipalSession`), and revoked in a `finally`. There is no system principal. + - **Refusals first, each with `code` and `status`, before any session is minted or browser started** (an engine that failed its last probe is re-probed only after them all): a caller that is not a human user acting for themselves (`PERMISSION_DENIED` / 403); an engine that is not ready (`SERVICE_UNAVAILABLE` / 503); an unknown page (`RESOURCE_NOT_FOUND` / 404); a page that is not the object's default record page or declares no `print` (`VALIDATION_ERROR` / 400); a record the caller cannot read (`RECORD_NOT_FOUND` / 404); an object without `enable: { files: true }` (`FILES_DISABLED` / 403). + - **The archive runs as the requesting user.** `@objectstack/service-storage`'s `file-attachments` service gains a caller-scoped face, `ICallerFileAttachmentService` (exported type), on the same slot and the same writer: `assertMayAttach(parentObject, parentId, context)` throws the insert's own refusal without writing (`PERMISSION_DENIED` / 403 when the caller may not create attachments, `ATTACHMENT_PARENT_ACCESS` / 403 when the attach rule refuses), and `attachFileAs(input, context)` writes one `sys_file` owned by the caller and one `sys_attachment` inserted under the caller's context, so every insert gate judges the caller and the row is stamped `uploaded_by` the caller. The print service asks `assertMayAttach` in its files step, before any session is minted or browser started. `attachFileAs` acts only in the caller's own organization: an `organizationId` other than the context's `tenantId` (or `null` when it names none) is refused `PERMISSION_DENIED` / 403. `attachFile` (the system verb) is unchanged. + + `@objectstack/spec`'s `llms.txt` counts the new package: the workspace publishes 72 packages. +- 4216796: Clause-②: no + + The docblock above `FieldSchema`'s lookup picker keys no longer says that objectui reads + `lookupColumns`, `lookupPageSize` and `allowCreate` with a snake_case alias first. objectui + retired those reads (objectui#12157), so the picker reads all seven keys (`displayField`, + `descriptionField`, `lookupColumns`, `lookupPageSize`, `lookupFilters`, `dependsOn`, + `allowCreate`) in camelCase only. A stored definition that spells one of them in snake_case is + folded onto the declared key when the console ingests it. The six lookup picker rows in + `liveness/field.json` carry the same reading, dated and pinned to the objectui commit it was taken + on. Where a reader outside the picker still falls back to a snake_case spelling, the row names + that reader. No key, schema or accepted value changes. +- c40a0dd: The liveness ledger's three public-form rows cite the code that reads their keys where it now lives + + Clause-②: no (only the rows' evidence paths move and every status stays `live`: `ViewSchema` accepts `form.sections`, `form.sharing` and `formViews` exactly as before, and no state count moves) + + - `liveness/view.json`: the `form.sections` and `form.sharing` rows cited `packages/rest/src/rest-server.ts` for the two public form doors, `GET /forms/:slug` and `POST /forms/:slug/submit`. `@objectstack/rest` moved the body of the registrar that mounts them, `RestServer#registerFormEndpoints`, to `packages/rest/src/form-routes.ts` with no behaviour change, so their three path references follow it. + - `liveness/view.json`: the `formViews` row cited the doors' `findPublicFormView`, which moved with that body and reaches every form view through `anonymousFormIntakeCandidates`. The row now cites that rule where it is declared, `packages/spec/src/ui/anonymous-form-intake.ts#anonymousFormIntakeCandidates`, the code on the read path that names `formViews`. + - The quoted code, the verdicts and the notes are unchanged. +- 60f9baf: Clause-②: no + + The `manifest.contributes.routes` and `App.apis` retire messages (and the published + `plugin-manifest-contributes-routes-retired` migration text) no longer send app authors to an + imperative `http.server` route registered on `kernel:ready`. An app declares its endpoint under + `defineStack({ apis })` (`type: 'object_operation'` or `'flow'`, ADR-0121), computes in a flow, + and registers code as a stack function (`defineStack({ functions })`) or a connector. The + `http.server` service itself is unchanged: platform plugins keep mounting their own doors on it. + No key is added or removed. +- 3fed1eb: fix(spec): the liveness ledger reads `app.branding.logo` as `live`, citing the console's `AppMark` reader (#23394) + + Clause-②: no + + **What was wrong.** The liveness ledger shipped with `@objectstack/spec` graded `app.branding.logo` `planned`, with a note saying no mounted component rendered it. The console now does: objectui's `AppMark` draws `branding.logo` as the app's square icon on the top-bar app pill, on each App Switcher row and in the phone drawer header. + + **What changes.** + + - `liveness/app.json`: `branding.logo` reads `live`, citing `AppMark` and the two shell components that mount it at the `.objectui-sha` pin. The designer pages that write the key stay cited as its `producer`. The `branding` container's note says the same. + - `liveness/state-counts/app.md` is regenerated: `app` moves from `live 50 · planned 1` to `live 51 · planned 0`. + + Unchanged: what `AppSchema` accepts and refuses (an app-level `logo` is still refused, and the refusal still points at `branding`), and every other ledger row. +- ee0e115: docs: the default-record-page remedies name the other package's page, not the whole package + + Clause-②: no + + A second `isDefault: true` record page for one object is refused with `DEFAULT_RECORD_PAGE_CONFLICT`. The package registry's refusal already told the author to keep one default or "compose the deployment without the other package's page". Three sibling texts still told them to drop the whole package, though a deployment can keep a package and leave out the one page it ships: + + - `@objectstack/metadata-protocol`: the metadata save door's refusal now reads "a page a package ships stays the default unless the deployment is composed without that package's page." Only the remedy clause changed; `code`, `status`, `object` and `holders` are unchanged. + - `@objectstack/spec`: `PageSchema.isDefault`'s TSDoc and the error-code ledger's comment on `DEFAULT_RECORD_PAGE_CONFLICT` say the same. + - `@objectstack/runtime`: `AppPlugin`'s private `bundle` field now documents the three readers outside the class that read it structurally, so a rename is not made blind. + + No behaviour changes; nothing to migrate. +- 8684079: **Shipped in 18.0.0-next.2**, whose CHANGELOG missed this entry (#23818); listed here one prerelease late. + + fix(metadata-protocol,metadata): on a database the organization-ownership ceremony has not migrated, the paged family reads, `DatabaseLoader`'s count and the package rollback agree with the environment-only reads; the ceremony's apply is named with `--plan-file` (ADR-0131 D6/D7) + + Clause-②: no + + Every change below matters only where legacy organization rows still sit in the `sys_metadata` family, the database `os serve` refuses to start on until `os migrate organization-ownership` has verified. On a migrated or fresh database every answer is unchanged. + + - **`@objectstack/metadata-protocol`: a paged read leaves the legacy rows out before the page is cut.** `auditMetaItem` (`limit`), `listCommits` (`limit`), `revertStoredPackage`'s first-active-row probe and `SysMetadataRepository.list({ limit })` used to filter a legacy organization's rows out after the limit. A page they sat in came back short, and `revertStoredPackage` answered `RESOURCE_CONFLICT` (409) "has never been published" for a package the environment had published when its first active row was an organization's. When a page holds a legacy row, the read is now repeated with those rows excluded in the query. + - **`@objectstack/metadata-protocol`: `rollbackToPackageCommit` refuses a legacy organization's commit by name.** It answered `COMMIT_NOT_FOUND` (404) for a commit that is there. It now gives `revertCommit`'s refusal: `NOT_OVERRIDABLE` (403), the same sentence, naming the commit and its organization. + - **`@objectstack/metadata`: `DatabaseLoader` counts what it reads.** `exists()` answered true for an address only a legacy organization row held, while `load()` answered null, and `queryHistory`'s `total` counted legacy history rows no page serves. Neither counts them now. + - **`@objectstack/metadata-protocol`: internal cleanup.** The organization plumbing that only ever carried the environment (`null`) is removed from the list merge and the served-row resolution. No export changes. + - **`@objectstack/spec`: wording.** The four ADR-0087 entries `18.sys-metadata{,-audit,-commit,-history}-organization-column-retired` now name the ceremony's reviewed apply, `os migrate organization-ownership --out PLAN` and then `--apply --plan-file PLAN`, which `--plan-file`'s own entry requires. No id, category, version or field the migration engine reads changes. + - **`@objectstack/cli`: wording.** `os migrate apply --allow-destructive`'s refusal on an unmigrated database now reads "a drop the organization-ownership ceremony owns, and the ceremony has not verified on this database". Its `--json` payload is unchanged. +- 23d0bc7: fix(spec): a page that declares `print` is no longer warned that nothing applies it, and the flow terminal-toast translations read `live` (#23710) + + Clause-②: no + + **What was wrong.** The console now prints a page that declares `print`: its `PageRenderer` mounts a print stylesheet built from every key of the declaration (paper size and orientation, the four margins, the running header and footer, page numbers, and the two page-break hints). But the liveness ledger shipped with `@objectstack/spec` still graded `page.print` `planned` with an author warning, so `os validate` / `os build` / `os lint` told every author of a print page that printing it prints the screen layout. The `print` describes said the same ("until it ships, nothing applies it"). + + **What changes.** + + - `liveness/page.json`: `print`, its eight keys and the four `margins` sides read `live`, citing the console reader at the `.objectui-sha` pin. The container's `authorWarn` / `authorHint` are dropped, so `lintLivenessProperties` no longer emits `liveness-planned-property` for a page that declares `print`. + - `liveness/translation.json`: `flows.successMessage` and `flows.errorMessage` read `live`. The console's flow runner shows the translated terminal toast through `translateFlow` and falls back to the flow's own text when the bundle has none. + - `PagePrintSchema` and `PageSchema.print`: the describes name the console's browser print rendering as the consumer that applies each key. The server-side PDF renderer is still to come. + + Unchanged: what a `print` declaration accepts and refuses, and every other ledger row. +- af3873b: Clause-②: no + + The published TSDoc now sends a form section's translations to the right key. `FormSectionSchema.name` used to name only `metadataForms..sections.`, which is the key for a metadata-editing form (`defineForm({ schemaId })`, read by `resolveMetadataFormLabels`). A section of an object-bound form view is keyed at `objects.._sections.`, where `` is the form's own `data.object`, else its view's. The docblock now names both keys and which form each one serves. `TranslationDataSchema._sections` now names the three surfaces that author its key (form-view sections, `record:details` sections, object `fieldGroups`) and its readers (`walkObjectSections`, `collectViewRecord`, `translateView`). `resolveMetadataFormLabels`' docblock no longer says that a section without a `name` keeps its section-level text: such a section is looked up under a slug of its `label`, as the function has always done. These are comment changes only: no key, `.describe()` text or behaviour changes. An object-form translation bundle written from the old docblock under `metadataForms.…` never translated anything. Move its section entries to `objects.._sections.`. +- f846cc2: docs(spec): the step-18 upgrade text and the `exportOptions` TSDoc say a stored page's next save meets the component-props gate at the metadata save door + + Clause-②: no + + Text only; no accept set moves. Since the component-props gate (`validateComponentProps` / `validateComponentTypes`) runs on a `page` write at the metadata save door, three step-18 semantic entries that `os migrate meta` and the protocol upgrade guide print — `ui-page-action-row-operation-declared`, `ui-object-block-grouping-config-typed` and `element-text-variant-heading-subheading-retired` — and the `object-grid` `exportOptions` TSDoc in the `.d.ts` still said a page component's `properties` is not parsed on the save path. They now say what runs: a stored page still loads, because `properties` is not parsed on the metadata load path, and the next save of the page meets the component-props gate at the metadata save door (422 `INVALID_METADATA` on a finding), beside `os validate`, `os build` and `os lint`. +- 1ecb08a: Clause-②: no + + The `description` of four metadata types in `DEFAULT_METADATA_TYPE_REGISTRY`, which Setup shows under the + type's title, is now one plain sentence for an administrator instead of ADR vocabulary: + + - `api`: "A stable HTTP URL that other systems call to run a flow or work with records in the app" + - `doc`: "A help page written in Markdown that a package ships or you write yourself" + - `book`: "A table of contents that groups help pages into ordered sections" + - `capability`: "A permission a package defines; you grant it through permission sets" + + The ADR references (ADR-0121, ADR-0046, ADR-0046 §6, ADR-0066 D1) moved to the comments beside the + registry rows. The generated metadata-forms translations carry the new sentences in en, zh-CN, ja-JP + and es-ES. No key, type or accept/refuse result changes. +- d0fe050: Clause-②: no + + Six help texts an administrator reads in Setup no longer carry ADR numbers. On the Permission Sets edit form, + `isDefault`, `managedBy` and `packageId` are rewritten as plain sentences that say what the field does, and + `adminScope` drops its "(ADR-0090 D12)" prefix. On the Positions edit form, `delegatable` drops "(ADR-0091 D3)". + The view form's End-user controls description drops "(ADR-0047, Airtable Interface parity)". + + The ADR references moved to the comments beside the form rows. The generated metadata-forms translations carry + the new wording in en, zh-CN, ja-JP and es-ES. No key, type or accept/refuse result changes. +- 92fd343: Clause-②: no + + The docblock on `EVERYONE_POSITION` and `GUEST_POSITION` no longer says that packages suggest + bindings to both audience anchors. It now reads that packages suggest bindings to `everyone` at + install time and the admin confirms each one, never auto-bound, and that no door binds a + permission set to `guest` until ADR-0138 D3 item 5's bind door is built, an act of the + environment's administrator and never of a package. The docblock ships in the package's + `src/**/*.zod.ts` files and in the JSDoc of `dist/*.d.ts`, so it is the hover text an author or an + AI reads on the two constants. No key, schema, export or accepted value changes. +- 905f57b: docs(spec): `deriveRecordFlowSurface`'s docblock no longer calls its override list closed, and names `editMode` + + Clause-②: no + + - **The override sentence.** The shipped docblock said the explicit overrides of the derived default were `navigation.mode` / `navigation.size`, `FormView.type` / `modalSize` or an assigned page — "the sanctioned per-object overrides — no new authorable key". The same package declares `ObjectSchema.editMode` (`'modal' | 'page'`), and the renderer applies it. The docblock now says what the function reads (`def.fields`, `flow`, `opts`, nothing else) and lists only the explicit config the console renderer measurably lets win over this default: `navigation.mode` / `navigation.size`, an assigned page, and `editMode`. The function still does not read `editMode`. `FormView.type` / `modalSize` are no longer listed: an authored `FormView.modalSize` does not reach the console's create/edit modal, and `FormView.type` changes that modal's layout, not its surface. + - **Unchanged:** no behaviour, no schema, no export changes. `deriveRecordSurface` and `deriveRecordFlowSurface` return what they returned before. +- 8a83ceb: docs(spec): `IHttpRequest.body`'s TSDoc says what a handler reads, and that reading it may refuse + + Clause-②: no + + - **What it is.** The parsed JSON document for `application/json`, or the parsed fields of a `multipart/form-data` or `application/x-www-form-urlencoded` form; `{}` when the request carries no body. + - **It may refuse.** An implementation that cannot parse the body, or does not read its media type, throws from the read instead of handing over an invented value. The standard transport (`@objectstack/plugin-hono-server`) does, and answers 400 `INVALID_REQUEST` itself, replacing whatever the handler wrote. So a handler must not catch the refusal and carry on. + - **Raw bytes.** A handler that needs them reads `rawBody()` and never reads `body`. + - **Unchanged:** the type (`body?: any`), every other member, and every runtime behaviour. This is prose in the shipped `.d.ts` only. +- 462d02f: Clause-②: no + + The `ActionParam` `options` docblock in `src/ui/action.zod.ts`, and the comment above that option + shape's `guidance` table, no longer say that objectui's `normaliseOptions` rebuilds each option a + field-backed param inherits as a bare `{ label, value }` pair. At the console pin (`.objectui-sha` + `f8267df93aba`) it keeps every key of an inherited option by spread + (`packages/fields/src/resolveActionParams.ts:515` in objectui). The comments now give the reason + that remains for the `guidance` texts not telling an author to make a param field-backed and + inherit `color`, `icon` or `disabled`: the keys survive, but no reader of them on that path is + measured. No key, schema, export, accepted value or message text changes. + ## 18.0.0-next.2 ### Minor Changes diff --git a/packages/spec/package.json b/packages/spec/package.json index b0f009aa866..34b89312ee5 100644 --- a/packages/spec/package.json +++ b/packages/spec/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/spec", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "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 91e1599e68b..708934dd72d 100644 --- a/packages/triggers/trigger-api/CHANGELOG.md +++ b/packages/triggers/trigger-api/CHANGELOG.md @@ -1,5 +1,52 @@ # @objectstack/trigger-api +## 18.0.0-next.3 + +### Patch Changes + +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [6422da5] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [4ffdebe] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [e246f06] +- Updated dependencies [a65a59b] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/core@18.0.0-next.3 + ## 18.0.0-next.2 ### Patch Changes diff --git a/packages/triggers/trigger-api/package.json b/packages/triggers/trigger-api/package.json index 932d1570ed3..242ac0d2dea 100644 --- a/packages/triggers/trigger-api/package.json +++ b/packages/triggers/trigger-api/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/trigger-api", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "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 9b4ffe83be2..080140efd25 100644 --- a/packages/triggers/trigger-record-change/CHANGELOG.md +++ b/packages/triggers/trigger-record-change/CHANGELOG.md @@ -1,5 +1,52 @@ # @objectstack/plugin-trigger-record-change +## 18.0.0-next.3 + +### Patch Changes + +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [6422da5] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [4ffdebe] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [e246f06] +- Updated dependencies [a65a59b] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/core@18.0.0-next.3 + ## 18.0.0-next.2 ### Patch Changes diff --git a/packages/triggers/trigger-record-change/package.json b/packages/triggers/trigger-record-change/package.json index f692a071806..89c073ca495 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": "18.0.0-next.2", + "version": "18.0.0-next.3", "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 5af833ee9e6..2fff2c1c564 100644 --- a/packages/triggers/trigger-schedule/CHANGELOG.md +++ b/packages/triggers/trigger-schedule/CHANGELOG.md @@ -1,5 +1,57 @@ # @objectstack/plugin-trigger-schedule +## 18.0.0-next.3 + +### Patch Changes + +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [30ce56e] +- Updated dependencies [df2c551] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [6422da5] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [4ffdebe] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [e246f06] +- Updated dependencies [a65a59b] +- Updated dependencies [077b75f] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/metadata-core@18.0.0-next.3 + - @objectstack/core@18.0.0-next.3 + - @objectstack/types@18.0.0-next.3 + ## 18.0.0-next.2 ### Patch Changes diff --git a/packages/triggers/trigger-schedule/package.json b/packages/triggers/trigger-schedule/package.json index e6ccf57eaf1..2b56f10ab07 100644 --- a/packages/triggers/trigger-schedule/package.json +++ b/packages/triggers/trigger-schedule/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/trigger-schedule", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "license": "Apache-2.0", "description": "Schedule flow trigger for ObjectStack \u2014 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 0c6f3e5a655..def6d003425 100644 --- a/packages/types/CHANGELOG.md +++ b/packages/types/CHANGELOG.md @@ -1,5 +1,49 @@ # @objectstack/types +## 18.0.0-next.3 + +### Patch Changes + +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [8776119] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9cb9d95] +- Updated dependencies [f846cc2] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [6422da5] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [bbdfc05] +- Updated dependencies [d050a8f] +- Updated dependencies [d731b89] +- Updated dependencies [905f57b] +- Updated dependencies [66d2757] +- Updated dependencies [a65a59b] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] + - @objectstack/spec@18.0.0-next.3 + ## 18.0.0-next.2 ### Patch Changes diff --git a/packages/types/package.json b/packages/types/package.json index bd40d99c9fd..33b38783634 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/types", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "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 89b4cb30d8c..b5fec749c22 100644 --- a/packages/verify/CHANGELOG.md +++ b/packages/verify/CHANGELOG.md @@ -1,5 +1,132 @@ # @objectstack/verify +## 18.0.0-next.3 + +### Patch Changes + +- Updated dependencies [b15f8a1] +- Updated dependencies [4216796] +- Updated dependencies [c40a0dd] +- Updated dependencies [60f9baf] +- Updated dependencies [b9dbc3d] +- Updated dependencies [3fed1eb] +- Updated dependencies [a35f7a6] +- Updated dependencies [1eac7ad] +- Updated dependencies [9fcea20] +- Updated dependencies [f606ba2] +- Updated dependencies [105902f] +- Updated dependencies [f901746] +- Updated dependencies [ee0e115] +- Updated dependencies [8684079] +- Updated dependencies [a69a40a] +- Updated dependencies [0a052e6] +- Updated dependencies [ae0ca66] +- Updated dependencies [23d0bc7] +- Updated dependencies [7428da0] +- Updated dependencies [b55be42] +- Updated dependencies [abb6df0] +- Updated dependencies [5b6c257] +- Updated dependencies [0b1179f] +- Updated dependencies [8776119] +- Updated dependencies [74b4362] +- Updated dependencies [f7a5ebe] +- Updated dependencies [0a168a3] +- Updated dependencies [97b8e7a] +- Updated dependencies [af3873b] +- Updated dependencies [9d4fb3c] +- Updated dependencies [c5cf546] +- Updated dependencies [9cb9d95] +- Updated dependencies [cf07168] +- Updated dependencies [f846cc2] +- Updated dependencies [34b740f] +- Updated dependencies [30ce56e] +- Updated dependencies [2e0e451] +- Updated dependencies [94114c1] +- Updated dependencies [1ecb08a] +- Updated dependencies [adad73f] +- Updated dependencies [0cfd0c4] +- Updated dependencies [ae4dfaf] +- Updated dependencies [099cc9d] +- Updated dependencies [77efc66] +- Updated dependencies [747c8e9] +- Updated dependencies [6422da5] +- Updated dependencies [6422da5] +- Updated dependencies [8f4260f] +- Updated dependencies [d0fe050] +- Updated dependencies [dd11563] +- Updated dependencies [67e8792] +- Updated dependencies [2e15620] +- Updated dependencies [0cb45e9] +- Updated dependencies [2c702fd] +- Updated dependencies [92fd343] +- Updated dependencies [b74434c] +- Updated dependencies [bbdfc05] +- Updated dependencies [605aa01] +- Updated dependencies [a33f3d5] +- Updated dependencies [d050a8f] +- Updated dependencies [9e797a8] +- Updated dependencies [b25d98a] +- Updated dependencies [75ced06] +- Updated dependencies [5fb8d15] +- Updated dependencies [2054a08] +- Updated dependencies [5c2ac83] +- Updated dependencies [4ffdebe] +- Updated dependencies [33228c5] +- Updated dependencies [d731b89] +- Updated dependencies [19955fb] +- Updated dependencies [905f57b] +- Updated dependencies [aefab7c] +- Updated dependencies [66d2757] +- Updated dependencies [c7168ac] +- Updated dependencies [44fe210] +- Updated dependencies [e246f06] +- Updated dependencies [a65a59b] +- Updated dependencies [b75129f] +- Updated dependencies [6dede7a] +- Updated dependencies [62f959d] +- Updated dependencies [c1c0a90] +- Updated dependencies [ab3ba2c] +- Updated dependencies [8a83ceb] +- Updated dependencies [462d02f] +- Updated dependencies [544b8d7] +- Updated dependencies [45e6001] +- Updated dependencies [9f024d8] +- Updated dependencies [cdf3777] + - @objectstack/spec@18.0.0-next.3 + - @objectstack/service-storage@18.0.0-next.3 + - @objectstack/plugin-security@18.0.0-next.3 + - @objectstack/rest@18.0.0-next.3 + - @objectstack/runtime@18.0.0-next.3 + - @objectstack/service-job@18.0.0-next.3 + - @objectstack/service-automation@18.0.0-next.3 + - @objectstack/metadata@18.0.0-next.3 + - @objectstack/objectql@18.0.0-next.3 + - @objectstack/plugin-hono-server@18.0.0-next.3 + - @objectstack/plugin-audit@18.0.0-next.3 + - @objectstack/platform-objects@18.0.0-next.3 + - @objectstack/plugin-approvals@18.0.0-next.3 + - @objectstack/plugin-auth@18.0.0-next.3 + - @objectstack/service-messaging@18.0.0-next.3 + - @objectstack/plugin-sharing@18.0.0-next.3 + - @objectstack/service-settings@18.0.0-next.3 + - @objectstack/core@18.0.0-next.3 + - @objectstack/plugin-email@18.0.0-next.3 + - @objectstack/service-package@18.0.0-next.3 + - @objectstack/mcp@18.0.0-next.3 + - @objectstack/service-mailbox@18.0.0-next.3 + - @objectstack/plugin-webhooks@18.0.0-next.3 + - @objectstack/service-analytics@18.0.0-next.3 + - @objectstack/service-cache@18.0.0-next.3 + - @objectstack/service-datasource@18.0.0-next.3 + - @objectstack/service-queue@18.0.0-next.3 + - @objectstack/service-realtime@18.0.0-next.3 + - @objectstack/service-sms@18.0.0-next.3 + - @objectstack/trigger-api@18.0.0-next.3 + - @objectstack/trigger-record-change@18.0.0-next.3 + - @objectstack/trigger-schedule@18.0.0-next.3 + - @objectstack/types@18.0.0-next.3 + - @objectstack/plugin-pinyin-search@18.0.0-next.3 + ## 18.0.0-next.2 ### Minor Changes diff --git a/packages/verify/package.json b/packages/verify/package.json index e99556d63c3..7d862175334 100644 --- a/packages/verify/package.json +++ b/packages/verify/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/verify", - "version": "18.0.0-next.2", + "version": "18.0.0-next.3", "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/skills/objectstack-ai/SKILL.md b/skills/objectstack-ai/SKILL.md index 000637f0d88..9c9f6def51d 100644 --- a/skills/objectstack-ai/SKILL.md +++ b/skills/objectstack-ai/SKILL.md @@ -12,7 +12,7 @@ license: Apache-2.0 compatibility: Requires @objectstack/spec 18.x (Zod v4 schemas) metadata: author: objectstack-ai - version: "18.0.0-next.2" + version: "18.0.0-next.3" domain: ai tags: agent, tool, skill, knowledge, llm, embedding, mcp --- diff --git a/skills/objectstack-api/SKILL.md b/skills/objectstack-api/SKILL.md index 19be4ce894a..2d55cd4fa2f 100644 --- a/skills/objectstack-api/SKILL.md +++ b/skills/objectstack-api/SKILL.md @@ -14,7 +14,7 @@ license: Apache-2.0 compatibility: Requires @objectstack/spec 18.x (Zod v4 schemas) metadata: author: objectstack-ai - version: "18.0.0-next.2" + version: "18.0.0-next.3" domain: api tags: rest, endpoint, auth, realtime, server --- diff --git a/skills/objectstack-automation/SKILL.md b/skills/objectstack-automation/SKILL.md index e9affe94639..4bf131d95d7 100644 --- a/skills/objectstack-automation/SKILL.md +++ b/skills/objectstack-automation/SKILL.md @@ -13,7 +13,7 @@ license: Apache-2.0 compatibility: Requires @objectstack/spec 18.x (Zod v4 schemas) metadata: author: objectstack-ai - version: "18.0.0-next.2" + version: "18.0.0-next.3" domain: automation tags: flow, workflow, trigger, approval, state-machine, scheduled, webhook --- diff --git a/skills/objectstack-data/SKILL.md b/skills/objectstack-data/SKILL.md index be49e25a4be..09f3b49c2a4 100644 --- a/skills/objectstack-data/SKILL.md +++ b/skills/objectstack-data/SKILL.md @@ -15,7 +15,7 @@ license: Apache-2.0 compatibility: Requires @objectstack/spec 18.x (Zod v4 schemas) metadata: author: objectstack-ai - version: "18.0.0-next.2" + version: "18.0.0-next.3" domain: data tags: object, field, validation, index, relationship, hook, schema, permission, rls, security, seed, fixture --- diff --git a/skills/objectstack-formula/SKILL.md b/skills/objectstack-formula/SKILL.md index f4243bd6135..ff3e90f0b3b 100644 --- a/skills/objectstack-formula/SKILL.md +++ b/skills/objectstack-formula/SKILL.md @@ -13,7 +13,7 @@ license: Apache-2.0 compatibility: Requires @objectstack/spec 18.x and @objectstack/formula 18.x (CEL) metadata: author: objectstack-ai - version: "18.0.0-next.2" + version: "18.0.0-next.3" domain: expression tags: cel, formula, predicate, condition, validation, visibility, seed-dynamic --- diff --git a/skills/objectstack-i18n/SKILL.md b/skills/objectstack-i18n/SKILL.md index ee2e5d7616d..f3335b6bf48 100644 --- a/skills/objectstack-i18n/SKILL.md +++ b/skills/objectstack-i18n/SKILL.md @@ -13,7 +13,7 @@ license: Apache-2.0 compatibility: Requires @objectstack/spec 18.x (Zod v4 schemas) metadata: author: objectstack-ai - version: "18.0.0-next.2" + version: "18.0.0-next.3" domain: i18n tags: i18n, translation, locale, l10n, bundle, coverage --- diff --git a/skills/objectstack-platform/SKILL.md b/skills/objectstack-platform/SKILL.md index c064193125c..bbee24c8068 100644 --- a/skills/objectstack-platform/SKILL.md +++ b/skills/objectstack-platform/SKILL.md @@ -17,7 +17,7 @@ license: Apache-2.0 compatibility: Requires @objectstack/spec 18.x and @objectstack/core 18.x (Zod v4 schemas), Node 22+ metadata: author: objectstack-ai - version: "18.0.0-next.2" + version: "18.0.0-next.3" domain: platform tags: project, defineStack, driver, hono, plugin, kernel, service, requires, capability, DI, lifecycle, cli, deploy, ops --- diff --git a/skills/objectstack-query/SKILL.md b/skills/objectstack-query/SKILL.md index 2ddc4313d2f..d5a6400a665 100644 --- a/skills/objectstack-query/SKILL.md +++ b/skills/objectstack-query/SKILL.md @@ -11,7 +11,7 @@ license: Apache-2.0 compatibility: Requires @objectstack/spec 18.x (Zod v4 schemas) metadata: author: objectstack-ai - version: "18.0.0-next.2" + version: "18.0.0-next.3" domain: query tags: query, filter, sort, paginate, aggregate, ObjectQL, full-text --- diff --git a/skills/objectstack-ui/SKILL.md b/skills/objectstack-ui/SKILL.md index 87dc86bc463..0a6dcba4ed7 100644 --- a/skills/objectstack-ui/SKILL.md +++ b/skills/objectstack-ui/SKILL.md @@ -18,7 +18,7 @@ license: Apache-2.0 compatibility: Requires @objectstack/spec 18.x (Zod v4 schemas) metadata: author: objectstack-ai - version: "18.0.0-next.2" + version: "18.0.0-next.3" domain: ui tags: view, app, page, dashboard, report, chart, action, widget, doc --- diff --git a/skills/objectstack-upgrade/SKILL.md b/skills/objectstack-upgrade/SKILL.md index fdf54ef73da..00600a8eaca 100644 --- a/skills/objectstack-upgrade/SKILL.md +++ b/skills/objectstack-upgrade/SKILL.md @@ -16,7 +16,7 @@ license: Apache-2.0 compatibility: Requires `@objectstack/spec` and `@objectstack/cli` at the TARGET major; the chain replays from the spec's `MIGRATION_SUPPORT_FLOOR`. No network access required. metadata: author: objectstack-ai - version: "18.0.0-next.2" + version: "18.0.0-next.3" domain: process tags: upgrade, migration, protocol, major, retired-keys, tombstone, conversions, validate, report ---