- @objectstack/spec@10.3.0
- @objectstack/metadata-core@10.3.0
- Updated dependencies [b496498]
- @objectstack/spec@10.2.0
- @objectstack/metadata-core@10.2.0
- Updated dependencies [49da36e]
- Updated dependencies [ac79f16]
- @objectstack/spec@10.1.0
- @objectstack/metadata-core@10.1.0
-
e16f2a8: BREAKING: the system object
sys_departmentis renamed tosys_business_unit— object + member table (sys_department_member→sys_business_unit_member), fields, and i18n — with no compatibility alias. Any deployment holdingsys_departmentrows, or metadata that references the object by name (lookups, list views, queries, sharing/approval scopes), must migrate tosys_business_unit. A renamed shipped system object is a breaking change to the platform's public data surface, so this lands as a major. Verified per ADR-0059's pre-publish hotcrm gate: no published downstream consumer references the old name.ADR-0057 — ERP authorization core. Adds permission-grant access DEPTH (
own/own_and_reports/unit/unit_and_below/org), renamessys_department→sys_business_unit(no aliases — see BREAKING above), introduces the platform-ownedsys_user_roleassignment, and seeds stack-declaredroles/sharingRulesintosys_role/sys_sharing_ruleat boot (closes #2077). Hierarchy-relative scopes are delegated to a pluggableIHierarchyScopeResolver(open edition fails closed to owner-only;defineStackerrors withoutrequires: ['hierarchy-security']). Also fixes a latent over-grant whereengine.find({ filter })was ignored (driver readswhere) — normalizedfilter→wherein the engine.
-
2256e93: Setup nav: gate Organizations/Invitations on multi-org; enforce
requiresServiceserver-side (ADR-0057 addendum D10).rest-server'sfilterAppForUsernow honoursNavigationItem.requiresService— entries whose named kernel service isn't registered are dropped from the served app metadata (fail-open when the kernel can't be probed; previously the field was a frontend-only hint). AppliesrequiresService: 'org-scoping'to the Setup app's Organizations and Invitations entries, so they surface only in multi-org (multi-tenant) deployments and disappear in single-tenant. Business Units is intentionally left ungated — it is open per the open/paid seam + D12 ("pick people by BU"); only the hierarchy rollup capability is enterprise. -
7108ff3: Drop the unused
teamvalue fromsys_business_unit.kind(ADR-0057 addendum D11).The
teamkind collided head-on with the first-classsys_teamobject: akind='team'business unit walks the hierarchicalBusinessUnitGraphService, whilesys_teamis the flat better-auth collaboration grouping served byTeamGraphService.kindis a display-only categorisation hint (it does not change graph semantics) and had zero usages anywhere in the repo, so this is a safe narrowing with no data migration. New enum:company | division | department | office | cost_center. -
30c0313: Add
sys_user.primary_business_unit_idprojection (ADR-0057 addendum D12).Adds a denormalised
primary_business_unit_idlookup tosys_user, maintained by plugin-sharing as a projection ofsys_business_unit_member.is_primary(insert/update/delete hooks + a boot-time backfill). This makes "pick people by business unit" — the Dataverse filtered lookup / ServiceNow reference qualifier interaction — expressible as a plainwhere: { primary_business_unit_id: X }(and thus as alookupFilterspicker filter) with zero query-engine change, without traversing the membership junction.sys_business_unit_memberremains the effective-dated, matrix-friendly source of truth; the new column is a maintained projection, not a second source. Home is plugin-sharing (always loaded, owns the BU graph) rather than plugin-org-scoping, so the projection works in single-tenant deployments too. Picker filtering by BU is therefore an open (non-enterprise) capability — only hierarchy rollup stays paid. -
ae271d0: feat(identity): add an Org Chart tree view to
sys_business_unitsys_business_unitis already a self-referencing hierarchy (parent_business_unit_id, ADR-0057 D2) but Setup only exposed flat grids. Adds anorg_chartlist view (type: 'tree') that renders the hierarchy as an indented, expand/collapse tree-grid, listed first so it's the default tab. No schema change — the parent pointer and graph traversal already existed; this only surfaces them. Theactive/inactive/by_kind/allgrids stay for search, filter, and bulk edit. -
47d978a: Add
manager_id(self-lookup) tosys_user— the reporting chain that the ADR-0057own_and_reportshierarchy scope walks.The
own_and_reportsscope was implemented in the resolver but unbacked: nothing onsys_usermodelled a manager, so it always degraded to owner-only. This adds the field (+ en/zh/ja/es labels) and extends the scope-depth dogfood to prove the scope end-to-end — a user now sees their own records plus everyone down theirmanager_idchain.
-
61ed5c7: Complete the ADR-0057
sys_department→sys_business_unitrename in the Setup app and across the object's i18n (en / zh / ja / es).- Setup nav entry "Departments" → "Business Units" (
nav_departments→nav_business_units). sys_business_unit/sys_business_unit_memberfield labels and descriptions in the object definitions now read "business unit" instead of "department" (the generatedenlabels had been hand-updated ahead of the def; the def was the stale source).- All four locales' generated object translations aligned to 业务单元 / ビジネスユニット / Unidad de negocio.
Intentionally preserved: the
kindenum valuedepartment(a business unit can be of kind department) and the multi-concept node descriptions that list kinds. - Setup nav entry "Departments" → "Business Units" (
-
0df063e: Fix:
sys_business_unit/sys_teamcould not be created in single-tenant deployments.organization_idwasrequired, but single-tenant has nosys_organizationrow and nothing auto-stamps one (OrgScopingPlugin is multi-tenant-only), so every create failed withVALIDATION_FAILED: organization_id (required). Makeorganization_idoptional on both objects: single-tenant leaves it null; multi-tenant still auto-stamps it via OrgScopingPlugin and tenant-isolation RLS hides any null-org row (fail-closed), so there is no cross-tenant exposure. (sys_member / sys_invitation carry the samerequiredflag but are created only through better-auth org flows, which always supply an org — left unchanged.) -
ce13bb8: Single-tenant audit follow-ups (ADR-0057):
sys_member/sys_invitation: makeorganization_idoptional (same class as the sys_business_unit/sys_team fix #2178). Single-tenant has no org row and no auto-stamp; multi-tenant still auto-stamps via OrgScopingPlugin with null-org rows hidden by tenant-isolation RLS (fail-closed). Completes the org-scoped identity graph's single-tenant consistency.BusinessUnitGraphService.headOf(): add the missingorgScope()org filter (it queries under SYSTEM_CTX, bypassing RLS, so the scope is the only isolation). PreviouslyheadOf(buId)read a business unit'smanager_user_idby id alone — a cross-organization leak in multi-tenant. Now consistent withdescendants(). +regression test.
-
Updated dependencies [d7ff626]
-
Updated dependencies [2a1b16b]
-
Updated dependencies [e16f2a8]
-
Updated dependencies [e411a82]
-
Updated dependencies [a581385]
-
Updated dependencies [220ce5b]
-
Updated dependencies [3efe334]
-
Updated dependencies [feead7e]
-
Updated dependencies [6ca20b3]
-
Updated dependencies [5f875fe]
-
Updated dependencies [b469950]
- @objectstack/spec@10.0.0
- @objectstack/metadata-core@10.0.0
- Updated dependencies [e7f6539]
- Updated dependencies [2365d07]
- Updated dependencies [6595b53]
- Updated dependencies [fa8964d]
- Updated dependencies [36138c7]
- Updated dependencies [a8e4f3b]
- Updated dependencies [4c213c2]
- Updated dependencies [2afb612]
- @objectstack/spec@9.11.0
- @objectstack/metadata-core@9.11.0
- 4331adb: fix(i18n): add view form
end_user_controlstranslations for en, es-ES, ja-JP and zh-CN metadata-forms bundles. - Updated dependencies [db02bd5]
- Updated dependencies [641675d]
- Updated dependencies [94e9040]
- Updated dependencies [1f88fd9]
- Updated dependencies [1f88fd9]
- @objectstack/spec@9.10.0
- @objectstack/metadata-core@9.10.0
- @objectstack/spec@9.9.1
- @objectstack/metadata-core@9.9.1
- Updated dependencies [84249a4]
- Updated dependencies [11af299]
- Updated dependencies [d5774b5]
- Updated dependencies [134043a]
- Updated dependencies [90108e0]
- Updated dependencies [9afeb2d]
- Updated dependencies [6bec07e]
- Updated dependencies [601cc11]
- Updated dependencies [575448d]
- @objectstack/spec@9.9.0
- @objectstack/metadata-core@9.9.0
- Updated dependencies [97c55b3]
- Updated dependencies [1b1f490]
- @objectstack/spec@9.8.0
- @objectstack/metadata-core@9.8.0
- @objectstack/spec@9.7.0
- @objectstack/metadata-core@9.7.0
- Updated dependencies [d1e930a]
- Updated dependencies [71578f2]
- Updated dependencies [5e3a301]
- Updated dependencies [5db2742]
- @objectstack/spec@9.6.0
- @objectstack/metadata-core@9.6.0
- Updated dependencies [ee72aae]
- @objectstack/spec@9.5.1
- @objectstack/metadata-core@9.5.1
-
5be7102: i18n(metadata-forms): correct stale page-
typehelp text across localesThe page
typefield help text still described page types as "record, home, app, dashboard …" — listingdashboard(and implying grid/kanban/calendar) as page types, which is wrong after the ADR-0047 page-type cleanup: those are visualizations configured under Interface, not page kinds. Updated en / zh-CN / ja-JP / es-ES to "page kind — list / record / home / app / utility; visualizations live under Interface". Also fixed the stale zh-CNkindhelp text (it described "record / list / detail" instead of the record-page override mode). -
Updated dependencies [d08551c]
-
Updated dependencies [707aeed]
-
Updated dependencies [7a103d4]
-
Updated dependencies [4b01250]
- @objectstack/spec@9.5.0
- @objectstack/metadata-core@9.5.0
- Updated dependencies [060467a]
- Updated dependencies [0856476]
- Updated dependencies [fef38ec]
- Updated dependencies [b678d8c]
- Updated dependencies [b678d8c]
- Updated dependencies [b678d8c]
- @objectstack/spec@9.4.0
- @objectstack/metadata-core@9.4.0
- c802327: Marketplace Setup navigation is now plugin-owned (cloud ADR-0009):
MarketplaceProxyPlugincarries the "Browse Marketplace" entry andMarketplaceInstallLocalPlugincarries "Installed Apps" — no plugin mounted (e.g.OS_CLOUD_URL=off), no entry, no dead page. The two entries are removed from@objectstack/platform-objects' setup-nav contributions (ADR-0029 K2 ownership handoff).
- Updated dependencies [1ada658]
- Updated dependencies [3219191]
- Updated dependencies [290f631]
- Updated dependencies [50b7b47]
- Updated dependencies [f15d6f6]
- Updated dependencies [f8684ea]
- Updated dependencies [b4765be]
- @objectstack/spec@9.3.0
- @objectstack/metadata-core@9.3.0
- Updated dependencies [2f57b75]
- Updated dependencies [2f57b75]
- @objectstack/spec@9.2.0
- @objectstack/metadata-core@9.2.0
- Updated dependencies [b9062c9]
- @objectstack/spec@9.1.0
- @objectstack/metadata-core@9.1.0
- Updated dependencies [1817845]
- @objectstack/spec@9.0.1
- @objectstack/metadata-core@9.0.1
- Updated dependencies [4c3f693]
- Updated dependencies [0bf39f1]
- Updated dependencies [f533f42]
- Updated dependencies [1c83ee8]
- @objectstack/spec@9.0.0
- @objectstack/metadata-core@9.0.0
- @objectstack/spec@8.0.1
- @objectstack/metadata-core@8.0.1
- Updated dependencies [a46c017]
- Updated dependencies [b990b89]
- Updated dependencies [99111ec]
- Updated dependencies [d5a8161]
- Updated dependencies [5cf1f1b]
- Updated dependencies [9ef89d4]
- Updated dependencies [3306d2f]
- Updated dependencies [bc44195]
- Updated dependencies [9e2e229]
- @objectstack/spec@8.0.0
- @objectstack/metadata-core@8.0.0
- @objectstack/spec@7.9.0
- @objectstack/metadata-core@7.9.0
- Updated dependencies [06f2bbb]
- Updated dependencies [36719db]
- Updated dependencies [424ab26]
- @objectstack/spec@7.8.0
- @objectstack/metadata-core@7.8.0
-
023bf93: fix(spec): reject unknown top-level keys on
ObjectSchema.create()(#1535)ObjectSchemaBaseis a plainz.object({...})(Zod default.strip()), so any unknown top-level key passed toObjectSchema.create()—workflows, a typo'dvalidation/indexs, etc. — was discarded silently: no error, no warning, and a greentsc. Declarative metadata an author believed they shipped (e.g. object-levelworkflows: [...]) vanished from every built artifact, dead from day one. This is the metadata-shape analogue of ADR-0032's "no silent failure" principle.create()now rejects unknown top-level keys with a precise, fixable build error that names the offending key(s), suggests the intended key on a likely typo (validation→validations), and — for known-confusable keys likeworkflows— points authors at the supported mechanism (a lifecycle hooksrc/objects/<name>.hook.tsor a top-levelrecord_changeflow; there is no object-levelworkflows[]field). The factory signature also constrains excess keys tonever, so the mistake is caught attsctime as well as at build.The non-strict
ObjectSchema.parse()load path (registry/artifact validation) is unchanged.Also fixes two platform objects (
sys_secret,sys_setting_audit) that carried silently-strippedviews/scope/defaultViewNamekeys: their intended list views are migrated to the supportedlistViewsfield (type: 'list'→'grid') so they now render instead of being dropped. Theobjectstack-dataskill's CRM blueprint no longer teaches the non-existentworkflows[]shape. -
764c747: fix(metadata): home the metadata-storage objects in metadata-core and register them from ObjectQL
Standalone "host config" apps boot without
@objectstack/metadata's MetadataPlugin, so nobody registered the metadata-storage objects (sys_metadata,_history,_audit,sys_view_definition) into ObjectQL — their tables were never schema-synced and ObjectQL's own protocol (loadMetaFromDb/getMetaItems) failed withno such table: sys_metadataon every read.- Move the four storage-object definitions from
@objectstack/platform-objects/metadatato@objectstack/metadata-core(the lowest package shared by their real consumers);platform-objects/metadatanow re-exports them for back-compat. ObjectQLPluginregisters these objects itself (gated onenvironmentId === undefined, mirroringrestoreMetadataFromDb) so their tables always sync on platform/standalone kernels.- Gate the SQL driver's tenant-audit warning on actual multi-tenant mode —
organization_idnow exists on every table, so column presence alone no longer implies "tenant-scoped"; single-tenant boots no longer spam the warning for system writes.
- Move the four storage-object definitions from
-
Updated dependencies [b391955]
-
Updated dependencies [f06b64e]
-
Updated dependencies [023bf93]
-
Updated dependencies [764c747]
- @objectstack/spec@7.7.0
- @objectstack/metadata-core@7.7.0
-
7ae6abc: Fix
sys_userload failure after the validation-rule type trim (#1485)#1485 trimmed the unenforceable validation-rule types (
unique,async,custom) from theValidationRuleSchemadiscriminated union, butsys_userstill declared anemail_uniquerule withtype: 'unique'. Loading the object then threw aZodError("Invalid discriminator value … at validations[0].type"), failingplatform-objects.test.tsand turningmainred.The rule was redundant:
sys_useralready declares a unique index onemail(indexes: [{ fields: ['email'], unique: true }]), and the user table is managed by better-auth which enforces email uniqueness at the source. Removed the unenforceable validation rule; uniqueness remains enforced by the index. No other object uses a trimmed validation type. -
Updated dependencies [955d4c8]
-
Updated dependencies [c4a4cbd]
-
Updated dependencies [b046ec2]
-
Updated dependencies [2170ad9]
-
Updated dependencies [02d6359]
-
Updated dependencies [7648242]
-
Updated dependencies [8fa1e7f]
-
Updated dependencies [55866f5]
-
Updated dependencies [60f9c45]
- @objectstack/spec@7.6.0
- @objectstack/spec@7.5.0
- @objectstack/spec@7.4.1
-
c72daad: ADR-0029 D7 — Setup app navigation contributions.
Adds the UI-layer analog of object
own/extend: a package can contribute navigation items into an app it does not own, so a shared admin app can be a thin shell while each capability plugin ships the menu for the objects it owns.@objectstack/spec— newNavigationContributionSchema({ app, group?, priority, items }) and an optionalnavigationContributionsfield on the manifest.@objectstack/objectql—SchemaRegistry.registerAppNavContribution()plus lazy merge ingetApp/getAllApps(by target group id + priority, cloning so the stored app is never mutated); the engine wiresmanifest.navigationContributionsduring app registration.@objectstack/platform-objects— the Setup app becomes a shell of empty group anchors; its entries for platform-objects-owned objects move toSETUP_NAV_CONTRIBUTIONS.@objectstack/plugin-auth— registersSETUP_NAV_CONTRIBUTIONSalongside the Setup app it already registers.@objectstack/plugin-webhooks— contributes itsWebhooks/Webhook Deliveriesentries into the Setupgroup_integrationsslot (it ownssys_webhook/sys_webhook_deliveryper K2.a), demonstrating end-to-end cross-plugin contribution.
The rendered Setup nav is identical to the former static artifact — just assembled from its owners. A disabled/absent capability contributes nothing and its slot stays empty (in addition to the existing
requiresObjectgating). This unblocks moving each remaining K2 domain's menu out of the monolith with its objects. -
eea3f1b: ADR-0029 K0 + K2.a — single-owner invariant and webhooks ownership pilot.
K0 (
@objectstack/objectql) — addSchemaRegistry.assertSingleOwnerPerObject(), the install-time backstop for the kernel-decomposition invariant: every registered object must resolve to exactly oneowncontributor. A second cross-package owner is already rejected at registration time; this additionally catches "extend with no owner" (which would otherwise resolve to nothing). Call after kernel bootstrap completes.K2.a (
@objectstack/plugin-webhooks←@objectstack/platform-objects) — move thesys_webhookobject definition out of theplatform-objectsmonolith into@objectstack/plugin-webhooks, where it joins its siblingsys_webhook_deliveryso the plugin owns both its data model and behavior as one unit.sys_webhookis no longer exported from@objectstack/platform-objects(or its/integrationsubpath, now an empty barrel); import it from@objectstack/plugin-webhooks/schemainstead. Runtime behavior is unchanged — the webhook plugin already registeredsys_webhookat runtime; only the definition's home moved. Setup-app navigation (which referencessys_webhookby name) and existing i18n bundles (object-name keyed) continue to work. Per ADR-0029 D8, migrating the object's i18n extraction into the plugin is a tracked follow-up before the next translation regeneration. -
e478e0c: ADR-0029 K2 — security domain ownership (RBAC + sharing) + Setup nav contributions.
Moves the security objects out of the
@objectstack/platform-objectsmonolith into the two capability plugins that already register and operate them, split by concern (the two are orthogonal — sharing objects never reference RBAC objects):@objectstack/plugin-security(RBAC) gainssys_role,sys_permission_set,sys_user_permission_set,sys_role_permission_set, and thedefaultPermissionSetsseed (which itsbootstrap-platform-adminalready consumes). The RBAC + default-permission-set tests move with them.@objectstack/plugin-sharinggainssys_record_share,sys_sharing_rule,sys_share_link.@objectstack/platform-objectsno longer defines/exports any security objects; the/securitysubpath is now an empty barrel. Runtime is unchanged (both plugins already registered these objects at runtime).
D7 navigation — the Setup app's
group_access_controlis now assembled from three sources:plugin-securitycontributes Roles / Permission Sets (priority 100),plugin-sharingcontributes Sharing Rules / Record Shares (priority 200), andplatform-objectskeeps only API Keys (sys_api_key, an identity object, priority 300) — preserving the original menu order.i18n (D8) — the objects are removed from the
platform-objectsi18n extract config; existing generated bundles keep working at runtime (object-name keyed). Migrating the i18n extraction to the owning plugins remains the tracked follow-up. -
4cc2ced: ADR-0029 K2.b — approvals domain ownership + Setup nav contribution.
Moves
sys_approval_request/sys_approval_actionout of the@objectstack/platform-objectsmonolith into@objectstack/plugin-approvals, which already registers and operates them — so the plugin now owns its data model, behavior, and admin menu as one unit.- The object definitions move to
plugin-approvals;platform-objectsno longer exports them from/audit. Runtime is unchanged (the plugin already registered them at runtime). - D7 navigation — the Setup app's
group_approvalsentries (Requests,Action History) move out ofplatform-objects'SETUP_NAV_CONTRIBUTIONSintoplugin-approvals'navigationContributions. The plugin fills the slot it owns; when the plugin is absent the slot stays empty. - i18n (D8) — the objects are removed from the
platform-objectsi18n extract config; their existing generated translation bundles keep working at runtime (object-name keyed). Migrating the i18n extraction/bundles to the plugin remains the tracked cross-cutting follow-up (best done with theos i18n extracttooling, not hand-edited generated files).
- The object definitions move to
-
13632b1: ADR-0030 P0 (framework) — converge notifications onto a single ingress and the layered model. Every producer now publishes through
NotificationService.emit(EmitInput); the in-app inbox is a materialization of delivery, not a row producers write.Single ingress (
@objectstack/service-messaging) — breakingMessagingService.emittakes the newEmitInputcontract (topic/audience/payload/severity/dedupKey/source/actorId/organizationId/channels) instead of the flatNotificationshape. It writes the L2sys_notificationevent (idempotent ondedupKey), resolves the audience, then fans out; it returns{ notificationId, deduped, deliveries, delivered, failed }.- New
sys_notification_receiptobject — the read-state spine (delivered|read|clicked|dismissed), keyed(notification_id, user_id, channel). The inbox channel writes adeliveredreceipt on materialization. sys_inbox_message: addsnotification_id/delivery_id, dropsread(read-state moved to the receipt), adds the userminelist view.
Event re-model (
@objectstack/platform-objects) — breakingsys_notificationis re-modeled from a per-user inbox into the L2 event (topic,payload,severity,dedup_key,source_*,actor_id). Removesrecipient_id/is_read/read_at/type/title/body/url/actor_nameand the inbox actions/views. App-nav: the account inbox points atsys_inbox_message; Setup shows the notification event log.
Producers routed through
emit()@objectstack/service-automation: thenotifynode maps its config toEmitInput.@objectstack/plugin-audit: collaboration@mention→collab.mentionand assignment →collab.assignment(both with adedupKey); no more directsys_notificationwrites. Collaboration notifications now requireMessagingServicePlugin(they degrade to a warn otherwise).
Migration (
@objectstack/metadata)- Idempotent
migrateSysNotificationToEventsplits legacysys_notificationinbox rows intosys_inbox_message+ receipts and rewrites the event row.
Startup (
@objectstack/cli,@objectstack/runtime)messagingis now a foundational capability. Onobjectstack serveit is added toALWAYS_ON_CAPABILITIES(every non-minimalpreset starts it); on cloud per-project kernels the capability loader expandsrequiresto addmessagingwheneverauditis present. This keeps collaboration@mention/ assignment notifications (which now flow through the pipeline) working out of the box on both paths.--preset minimalopts out.
The Console bell repoint (objectui) and phases P1–P3 are tracked in
docs/handoff/adr-0030-notification-convergence.md.
-
23c7107: ADR-0020 — converge the three "state machine" declaration shapes to one enforced
state_machinevalidation rule.Before this change a record state machine could be declared three ways (a
workflowmetadata type, anobject.stateMachinesmap, or astate_machinevalidation rule) and none of them were enforced at runtime — a declarative guardrail that was pure decoration, and a hallucination trap for AI authors.Enforcement (
@objectstack/objectql)- New
validation/rule-validator.tsevaluates the object'svalidationsunion on the write path:evaluateValidationRules,needsPriorRecord, and thelegalNextStatesintrospection helper (all exported from the package root). state_machinerules reject illegalfieldtransitions on update (with the rule'smessage);script/cross_fieldpredicate rules now also fire (they were silently broken on PATCH updates because only the patch, not the prior record, was available). The engine plumbs the prior record into rule evaluation on single-row update; multi-row (updateMany) updates log a warning and skip rule evaluation rather than enforce on incomplete data.
Convergence / retirement (
@objectstack/spec) — breaking- Retires the
workflowmetadata type (removed from the metadata-type enum, the registry, the schema map, theworkflowscollection key, and the plural→singular mapping). - Removes the
object.stateMachinesmap and thestack.workflowsarray. Thestate_machinevalidation rule is the single canonical home. - The XState-style
StateMachineSchemafile is kept (still used by the agent conversation lifecycle and the discovery protocol); only its role as theworkflowmetadata-type backing schema was removed. The optionalworkflowRPC service surface (CoreServiceName.workflow,/api/v1/workflow,IWorkflowService) is kept as a documented follow-up.
Introspection (
@objectstack/runtime)- Adds
GET /metadata/objects/:name/state/:field?from=:state, returning the legal next states for a field (next: nullwhen no FSM governs the field,[]for a declared dead-end) so UIs/agents read the transition table instead of re-deriving it.
Surfaces (
@objectstack/platform-objects,@objectstack/cli)- Studio drops the standalone "Workflow Rules" nav (state machines are edited alongside the object's other validation rules).
explainno longer listsworkflowas a related metadata type.
Migration: replace a
workflow/StateMachineConfigdeclaration with astate_machinevalidation rule on the object (field+{ from: [allowedTo] }transition table), and move any side-effecting actions (emails, task creation) into a record-triggered or scheduled Flow (ADR-0019). See the migratedexamples/app-crmflows for the pattern. - New
-
4404572: ADR-0029 D8 — migrate i18n ownership for the moved domains to their plugins.
The object translations for the domains decomposed in K2.a/K2.b/K2 previously lived in the
@objectstack/platform-objectsgenerated bundles even though the objects now live in their capability plugins. This moves each domain's i18n extraction + bundles to the owning plugin, preserving every hand-translated string (zh-CN / ja-JP / es-ES):- Each plugin gains a build-time
scripts/i18n-extract.config.tsand asrc/translations/bundle ({locale}.objects.generated.ts+ anindex.tsbarrel), generated withos i18n extractand self-baselined so re-runs preserve translations. - Each plugin loads its bundle at runtime on
kernel:readyviai18n.loadTranslations(the i18n service is optional — load is best-effort).plugin-webhooks←sys_webhook,sys_webhook_deliveryplugin-approvals←sys_approval_request,sys_approval_actionplugin-security←sys_role,sys_permission_set,sys_user_permission_set,sys_role_permission_setplugin-sharing←sys_record_share,sys_sharing_rule,sys_share_link
@objectstack/platform-objectstranslation bundles are regenerated to drop those objects' keys (its extract config already excluded them); all other objects' translations and the metadata-form bundles are preserved.
Net runtime effect is unchanged (same translations load, now contributed by the package that owns each object) — closing the D8 follow-up tracked since K2.a.
- Each plugin gains a build-time
-
82eb6cf: Fix system-metadata translations: locale fallback, app/dashboard localization, and coverage gaps.
Switching the UI language left many surfaces in English. Three root causes are addressed:
-
Locale fallback (server). The metadata translation resolver (
@objectstack/speci18n-resolver) now resolves a requested locale against the locales actually present in the bundle (exact → case-insensitive → base-language → variant), so a request forzhcorrectly hits thezh-CNbundle instead of falling back to English. This mirrorsresolveLocalein@objectstack/coreand benefits every resolver (objects, views, actions, settings, metadata forms). -
App & dashboard localization (server). Added
translateAppandtranslateDashboardresolvers and wiredapp/dashboardinto the REST/metatranslation path. App labels, sidebar/navigation group labels, and dashboard titles/widgets were previously never localized at the API boundary even though the translation data existed. -
Coverage & quality (data). Added translations for the previously untranslated platform objects
sys_share_link,sys_view_definition, andsys_metadata_audit(and registered them in the i18n-extract config so future extractions keep them). Replaced English placeholder strings left in thezh-CN/ja-JP/es-ESobject and metadata-form bundles (notably actionconfirmText/successMessageprompts). Added the missinges-ESbuilt-in Settings bundle in@objectstack/service-settings.
-
-
c381977: Harden the notification pipeline: race-safe dedup + opt-in retention (ADR-0030).
Race-safe dedup.
sys_notification.dedup_keyis now declared a UNIQUE index (was a plain index), andemit()converges on a unique-key conflict: the pre-insertdedup_keycheck is a fast-path, but if a concurrentemitraced past it and inserted first, our insert hits the violation — we catch it and converge to the winner's event (a dedup hit) instead of throwing or double-emitting. This mirrors the delivery outbox's enqueue convergence and stops a record-change storm from producing duplicate bell notifications. SQL treats NULLs as distinct, so the common events with nodedup_keyare unconstrained. (Enforcement is per-driver: where declared indexes are materialized the conflict path activates; drivers that don't materialize them fall back to the best-effort fast-path — the catch is simply never taken. Note the SQL driver currently doesn't sync declared object indexes, which already affects the delivery/receipt unique indexes — tracked separately.)Opt-in retention. New
NotificationRetentionsweeper + plugin optionsretentionDays/retentionSweepMs. Everyemit()writes asys_notificationevent (plus delivery/materialization/receipt rows), so a high-frequency periodic flow grows the tables unbounded. WhenretentionDays > 0, a low-frequency sweep (default hourly, timerunref'd) bulk-deletes events, deliveries, inbox messages and receipts older than the cutoff — a notification ages out wholesale, keeping the model consistent (no danglingnotification_id) and the bell (recent-only) unaffected. The delivery row's epoch-mscreated_atvs the others' ISOcreated_atis handled per target. Default off — no notification data is deleted without explicit operator policy. Each target is isolated (one object's failure doesn't abort the sweep), and the sweep runs under a system context (retention is a cross-tenant operator policy).Tests: +7
service-messagingcases (converge-on-conflict, non-conflict rethrow, retention cutoff-formatting per target, no-engine / non-positive no-ops, failure isolation, missing-count) — 102 passing. -
Updated dependencies [23c7107]
-
Updated dependencies [c72daad]
-
Updated dependencies [f115182]
-
Updated dependencies [2faf9f2]
-
Updated dependencies [2faf9f2]
-
Updated dependencies [2faf9f2]
-
Updated dependencies [58b450b]
-
Updated dependencies [82eb6cf]
-
Updated dependencies [13d8653]
-
Updated dependencies [ff3d006]
-
Updated dependencies [5e831de]
- @objectstack/spec@7.4.0
- Updated dependencies [5e7c554]
- @objectstack/spec@7.3.0
- @objectstack/spec@7.2.1
- @objectstack/spec@7.2.0
-
6228609: Account App: route Profile to a custom React component instead of the generic sys_user record page.
The Account App's
nav_account_profileentry switched fromtype: 'object'(sys_user record, current user id) totype: 'component'withcomponentRef: 'account:profile_card'. End users now see a settings-form-style "My Profile" card (avatar / name / password / SSO recovery) registered by the Console runtime, while thesys_userslotted record page (SysUserDetailPage) is unchanged and remains the admin view reached from Setup → Users.This is a behavioural change for any Studio override that mutates
nav_account_profile: the entry no longer hasobjectName,recordId, orrequiresObject. Override consumers should drop those fields and targetcomponentRef: 'account:profile_card'(or restore the previous nav item type explicitly).Requires a Console build that registers
account:profile_card(included in the matching@object-ui/consolerelease pinned via.objectui-sha).Verified end-to-end: login → Account App → 个人资料 sidebar item →
/_console/apps/account/component/account/profile_cardrenders the React Profile card; editing Name and clicking Save Changes POSTs/api/v1/auth/update-user(200) and persists.Also removes the
nav_account_preferencesentry that exposed the rawsys_user_preferencetable as a "Preferences" page in the Account App.sys_user_preferenceis an internal key-value store the UI uses for state likeui.recent,ui.favorites, theme, and sidebar collapse — not a user-curatable settings surface. A futureaccount:preferences_cardReact component should provide curated theme / locale / timezone / notifications toggles when needed. The correspondingnav_account_preferencesi18n labels were removed from all locale bundles (en / es-ES / ja-JP / zh-CN).The upstream
@object-ui/consolerelease also fixes a latentuseStatebug in the same ProfilePage: when mounted under<Suspense>beforeAuthProviderresolves,useris null on first render andsetName(user?.name ?? '')initialised to''with no follow-up sync. AuseEffectnow mirrorsuser.nameinto local state. This was masked when the page was only reached via the System Hub route (whereAuthGuardensured user was already loaded) and is exposed by the new mount path. -
Updated dependencies [47a92f4]
- @objectstack/spec@7.1.0
-
74470ad: New
accountApp for self-service identity management +App.hiddenshell hintAdds a dedicated Account App (
name: 'account', iconuser-circle) that exposes the three end-user identity surfaces:- Two-Factor Authentication —
sys_two_factor - Linked Accounts —
sys_account - OAuth Applications —
sys_oauth_application
The app declares no
requiredPermissions, so every authenticated user can reach it — unlike Setup, which requiressetup.accessand therefore excludes the defaultmember_defaultpermission set. Combined with the C-tierresultDialogactions already shipped on these objects (2FA QR + backup codes, OAuthclient_secretreveal,link_socialredirect), this replaces the legacy standaloneapps/accountSPA with a single console + metadata-driven surface.New
App.hidden: booleanfield (packages/spec/src/ui/app.zod.ts) hides an app from the top-level App Switcher. Hidden apps stay fully routable and permission-checked; the shell is expected to surface them through the avatar / user dropdown instead. Mirrors the GitHub Settings / Google account chip / Salesforce Personal Settings pattern. The Account app is the first user.Wiring:
plugin-authregistersACCOUNT_APPalongsideSETUP_APP/STUDIO_APP(packages/plugins/plugin-auth/src/auth-plugin.ts). The legacy duplicate entries inside Setup's Advanced group are kept unchanged — they remain admin-only for tenant-wide inspection.Follow-up for objectui: the shell's
AppSwitcherand avatarDropdownMenuneed updating to honourapp.hidden(filter hidden apps out of the switcher; render them as dropdown menu entries). Tracked separately. - Two-Factor Authentication —
-
d29617e: Add
Action.resultDialogfor one-shot reveal of API responsesSome platform operations return values the user MUST copy now because they cannot be retrieved later — TOTP enrollment URIs, OAuth client secrets, backup recovery codes. Previously these were handled by bespoke account-app pages because actions only surfaced a
successMessagetoast.This change adds:
-
Action.resultDialog— describes a post-success modal that renders selected fields fromresult.data. Supportsqrcode,code-list,secret,text, andjsonfield formats. When set, renderers SHOULD suppresssuccessMessageand require explicit acknowledgement. -
Action.targetinterpolation contract — formalised TSDoc spelling out the${param.X}and${ctx.X}substitution rules (with mandatoryencodeURIComponentfor URL query positions). Used by redirect-style actions likelink_social.
New / updated platform actions:
sys_two_factor:enable_two_factornow reveals TOTP URI + backup codes; addedregenerate_backup_codes.sys_oauth_application:rotate_client_secretnow reveals the new secret; addedcreate_oauth_applicationtoolbar action.sys_account: addedlink_socialtoolbar action (type:url, templated target) for self-service identity linking.
These let the Setup app cover OAuth-app registration, 2FA enrollment, and social-account linking entirely through metadata, removing the last must-have reasons to ship a separate
apps/accountSPA.Renderer-side work (separate PR in
objectui): consumeresultDialog, implement${param}/${ctx}interpolation, shipResultDialogcomponent. Seec-tier-renderer-contract.mddesign note. -
-
257954d: Organization detail page — Members / Invitations / Teams tabs (slotted Page)
Adds a record-detail Page for
sys_organization(SysOrganizationDetailPage) so admins can manage the entire membership graph from a single record view instead of switching between three separate Setup list views.The page uses
kind: 'slotted'and overrides only thetabsslot — header, actions, highlights, details and discussion fall through to the synthesized default, so the existing record-header actions (Set Active,Edit,Delete,Leave Organization) are preserved unchanged.Three tabs, each a
record:related_listscoped byorganization_id:- Members —
sys_member(user, role, joined) - Invitations —
sys_invitation(email, role, status, expires, inviter) - Teams —
sys_team(name, created, updated)
Per-row actions defined on each child object (
invite_user,cancel_invitation,remove_member,transfer_ownership,create_team, …) are inherited unchanged — no admin endpoint is re-declared here.Deliberately omitted:
- OAuth Apps —
sys_oauth_applicationis owned byuser_id, notorganization_id; it surfaces on the user's Account view instead. - SSO — no
sys_sso*object exists yet; will become a fourth tab when better-auth's SSO plugin lands.
Package wiring:
@objectstack/platform-objectsexposes a new./pagessubpath export and re-exportsSysOrganizationDetailPagefrom the root.plugin-authregisters it via the existingmanifest.register({ ..., pages: [SysOrganizationDetailPage] })call alongside the platform apps and dashboards.
Verified end-to-end on the console-starter shell against
example-crm— the three tabs render and the Members/Teams tables populate with the rows better-auth creates automatically when an org is provisioned. - Members —
-
d29617e: Add self-service account & invitation actions on
sys_*objects so the Setup App can host the day-to-day "account settings" affordances the standalone Account SPA used to own — no per-page React code needed.New actions:
sys_userupdate_my_profile— wrapsPOST /api/v1/auth/update-user(name + image)change_my_password— wrapsPOST /api/v1/auth/change-password(current + new + optional revoke-other-sessions)change_my_email— wrapsPOST /api/v1/auth/change-email(verification email is sent to the new address)delete_my_account— wrapsPOST /api/v1/auth/delete-user(requires current password)
sys_invitationaccept_invitation— wrapsPOST /api/v1/auth/organization/accept-invitationreject_invitation— wrapsPOST /api/v1/auth/organization/reject-invitation
sys_membertransfer_ownership— wrapsPOST /api/v1/auth/organization/update-member-rolewithrole: 'owner'(better-auth auto-demotes the previous owner to admin)
All four
sys_userself-service actions are gated byvisible: 'record.id == ctx.user.id'so they only render on the signed-in user's own row — they never leak into the admin Users list. The twosys_invitationrecipient actions userecord.email == ctx.user.email && record.status == 'pending'so they only appear on the user's incoming invitations. -
010757b: Fix two self-service identity action bugs:
sys_two_factorwas missing theverifiedboolean column that better-auth's two-factor plugin writes during enrollment. Without it the/2fa/enableendpoint 500'd withtable sys_two_factor has no column named verified. AddedField.boolean({ defaultValue: true })to match the better-auth schema.sys_account.link_socialaction'scallbackURLstill pointed at the pre-migration Setup path (/apps/setup/system/sys_account). Updated to/apps/account/sys_accountso users land back on the linked-accounts view after the OAuth dance.
-
Updated dependencies [74470ad]
-
Updated dependencies [d29617e]
-
Updated dependencies [dc72172]
- @objectstack/spec@7.0.0
- @objectstack/spec@6.9.0
- @objectstack/spec@6.8.1
-
c8b9f57: Metadata Admin engine — protocol foundations.
This is the backend half of the unified Metadata Admin shipped in the Setup app. The framework now exposes everything the engine needs to render a directory tile, schema-driven form, layered diff, references graph, and destructive-change confirmation for every registered metadata type.
GET /api/v1/meta/typesis now type-rich. Each entry includes{ icon, domain, schema (JSONSchema), allowOrgOverride, allowRuntimeCreate, supportsOverlay, ui? }so the client can render without a second round-trip per type.GET /api/v1/meta/:type/:name/referencesscans every registered metadata type for pointers to the given item (object fields, view sources, flow targets, permission objects, …) and returns the inbound edges so the UI can warn before deletes.GET /api/v1/meta/:type/:name?layers=code,overlay,effectivereturns each layer separately rather than the merged effective document, powering the 3-state diff editor (code source / overlay / effective).- Destructive-change detection on
PUT /api/v1/meta/object/:nameandPUT /api/v1/meta/field/:name: rejects field type narrowing, required toggled on without a default, removed enum values, etc., unless the client opts in withforce=true. - Env-var registry patch:
OBJECTSTACK_METADATA_WRITABLE=object,field,permission,view,…flipsallowOrgOverrideon for the listed types at boot, enabling runtime overlays for production without re-deploying spec. - New guide: Adding a Metadata Type walks through registry entry + Zod schema + optional custom editor.
Setup app navigation now uses the new component-route variant (
{ type: 'component', componentRef: 'metadata:directory' }) — the temporary/dev/metaroute is removed. -
45d27c5: Setup App: added a Data Model navigation group with Objects and Fields entries that open filtered list views of
sys_metadata.To support the new entries,
sys_metadata.listViewsnow includesonly_objects,only_fields, andall_metadata— each filtered bytypeand projecting a curated column set (name, namespace, scope, managed_by, state, updated_at). The new list views are the read side of the protocol-driven metadata editing flow; the matching write surface is provided byMetadataObjectsPage/MetadataFieldsPagein@object-ui/plugin-designer(separate package), which call the existing/api/v1/meta/*REST endpoints.No behavioural changes to the metadata REST endpoints themselves; no migration required.
- Updated dependencies [6e88f77]
- Updated dependencies [c8b9f57]
- @objectstack/spec@6.8.0
- @objectstack/spec@6.7.1
-
4f9e9d4: Setup App: complete the Configuration settings pages.
Setup App navigation
The Configuration group now lists every built-in settings namespace (previously Storage was missing entirely, and Knowledge had no entry):
- Branding · Email · File Storage · AI & Embedder · Knowledge · Feature Flags
Order in the left-nav now matches
builtinSettingsManifestsso the "All Settings" index and the left-nav stay aligned.AI manifest — embedder section
ai.manifest.tsnow ships an Embedder section in addition to the existing chat-LLM section. Knobs:embedder_provider—none(default) /openai/azure/dashscope(阿里通义) /zhipu(智谱) /siliconflow(硅基流动) /doubao(火山引擎) /minimax/ollama/custom. Preset list mirrors@objectstack/embedder-openai'sOPENAI_COMPATIBLE_PRESETS.embedder_api_key— encrypted password.embedder_model— free text with documented examples per provider.embedder_base_url— visible forcustom/azureonly.embedder_dimensions— optional Matryoshka override.embedder_batch_size—embed()chunk batch size.- Test action wired to
POST /api/settings/ai/test_embedder— fallback validates form completeness; real probe lives inservice-ai/service-knowledge.
New
knowledgesettings manifestknowledge.manifest.tsis the canonical surface for RAG infrastructure:adapter—memory/turso/ragflow.- Turso group —
turso_url(libsql://, file:, :memory:) + encryptedturso_auth_token. Leaving URL blank means "reuse the tenant's primary libSQL connection" — the recommended cloud setup. - RAGFlow group — base URL + encrypted API key + default dataset id.
- Indexing defaults —
chunk_target,chunk_overlap,over_fetch. - Permissions —
enforce_rlsdefaults totrue(security-critical; toggling off skips the platform's unique RLS re-check on every hit). - Test action wired to
POST /api/settings/knowledge/test.
Translations
Full
aiandknowledgetranslation blocks added to bothen.tsandzh-CN.ts. Storage block had translations already.Tests
ai.manifest.test.ts: +5 cases covering embedder select, encryption, test action wiring, and embedder handler validation across 5 provider shapes (none / ollama / OpenAI-compatible cloud / custom / azure).knowledge.manifest.test.ts: 20 new cases covering manifest shape, adapter selection, secret encryption, defaultenforce_rls=true, test handler validation across all 3 adapters and payload merging.
78/78 tests pass in
@objectstack/service-settings.
- Updated dependencies [430067b]
- Updated dependencies [4f9e9d4]
- @objectstack/spec@6.7.0
- Updated dependencies [a49cfc2]
- @objectstack/spec@6.6.0
- @objectstack/spec@6.5.1
- @objectstack/spec@6.5.0
- Updated dependencies [f8651cc]
- Updated dependencies [f8651cc]
- Updated dependencies [0bf6f9a]
- @objectstack/spec@6.4.0
- @objectstack/spec@6.3.0
- Updated dependencies [b4c74a9]
- @objectstack/spec@6.2.0
- @objectstack/spec@6.1.1
- Updated dependencies [93c0589]
- @objectstack/spec@6.1.0
-
944f187: # v5.0 —
project→environmenthard renameThe runtime concept previously called "project" (per-tenant business workspace; Org → Project → Branch hierarchy; per-project ObjectKernel, per-project DB, per-project artifact) is now uniformly called "environment".
This is a hard rename with no aliases, deprecation shims, or compatibility layer. Upgrade requires a coordinated update of CLI, runtime, server, and any clients calling the REST API.
Note: "project" in the npm / monorepo sense (the framework itself,
package.json, tsconfig project references, vitestprojectsconfig) is unchanged.- Flags renamed:
--project/-p→--environment/-e(os publish,os rollback)--project-id→--environment-id(os dev)
- Default local env id:
proj_local→env_local. - Env var:
OS_PROJECT_ID→OS_ENVIRONMENT_ID. - Command group renamed:
os projects ...→os environments ...(bind,create,list,show,switch). - Persisted auth-config key:
activeProjectId→activeEnvironmentId.
- Scoped routes:
/api/v1/projects/:projectId/...→/api/v1/environments/:environmentId/.... - Cloud control-plane routes:
/api/v1/cloud/projects/...→/api/v1/cloud/environments/...(including/cloud/environments/:id/artifact,/cloud/environments/:id/metadata,/cloud/environments/:id/credentials/rotate, etc.). - Header:
X-Project-Id(and lowercasex-project-id) →X-Environment-Id(x-environment-id). - Route param name in handlers:
req.params.projectId→req.params.environmentId. - Hostname-routing and tenant-resolution code-paths use
environmentIdend-to-end.
- Exported symbols (no aliases):
createSystemProjectPlugin→createSystemEnvironmentPluginSYSTEM_PROJECT_ID→SYSTEM_ENVIRONMENT_IDProjectArtifactSchema→EnvironmentArtifactSchemaPROJECT_ARTIFACT_SCHEMA_VERSION→ENVIRONMENT_ARTIFACT_SCHEMA_VERSIONObjectOSProjectPlugin→ObjectOSEnvironmentPlugincreateSingleProjectPlugin→createSingleEnvironmentPlugin
- Plugin identifier strings:
com.objectstack.runtime.objectos-project→objectos-environmentcom.objectstack.studio.single-project→single-environmentcom.objectstack.multi-project→multi-environmentcom.objectstack.runtime.system-project→system-environment
- Provisioning hook:
provisionSystemProject→provisionSystemEnvironment.
- Column renames on
sys_metadataandsys_metadata_history:project_id→environment_id. - Column renames on
sys_activity:project_id→environment_id(plus index). - Object renames in platform-objects metadata:
sys_project→sys_environment(lookup targets),sys_project_member→sys_environment_member,sys_project_credential→sys_environment_credential. - Auth-context field:
active_project_id→active_environment_id. - JSON schemas under
packages/spec/json-schema/system/:ProjectArtifact*.json→EnvironmentArtifact*.json(regenerated at build).
A new migration
migrateProjectIdToEnvironmentId(packages/metadata/src/migrations/migrate-project-id-to-environment-id.ts) auto-runs fromDatabaseLoader.ensureSchema()on bootstrap and rewrites any existingproject_idcolumn onsys_metadata/sys_metadata_historytoenvironment_id(idempotent, best-effort). Existing rows are preserved.The legacy reverse migration
migrateEnvIdToProjectIdis retained verbatim for historical / disaster-recovery use; it is not auto-run.-os publish --project proj_xyz +os publish --environment env_xyz -curl -H "X-Project-Id: env_xyz" https://api.example.com/api/v1/data/customer +curl -H "X-Environment-Id: env_xyz" https://api.example.com/api/v1/data/customer -OS_PROJECT_ID=env_xyz os dev +OS_ENVIRONMENT_ID=env_xyz os dev -import { createSystemProjectPlugin, SYSTEM_PROJECT_ID } from "@objectstack/runtime"; +import { createSystemEnvironmentPlugin, SYSTEM_ENVIRONMENT_ID } from "@objectstack/runtime"; -import { ProjectArtifactSchema } from "@objectstack/spec"; +import { EnvironmentArtifactSchema } from "@objectstack/spec";
If you maintain a Cloud control-plane deployment, the
cloudrepository must be updated in lockstep to pick up the new plugin identifier strings (single-environment,multi-environment,objectos-environment). - Flags renamed:
- Updated dependencies [629a716]
- Updated dependencies [dbc4f7d]
- Updated dependencies [944f187]
- @objectstack/spec@6.0.0
-
bab2b20: feat(approvals): execution-pinned approval processes (ADR-0009)
When an approval request is submitted, the engine now records a
process_hashonsys_approval_request— the sha256 of the approval process body resolved throughMetadataRepository. While the request is in flight,approve/reject/recallresolve the pinned process body viaMetadataRepository.getByHash. Upgrading the approval process definition mid-flight therefore no longer affects requests that already started against the previous version.Behavior:
sys_approval_requestgains aprocess_hashcolumn (text, nullable, read-only). Existing rows keep working — the engine falls back to the currentsys_approval_processprojection when the column is empty.ApprovalServiceOptionsaccepts an optionalmetadataRepo. When omitted (e.g. defining processes purely through the runtime API or in unit tests), pinning is silently disabled and the service behaves as before.ApprovalsServicePluginlooks up the metadata service from the kernel and wires its repository automatically.- The metadata-core local
MetadataTypeSchemaenum was realigned with the canonical@objectstack/spec/kernelenum (drift fix:approval,field,function,service, …).
This is the first user-visible consumer of the
executionPinnedcapability introduced in ADR-0009.
-
f0f7c27: Add
mark_read/mark_unreadrow actions tosys_notificationand polish listView columns + grouping.- Row-level
mark_read/mark_unreadactions guarded by CELvisibleexpressions so each only renders on rows in the appropriate state. Both use the generic PATCH/api/v1/data/sys_notification/{id}endpoint withbodyExtrato flipis_read(and clearread_aton unmark). - Reordered listView columns to lead with
title+actor_name(the "who did what" users actually scan) and demotetypeto a chip column. mineview now groups bytypeso mention/assignment storms don't bury system or task_due rows.
mark_all_readis intentionally not added server-side — there's no bulk PATCH primitive on the REST surface yet, and the popover already handles multi-row mark-all client-side via N single-row PATCHes (InboxPopover.tsx→AppHeader.markAllRead). - Row-level
-
b806f58: Scope
sys_uservisibility to fellow organization members.The default RLS policy on
sys_userwasid = current_user.id, which meant @-mention pickers, owner/assignee lookups, reviewer selectors and the user roster all returned just the current user. The RLS compiler doesn't support subqueries, so aid IN (SELECT user_id FROM sys_member ...)policy isn't expressible.This change:
- Pre-resolves
org_user_ids(the IDs of all users in the active org) intoExecutionContextin all three REST entry-point resolvers (@objectstack/rest,@objectstack/runtime,@objectstack/plugin-hono-server). - Adds the field to
ExecutionContextSchemaso it survives Zod parsing. - Adds an
org_user_idsfield to the RLS compiler's user context. - Adds a new
sys_user_org_memberspolicy (id IN (current_user.org_user_ids)) to bothmember_defaultandviewer_readonlypermission sets, alongside the existingsys_user_selfpolicy. The RLS compiler OR-combines them, so users see themselves AND their org collaborators.
Capped at 1000 members per request. Large enterprises should plug in a directory cache or split per workspace.
- Pre-resolves
-
Updated dependencies [bab2b20]
-
Updated dependencies [fa011d8]
-
Updated dependencies [b806f58]
- @objectstack/spec@5.2.0
-
75f4ee6: feat(metadata): introduce
executionPinnedcapability for runtime version pinning (ADR-0009)Adds a new capability flag on the metadata type registry so that types whose runtime transaction rows reference a specific historical version (flow, workflow, approval) get unified pinning behavior — instead of every business table re-implementing its own snapshot column.
MetadataTypeRegistryEntrySchemagainsexecutionPinned: boolean, enforced invariantexecutionPinned ⇒ supportsVersioning.flow,workflow,approvalflipped toexecutionPinned: true.approvalalso corrected tosupportsVersioning: true(it was wronglyfalse).MetadataRepository.getByHash(ref, hash)added to the interface. Production implementation inSysMetadataRepositoryresolves historical bodies throughsys_metadata_historykeyed by(organization_id, type, name, checksum). In-memory and FS repositories serve HEAD-only matches.sys_metadata_historygains an index on(organization_id, type, name, checksum)to keep hash lookups O(log n).HistoryCleanupManagerskips pinned types entirely (both age-based and count-based retention) — pinned-type history must never be GC'd.
See
docs/adr/0009-execution-pinned-metadata.mdfor full rationale and the list of rejected alternatives (no shared snapshot table, no inlined snapshot column). -
823d559: Remove
sys_metadata_history.metadata_idcolumn.The column was originally a
Field.lookupFK intosys_metadata.id, then downgraded to plaintextduring the M1 history-writes work so that DELETE tombstones could keep an orphaned ref. After M1 we concluded the column carries no business value:- Audit-time joins use
(organization_id, type, name, version), which is already a UNIQUE composite key. - The physical row id is a database-internal detail with no logical identity — it cannot follow an item through delete + recreate.
- No code reader was ever added.
This release removes the column outright:
- Dropped
metadata_idfromSysMetadataHistoryObject(@objectstack/platform-objects). - Dropped
metadataIdfromMetadataHistoryRecordSchema(@objectstack/spec). SysMetadataRepository.put/deleteno longer write the column.- Legacy
DatabaseLoader.createHistoryRecordno longer writes it;getHistoryRecord/queryHistoryfilter by(type, name)directly (no parent-row lookup needed). MetadataHistoryCleanupmaxVersionspolicy groups by(type, name)instead ofmetadata_id.
Migration: Drop the column from existing
sys_metadata_historytables in a follow-up SQL migration. Existing history rows remain queryable since(organization_id, type, name, version)is already the canonical lookup key. No consumer code should be readingmetadata_id— if you are, switch to(organization_id, type, name, version).See ADR-0008 §14 for the full rationale.
- Audit-time joins use
- Updated dependencies [75f4ee6]
- Updated dependencies [823d559]
- @objectstack/spec@5.1.0
-
888a5c1: PR-10d.3 — feature flag for
SysMetadataRepository.putwrite path insaveMetaItem.ObjectStackProtocolImplementationnow accepts anoptions.useRepositoryWritePathflag (also honored viaOBJECTSTACK_USE_REPOSITORY_WRITE_PATH=1) that routes overlay writes throughSysMetadataRepository.put, appending to the change-log and emitting HMRseq.saveMetaItemrequest grew optionalparentVersion(If-Match) andactorfields.ConflictErroris mapped to a 409metadata_conflictAPI error.- Plural metadata type aliases (
views,dashboards, ...) are normalized to singular before the repo's overlay-allowlist gate. SysMetadataRepository.put/deletenow update/delete by rowid(the engine's strict.updatesemantics require an id ormulti:true).sys_metadata.checksumcolumn widened from 64 → 71 chars to hold the"sha256:"prefix produced byhashSpec().- Default behaviour unchanged: legacy raw-engine path remains until PR-10d.4 flips the flag and removes it.
-
Updated dependencies [2f9073a]
- @objectstack/spec@5.0.0
- Updated dependencies [2869891]
- @objectstack/spec@4.2.0
- @objectstack/spec@4.1.1
- Updated dependencies [2108c30]
- Updated dependencies [23db640]
- @objectstack/spec@4.1.0
- 15e0df6: chore: unify all package versions to a single patch release
- Updated dependencies [15e0df6]
- @objectstack/spec@4.0.5