Releases: objectstack-ai/objectstack
Release list
@objectstack/observability@16.0.0
Minor Changes
-
efbcfe1: feat(observability): admin-only richer per-request timing detail via
X-OS-Debug-Timing: json(#2408)Completes the optional "richer JSON" diagnostic from #2408. In addition to the
basicServer-Timingheader, an admin/service caller can now request a
per-query breakdown — the slowest SQL statements and a query count — by sending
X-OS-Debug-Timing: json. The detail is returned in a separate
X-OS-Debug-Timing-Detailresponse header (compact JSON) and is admin-only,
even under global mode: an ordinary caller never sees SQL shapes.- observability:
PerfTiminggains opt-in per-event detail capture
(enableDetail/recordDetail/details) plus the ambient
recordServerTimingDetail. The disclosure gate gains aprivilegedlevel
(set byallowPerfDisclosure, read viaisPerfDisclosurePrivileged) so the
richer detail can be gated independently of the basic header. - driver-sql: when detail capture is on, the query listener additionally
records each query's parametrized statement (knex'sq.sql,?
placeholders) — never the bindings, so no literal row value ever enters the
collector. Zero overhead when detail is off. - plugin-hono-server:
X-OS-Debug-Timing: jsonenables detail capture; the
middleware emitsX-OS-Debug-Timing-Detail(slowest queries, capped and
sanitized to header-safe ASCII) only when the principal is a proven admin.
Basic and global behavior are unchanged;
jsonis purely additive. - observability:
-
2049b6a: feat(observability): admin-gated per-request
Server-TimingviaX-OS-Debug-Timing(#2408)Perf-tuning mode was previously global-only (
serverTimingoption /
OS_SERVER_TIMING), which discloses internal phase durations — a mild
backend-fingerprinting surface — to every caller. This adds the per-request
gating path from the design so an operator can pull a single request's
Server-Timingbreakdown on a live environment without turning the header on
for everyone.- observability: a request-scoped disclosure gate (
runWithPerfDisclosure,
allowPerfDisclosure,isPerfDisclosureAllowed,PerfDisclosureGate) kept
separate from the purePerfTimingcollector and pinned to its own
Symbol.forstore so the middleware and dispatcher share it across module
copies. - plugin-hono-server: the Server-Timing middleware is registered by default
(unlessserverTiming: false). It runs the collector when timing is global
or the request sendsX-OS-Debug-Timing: 1, and emits the header only
when the gate is open.OS_PERF_TIMING=1now also enables global mode. - runtime: after resolving the execution context, the dispatcher opens the
gate for admin/service/system principals, so ordinary callers never receive
the header even if they send the debug header.
Existing global-mode behavior is unchanged.
- observability: a request-scoped disclosure gate (
Patch Changes
-
ce468c8: feat(observability): decompose
Server-Timinginto auth / db / hooks / serialize spans (perf-tuning mode)The opt-in
Server-Timingheader now breaks a request's server time into the phases that actually explain it, so an operator can open DevTools → Network → Timing and see where the time went without standing up an external tracing backend:db— total SQL time with a query count. The SQL driver wires knex'squery/query-responseevents (keyed by__knexQueryUid) and folds each query into one aggregate member (db;dur=210;desc="6 queries") — the query count is the number most useful for spotting N sequential round-trips. Timing is attributed to the originating request viaAsyncLocalStorage, so it is correct under concurrency and never cross-attributes. SQL text is never emitted, only durations and a count.auth— identity / session resolution in the dispatcher, the prime suspect for unexplained data-API overhead.hooks— total business-hook execution time with a hook count, fed through the engine's existingHookMetricsRecorderseam (wired from the runtime, so@objectstack/objectql's leancoretier stays observability-free).serialize— response JSON encoding in the HTTP adapter.
Adds
countServerTiming(name, dur, unit)(andPerfTiming.count) to fold high-frequency phases into a single aggregate member instead of flooding the header. Every phase is a no-op when perf-tuning is off (serverTiming: true/OS_SERVER_TIMING=true), so there is zero measurable overhead on the normal path.Closes #2408.
-
Updated dependencies [f972574]
-
Updated dependencies [6289ec3]
-
Updated dependencies [22013aa]
-
Updated dependencies [3ad3dd5]
-
Updated dependencies [8efa395]
-
Updated dependencies [3a18b60]
-
Updated dependencies [a8aa34c]
-
Updated dependencies [a3823b2]
-
Updated dependencies [43a3efb]
-
Updated dependencies [524696a]
-
Updated dependencies [bfa3c3f]
-
Updated dependencies [5e3301d]
-
Updated dependencies [46e876c]
-
Updated dependencies [158aa14]
-
Updated dependencies [62a2117]
-
Updated dependencies [d2723e2]
-
Updated dependencies [fefcd54]
-
Updated dependencies [beaf2de]
-
Updated dependencies [369eb6e]
-
Updated dependencies [06ff734]
-
Updated dependencies [b659111]
-
Updated dependencies [5754a23]
-
Updated dependencies [6c270a6]
-
Updated dependencies [668dd17]
-
Updated dependencies [8abf133]
-
Updated dependencies [e0859b1]
-
Updated dependencies [04ecd4e]
-
Updated dependencies [4d5a892]
-
Updated dependencies [16cebeb]
-
Updated dependencies [86d30af]
-
Updated dependencies [8923843]
-
Updated dependencies [a2795f6]
-
Updated dependencies [f16b492]
-
Updated dependencies [4b6fde8]
-
Updated dependencies [2018df9]
-
Updated dependencies [fc5a3a2]
-
Updated dependencies [8ff9210]
- @objectstack/spec@16.0.0
@objectstack/objectql@16.0.0
Major Changes
-
6c270a6: BREAKING: remove the deprecated
ctx.session.tenantId/ctx.user.tenantIdalias from the hook & action authoring surface — converge onorganizationId(#3290).#3280 made
organizationIdthe blessed developer-facing name for the caller's active org across the JS authoring surface and kepttenantIdas a@deprecatedalias carrying the identical value. That alias is now removed from the hookctx.session, the action-bodyctx.session, and the action-bodyctx.user. Read the caller's active org under the single blessed name:- const org = ctx.session.tenantId; // hook or action body + const org = ctx.user?.organizationId ?? ctx.session?.organizationId;
FROM → TO migration (in any
*.hook.ts/*.action.tsbody):ctx.session.tenantId→ctx.session.organizationIdctx.user.tenantId(action body) →ctx.user.organizationId
The value is unchanged —
organizationIdis the same active-org id, matching theorganization_idcolumn andcurrent_user.organizationIdin RLS/sharing.ctx.userisundefinedfor system / unauthenticated writes, so readctx.session?.organizationIdwhen a hook or action must work regardless of a resolved user.What changed internally:
@objectstack/spec—HookContextSchema.sessiondrops thetenantIdfield (onlyorganizationIdremains). A straytenantIdon a constructed session is now stripped by the schema.@objectstack/objectql— the engine'sbuildSession()no longer emitssession.tenantId; the audit-stamp plugin sources thetenant_idcolumn fromsession.organizationId.@objectstack/runtime—buildActionSession()and the REST actionctx.userno longer emittenantId.@objectstack/trigger-record-change— readssession.organizationId(wassession.tenantId) when forwarding the writer's org to arunAs:'user'flow; behavior is identical.
Explicit non-goal (unchanged): the generic driver-layer tenancy abstraction is not touched —
ExecutionContext.tenantId,DriverOptions.tenantId,SqlDriver.applyTenantScope/TenancyConfig.tenantField, andExecutionLog.tenantId. That isolation column is configurable and legitimately carries an environment id in database-per-tenant kernels; it is a distinct axis from the developer-facing org. The build-timecheck:org-identifierguard now also coverspackages/**to keep reference bodies off the removed name.
Minor Changes
-
22013aa: Split the overloaded
managedBy: 'system'bucket into engine-owned vs. admin-writable, and enforce engine-owned writes (ADR-0103, #3220). Thesystembucket conflated two incompatible write policies: rows a platform service owns end to end (never user-written), and platform-defined schema whose rows are legitimately admin/user-writable. It carried the same all-false affordance row asbetter-auth/append-onlybut, unlikebetter-auth, had no engine enforcement — a wildcard admin could raw-write these rows through the generic data API (ADR-0049 gap).Rather than add a new
managedByenum value (which would fall through to fully-editableplatformdefaults on already-deployed Console clients), the write policy is now the resolved affordance (resolveCrudAffordances= bucket default +userActions), and engine-owned is defined as asystem/append-onlyobject that grants no write:- Writable set declares
userActions— the RBAC link tables (sys_user_position,sys_user_permission_set,sys_position_permission_set),sys_user_preference,sys_approval_delegation, and the messaging config grids (sys_notification_preference/…_subscription/…_template) now declareuserActions: { create, edit, delete: true }. The affordance is a declaration only — theDelegatedAdminGate/ RLS / permission sets remain the authz. - Engine-owned objects locked to reads —
apiMethods: ['get','list']added where absent (jobs, notifications, approval request/approver/token/action,sys_record_share,sys_automation_run, mail/settings/secret audit, the messaging delivery pipeline).sys_secretis explicitly read-locked (an emptyapiMethodsarray fails open). sys_import_jobstays engine-owned: the REST import route now writes its job rowsisSystem-elevated (attribution preserved via the explicitcreated_bystamp) and the object is locked to['get','list'].- New engine write guard (
assertEngineOwnedWriteAllowed, plugin-security) fail-closed rejects user-context generic writes to engine-ownedsystem/append-onlyobjects, keyed off the resolved affordance;isSystemand context-less engine/service writes bypass by construction. Wired into the security middleware alongside the other data-layer gates. reconcileManagedApiMethods(objectql registry) now runs for every managed bucket, not justbetter-auth: any advertised write verb an object's resolved affordances forbid is stripped at registration with a warning (the drift backstop, ADR-0049)./me/permissionsclamp (plugin-hono-server) now clampssystem/append-onlyas well asbetter-auth, so the client hint reflectspermission ∩ guard.
Potentially breaking: a downstream/third-party
systemobject that advertised generic write verbs relying on today's fail-open behaviour will have those verbs stripped (with a warning) and user-context generic writes to it rejected. DeclareuserActionsopening the verbs the object legitimately takes from a user context.better-authkeeps plugin-auth's identity write guard unchanged; the row-levelmanaged_byprovenance vocabulary (ADR-0066) is a different axis and is untouched. - Writable set declares
-
04ecd4e: feat(validation):
state_machine.initialStatesenforces the FSM entry point on INSERT (#3165)A
state_machinerule'stransitionsonly governs UPDATE — on INSERT the rule
was a no-op, and aselectfield permits ANY declared option as the initial
value. So a record could be born mid-flow (created alreadyapproved), skipping
the whole state machine. This was the gap #3043's mitigation idea assumed didn't
exist (declared ≠ enforced, ADR-0049).state_machinerules gain an optionalinitialStates: string[]— the states a
record may be CREATED in. When set, an insert whose (defaulted) state-field value
is outside the list is rejected server-side withcode: 'invalid_initial_state'.
Omit it to keep the legacy behavior (no initial-state check on insert). A missing
/ empty value is left to required-validation;transitions(UPDATE) is
unaffected. Enforced at the sameevaluateValidationRules(..., 'insert')seam the
engine already runs after field defaults. -
4d5a892: feat(objectql): roll-up
summaryfields can filter which child rows they aggregate (#1868)summaryOperationsgains an optionalfilter— a querywhereFilterCondition
evaluated against each child row, so a summary aggregates only the matching
children instead of the whole collection. This is what lets a single child object
feed several distinct parent totals, which the cross-object rollup templates need:// One `engagement` child → distinct filtered totals. total_signups: { type: 'summary', summaryOperations: { object: 'engagement', field: 'id', function: 'count', filter: { type: 'signup' } }, } // Sum only received receipt lines (3-way match). received_amount: { type: 'summary', summaryOperations: { object: 'procurement_receipt', field: 'amount', function: 'sum', filter: { status: 'received' } }, }
The engine ANDs the predicate with the parent-FK match when it recomputes, and
because the whole filtered aggregate is re-run on every child write, a child that
moves in or out of the predicate (e.g. a status change) keeps the parent current
with no extra wiring. Operator and compound forms work too
(filter: { type: { $in: ['signup', 'trial'] }, amount: { $gte: 100 } }).Purely additive: omitting
filteraggregates every child exactly as before.
Patch Changes
-
a8aa34c: Enforce validation rules,
requiredWhen, and per-optionvisibleWhenon multi-row updates (#3106). The bulk branch ofengine.update(options.multi→driver.updateMany) previously never calledevaluateValidationRules, so every object-level rule (script,state_machine,format,cross_field,json_schema,conditional), field-levelrequiredWhen, and per-optionvisibleWhencheck was a silent no-op there. The engine now reads the row-scoped match set (the same AST the write binds, one query shared with thereadonlyWhenbulk strip) and evaluates the payload against each matched row's prior state; any error-severity violation rejects the whole batch withValidationError(annotated with the failing record id) before anything is written. Schemas needing no prior state (format/json_schema-only) are evaluated once against the payload with no fetch, and rule-free schemas are unaffected. Behavior change: bulk writes that previously slipped past declared rules now throw. Doc comments inrule-validator.tsandvalidation.zod.tsno longer overstate coverage and name the remainingevents: ['delete']gap (tracked separately). -
e057f42: fix: harden the bulk-write path — retries, idempotency, contracts, and summary visibility (#3147–#3152)
Six reliability fixes to the batched seed/import +
engine.insert(array)path
introduced by the #2678 bulk-write rework:- #3151
bulkWritevalidates thatwriteBatchreturns one record per input
row (a short/long/non-array return is degraded per-row, not backfilled as
phantom success);engine.insert(array)likewise rejects a short driver
bulkCreatereturn instead of padding afterInsert withundefined. - #3150 wraps the two remaining un-retried write points (seed
writeRecord/resolveDeferredUpdates, import's no-`createManyDa...
- #3151
@objectstack/metadata@16.0.0
Patch Changes
-
fdc244e: Dev-loop DX fixes from the 15.1 third-party evaluation (P2 batch):
- Hot-added objects are now queryable without a restart. Adding a
*.object.tsunderos devused to recompile "green" while every query answeredno such table(ornot registered) until a manual restart: the artifact reload never notified the ObjectQL registry, tables were only created at boot, and seeds only loaded from the boot-time bundle. Themetadata:reloadedpayload now carries the parsed artifact; ObjectQL ingests the object definitions and re-runs the idempotent schema sync (sameskipSchemaSyncopt-out as boot), and the runtime loads seeds for first-seen objects (dev, single-tenant).os devalso prints✚ new object(s): …on recompile. - Dev admin credentials stay visible. The
os devstartup banner only showedadmin@objectos.ai / admin123on the boot that actually seeded it; with the persistent default DB every later boot hid it, and the Console login page never knew it existed. The hint now re-arms on every dev boot for as long as the account still verifies against the default password, andGET /api/v1/auth/configexposes a dev-gateddevSeedAdminfield (never present outsideNODE_ENV=development) so the login page can show it. os doctorreference analysis understands current metadata shapes. Objects bound throughdefineViewcontainers (list/listViews/form/formViews→data.object, subformchildObject, lookup form fields) and app navigation (objectName, nestedchildren,areas) were reported as "defined but not referenced". The collector now walks the canonical shapes (plus flow nodeconfig.object/objectName) and the orphan-view check descends into containers.
- Hot-added objects are now queryable without a restart. Adding a
-
d2723e2:
MetadataManager.register()/unregister()now announce tosubscribe()watchers. Both updated the registry, persisted to writable loaders and published to realtime, but never fired the watch callbacks — sosubscribe()looked like it covered every write while silently missing all of them. Only thesaveMetaItempath (via the repository watch stream) and the filesystem watcher ever reached a subscriber. Runtime consumers that cache metadata — notably ObjectQL's SchemaRegistry bridge, the component that decides what is queryable — went stale on every other write until the process restarted.Announcing is now the default, so a new call site is correct without knowing this contract exists. This is a contract fix rather than a bug fix: the one live behavior change is that runtime datasource writes (
datasource-admin) now reach the HMR SSE stream, which subscribes to every registered type.unregisterPackage()/bulkUnregister()also announce their deletes now — correct, but latent, since neither has a production caller today.Bulk ingest opts out explicitly with the new
MetadataWriteOptions({ notify: false }) — boot-time filesystem priming, artifact ingest, and ObjectQL's registry bridge, each of which either runs before consumers cache anything or announces the whole batch once (as the artifact reload path does viametadata:reloaded). The bridge in particular MUST stay silent: it copies objects out of the SchemaRegistry, and announcing would feed them back through a handler that re-registers under_packageId ?? 'metadata-service', overwriting the true package provenance of every object whose body carries no_packageId.Additive only —
register(type, name, data)andunregister(type, name)keep working unchanged.Fixes #3112.
-
Updated dependencies [f972574]
-
Updated dependencies [6289ec3]
-
Updated dependencies [22013aa]
-
Updated dependencies [3ad3dd5]
-
Updated dependencies [8efa395]
-
Updated dependencies [3a18b60]
-
Updated dependencies [a8aa34c]
-
Updated dependencies [e057f42]
-
Updated dependencies [a3823b2]
-
Updated dependencies [bc65105]
-
Updated dependencies [43a3efb]
-
Updated dependencies [524696a]
-
Updated dependencies [bfa3c3f]
-
Updated dependencies [5e3301d]
-
Updated dependencies [dd9f223]
-
Updated dependencies [46e876c]
-
Updated dependencies [5f05de2]
-
Updated dependencies [021ba4c]
-
Updated dependencies [158aa14]
-
Updated dependencies [62a2117]
-
Updated dependencies [83e8f7d]
-
Updated dependencies [d2723e2]
-
Updated dependencies [fefcd54]
-
Updated dependencies [beaf2de]
-
Updated dependencies [06cb319]
-
Updated dependencies [369eb6e]
-
Updated dependencies [06ff734]
-
Updated dependencies [b659111]
-
Updated dependencies [5754a23]
-
Updated dependencies [6c270a6]
-
Updated dependencies [290e2f0]
-
Updated dependencies [668dd17]
-
Updated dependencies [8abf133]
-
Updated dependencies [e0859b1]
-
Updated dependencies [92f5f19]
-
Updated dependencies [32899e6]
-
Updated dependencies [04ecd4e]
-
Updated dependencies [4d5a892]
-
Updated dependencies [16cebeb]
-
Updated dependencies [86d30af]
-
Updated dependencies [8923843]
-
Updated dependencies [a2795f6]
-
Updated dependencies [f16b492]
-
Updated dependencies [4b6fde8]
-
Updated dependencies [2018df9]
-
Updated dependencies [fc5a3a2]
-
Updated dependencies [8ff9210]
- @objectstack/spec@16.0.0
- @objectstack/platform-objects@16.0.0
- @objectstack/core@16.0.0
- @objectstack/metadata-core@16.0.0
- @objectstack/types@16.0.0
- @objectstack/metadata-fs@16.0.0
@objectstack/metadata-protocol@16.0.0
Minor Changes
-
bfa3c3f: Broadcast a
transactionalBatchcapability bit in discovery so clients negotiate the atomic cross-object batch declaratively, instead of runtime-probing 404/405/501 (#3298).The atomic cross-object batch endpoint (
POST {basePath}/batch, #1604 / ADR-0034 item 4) and its typed SDK surface (client.data.batchTransaction, #3271) already shipped, but discovery never told a client whether a backend actually supports it. Consumers (notably ObjectUI'sObjectStackAdapter) had to probe: fire a/batch, read404/405(no route) or501(no runtime transaction), and only then fall back to non-atomic client-side simulation. That is "find out by calling", not capability negotiation — it cannot be decided at connect time and cannot serve as the "minimum backend supports/batch" gate that blocks hard-deleting the non-atomic fallback downstream.WellKnownCapabilitiesSchemagains a requiredtransactionalBatch: boolean, and every discovery producer fills it honestly (declared === enforced), so it never becomes a declared-but-unpopulated bit:@objectstack/metadata-protocol(getDiscovery) — reports whether the runtime engine can honour a transaction (typeof engine.transaction === 'function'). The/batchhandler runs its ops insideengine.transaction(), which degrades to a non-atomic passthrough (or 501) without one.@objectstack/rest(/discovery) — ANDs the engine signal with whether it actually mounts the route (api.enableBatch), so a server with batch disabled reportsfalseeven on a transaction-capable engine (never advertise an endpoint that would 404).@objectstack/plugin-hono-server(standalone discovery) — reportsfalse: this minimal surface registers CRUD only and does not mount/batch(that ships with@objectstack/rest). Under-reporting is the safe direction — a client keeps its correct-but-slower fallback rather than losing atomicity.@objectstack/client— already normalizes hierarchicalcapabilitiesto flat booleans, soclient.capabilities.transactionalBatchis exposed (and now typed) for declarative consumers.
The bit follows the existing capability semantics:
true⟺ the/batchroute is mounted and the runtime can honour a transaction — the exact condition under which the endpoint returns200rather than404/405/501. Additive and behavior-preserving; only the discovery payload gains a field. -
668dd17: Breaking (npm type surface): retire the vestigial feed contracts + protocol surface (ADR-0052 §5 follow-up, #1959).
The
service-feedruntime was deleted in #1955;sys_comment/sys_activity
are the canonical record-collaboration/timeline backend. This removes the dead
type surface that still pointed at the deleted runtime — every removed method was
already unreachable (the feed REST route was never mounted → 404; the protocol
implementation was never wired with a feed service, sorequireFeedService()
could only throw). No behavior changes.No authorable metadata key is removed (the
feeds:object capability flag and
theRecordActivityUI component config are unchanged), soPROTOCOL_MAJOR
stays 15 and this ships asminorrather than a protocol major.FROM → TO migration for every removed export:
@objectstack/spec/contracts—IFeedService,CreateFeedItemInput,
UpdateFeedItemInput,ListFeedOptions,FeedListResult→ removed, no
replacement. Comments/activity are plain records: writesys_comment/ read
sys_activityvia the data engine or the REST data API.@objectstack/spec/api—FeedApiContracts,FeedApiErrorCode,
FeedProtocol, and all feed request/response schemas + types (GetFeed*,
CreateFeedItem*,UpdateFeedItem*,DeleteFeedItem*,AddReaction*,
RemoveReaction*,PinFeedItem*,UnpinFeedItem*,StarFeedItem*,
UnstarFeedItem*,SearchFeed*,GetChangelog*,ChangelogEntry,
SubscribeRequest/Response,FeedUnsubscribeRequest,UnsubscribeResponse,
FeedPathParams,FeedItemPathParams,FeedListFilterType) → removed. Use
the data API againstsys_comment/sys_activity(/api/v1/data/sys_comment/…);
reactions and threaded replies are fields onsys_comment.@objectstack/spec/data—FeedItemSchema/FeedItem,FeedActorSchema/FeedActor,
MentionSchema/Mention,ReactionSchema/Reaction,
FieldChangeEntrySchema/FieldChangeEntry,FeedVisibility,
RecordSubscriptionSchema/RecordSubscription,SubscriptionEventType, and the
data-namespaceNotificationChannel→ removed.FeedItemTypeand
FeedFilterModeare kept (live UI activity-timeline config). For notification
channels useNotificationChannelSchemafrom@objectstack/spec/system.@objectstack/client—client.feed.*(list/create/update/delete/
addReaction/removeReaction/pin/unpin/star/unstar/search/
getChangelog/subscribe/unsubscribe) and the re-exported feed response
types → removed. One-line fix: useclient.data.*onsys_comment/
sys_activity, e.g.client.data.create('sys_comment', { object, record_id, body })
andclient.data.find('sys_activity', { filters: [['record_id', '=', id]] }).@objectstack/metadata-protocol—ObjectStackProtocolImplementationno longer
implements the 14 feed methods; its constructor
(engine, getServicesRegistry?, getFeedService?, environmentId?)becomes
(engine, getServicesRegistry?, environmentId?). One-line fix: delete the third
argument.
Patch Changes
-
e057f42: fix: harden the bulk-write path — retries, idempotency, contracts, and summary visibility (#3147–#3152)
Six reliability fixes to the batched seed/import +
engine.insert(array)path
introduced by the #2678 bulk-write rework:- #3151
bulkWritevalidates thatwriteBatchreturns one record per input
row (a short/long/non-array return is degraded per-row, not backfilled as
phantom success);engine.insert(array)likewise rejects a short driver
bulkCreatereturn instead of padding afterInsert withundefined. - #3150 wraps the two remaining un-retried write points (seed
writeRecord/resolveDeferredUpdates, import's no-createManyData
fallback) inwithTransientRetry;defaultIsTransientErrorshort-circuits
definitive logical errors to non-transient. - #3148 import
resolveRefflushes pending creates on a same-object miss so
a later row can reference an earlier same-file CREATE, and no longer
negatively caches a miss. - #3149 threads an
attemptcounter throughbulkWrite; seed rechecks by
externalIdand import bymatchFieldsbefore re-writing, so a
commit-then-lost-response retry cannot duplicate a batch. - #3147
recomputeSummariesretries transient failures and, on exhaustion,
surfacesSummaryRecomputeError(ERR_SUMMARY_RECOMPUTE) instead of a
silent warn; seed/import recover it to a warning without re-writing. - #3152 autonumbers are assigned after validation, so a batch that dies in
validation consumes no sequence value (no number-range gaps).
- #3151
-
0e41302: fix(metadata-protocol): unscoped metadata list dedupes package-aware, not by bare name (ADR-0048 #1828)
getMetaItemsmerged registry items,sys_metadataoverlay rows, draft-preview
rows, and MetadataService items intoMaps keyed by barename, so two installed
packages shipping the sametype/name(e.g.page/home) collapsed to one row
(last-write-wins) on an unscopedGET /meta/:typewhenever either package had an
overlay — and the frontend prefer-local resolution, which reads that list, could
no longer tell the two packages' rows apart.The three merge sites (plus the env/org pre-merge) now key by
(package, name),
mirroringgetMetaItem's scoped-then-global-fallback resolution: colliding rows
stay distinct each with its own_packageId, a package-less (env-wide) overlay
still wins over the single artifact it customizes (ADR-0005 precedence and
single-package behaviour unchanged), and the registry-hydration artifact graft is
scoped to each row's ownpackage_idso a collision no longer mislabels provenance. -
b8a21ad: Publish/discard package drafts in the draft's own org scope, fixing
no_draftafter saving a draft via Studio.Studio "Save Draft" (
PUT /meta/:type/:name?mode=draft) never threads the session'sactiveOrganizationId, so the draft row is written env-wide (organization_id = NULL). "Publish" (POST /packages/:id/publish-drafts) resolves the active org and passed it topromoteDraft, which looked the draft up with a strictorganization_id = <org>equality — so it 404'd ([no_draft] No pending draft exists …) on the env-wide row it could never match, even thoughlistDraftshad already surfaced that draft to the publish CTA (PR #1852's$or).discardPackageDraftshad the same latent gap.listDraftsnow projects each draft's ownorganizationId, andpublishPackageDrafts/discardPackageDraftspromote / delete each draft in that scope (env-wide stays env-wide, per-org stays per-org). Seed-body capture and the ADR-0067 revert-plan pre-state read are scoped the same way.Fixes #3115.
-
beaf2de: fix(metadata-protocol): strip static
readonlyon INSERT at the data-write ingress (#3043)#2948/#3003 made static
readonly: truefields server-enforced on UPDATE (a
non-system PATCH forgingapproval_status: 'approved'is silently stripped in
the engine), but INSERT was exempt. For approval/status/verdict columns that
exemption was the shorter attack: instead of the #3003 draft-then-PATCH move, a
non-system caller couldPOSTa record alreadyapproval_status: 'approved'in
one step — and the UPDATE-only strip never reached it.The...
@objectstack/metadata-fs@16.0.0
@objectstack/metadata-core@16.0.0
Patch Changes
-
62a2117: Split the overloaded
managedBy: 'system'bucket with an explicitengine-ownedvalue (ADR-0103 addendum, #3343). ADR-0103 deferred the enum split ("revisitable later as a rename") because a newmanagedByvalue would fall through to the fully-editableplatformdefault on deployed Console clients. Both reasons against it are now retired — the server-side write guard /apiMethodsreconciliation //me/permissionsclamp make that fallthrough cosmetic (the write is rejected regardless of what the client renders), and objectui#2712 closed the UI union — so v16 lands it, additively.- New enum value
engine-ownedwith the same all-locked default affordance row assystem(create/import/edit/delete: false,exportCsv: true). It joinsENGINE_OWNED_BUCKETS(the engine write guard) andGUARDED_WRITE_BUCKETS(the/me/permissionsclamp); the guard,reconcileManagedApiMethods, and the clamp mechanisms are unchanged —engine-ownedis an explicit member of the set they already covered by resolved affordance. - 20 objects relabelled
system → engine-owned— the ones the engine owns end to end and that declared no write-openinguserActions(the metadata store, jobs, approval runtime rows, sharing rows,sys_automation_run, the messaging delivery/receipt pipeline,sys_secret, settings). One-line, behaviour-identical per object. - 8 admin/user-writable objects keep
managedBy: 'system'(the RBAC link tables,sys_user_preference,sys_approval_delegation, the messaging config grids) —systemnow reads as "engine-managed schema, writable viauserActions".
Behaviour-, enforcement- and wire-identical: resolved affordances, the guard verdict, the 405
apiMethodsreconciliation, and the permissions clamp are the same before and after — this is a self-documenting relabel, not a policy change. No data migration (managedByis schema metadata) and no code branches on the'system'literal. Retiring the overloadedsystementirely (moving the 8 writable objects to a dedicated bucket) is a breaking rename deferred to v17. - New enum value
-
06cb319: fix(identity): close the generic-write apiMethods hole on sys_presence and sys_metadata (#3220)
Follow-through on #1591/#3213 (better-auth apiMethods reconciliation) for two
non-better-auth managed objects that shipped the same contradiction: their
enable.apiMethodsadvertised genericcreate/update/deletewhile their
managedBybucket forbids user-context writes, leaving the generic/data
route open to a write the bucket does not permit.sys_presence(managedBy: 'append-only') advertisedcreate/update/delete
(update/delete on an append-only object at that) but is written only over the
realtime websocket/in-memory path, never through ObjectQL. Narrowed to
['get', 'list'].sys_metadata(managedBy: 'system') advertised full CRUD but customization
overlays are authored only through the metadata-protocol RPC (engine writes
carry a transaction context, not a user session); neither the framework nor
the Console (objectui) POSTs/data/sys_metadata. Narrowed to['get', 'list'].
Reads stay open. The metadata-protocol / realtime write paths are engine-level
and bypass the HTTP exposure gate, so they are unaffected — verified by the
metadata-authoring dogfood and the objectql overlay tests.A blast-radius audit confirmed the broader
system/append-onlybuckets are NOT
safe to guard wholesale: severalsystemobjects (sys_user_position,
sys_user_permission_set,sys_position_permission_set,sys_user_preference,
sys_import_job) are legitimately user-writable by design (delegated
administration, user preferences, imports). Generalizing the engine write guard
to those buckets is intentionally NOT done here — see #3220 for the bucket-taxonomy
root cause. -
Updated dependencies [f972574]
-
Updated dependencies [6289ec3]
-
Updated dependencies [22013aa]
-
Updated dependencies [3ad3dd5]
-
Updated dependencies [8efa395]
-
Updated dependencies [3a18b60]
-
Updated dependencies [a8aa34c]
-
Updated dependencies [a3823b2]
-
Updated dependencies [43a3efb]
-
Updated dependencies [524696a]
-
Updated dependencies [bfa3c3f]
-
Updated dependencies [5e3301d]
-
Updated dependencies [46e876c]
-
Updated dependencies [158aa14]
-
Updated dependencies [62a2117]
-
Updated dependencies [d2723e2]
-
Updated dependencies [fefcd54]
-
Updated dependencies [beaf2de]
-
Updated dependencies [369eb6e]
-
Updated dependencies [06ff734]
-
Updated dependencies [b659111]
-
Updated dependencies [5754a23]
-
Updated dependencies [6c270a6]
-
Updated dependencies [668dd17]
-
Updated dependencies [8abf133]
-
Updated dependencies [e0859b1]
-
Updated dependencies [04ecd4e]
-
Updated dependencies [4d5a892]
-
Updated dependencies [16cebeb]
-
Updated dependencies [86d30af]
-
Updated dependencies [8923843]
-
Updated dependencies [a2795f6]
-
Updated dependencies [f16b492]
-
Updated dependencies [4b6fde8]
-
Updated dependencies [2018df9]
-
Updated dependencies [fc5a3a2]
-
Updated dependencies [8ff9210]
- @objectstack/spec@16.0.0
@objectstack/mcp@16.0.0
Minor Changes
-
15dbe18: feat(mcp)!: stdio transport requires an API-key principal — fail-closed, no unscoped bridge (ADR-0101, #3246)
The long-lived MCP stdio transport no longer reads data unscoped. It now runs
under an env-supplied identity, closing the platform's last identity-less
execution surface (themcp-stdio-authorityconformance row graduates
experimental→enforced).OS_MCP_STDIO_API_KEY=osk_...supplies the stdio identity, resolved through
the SAME@objectstack/coreverify + authorization chain as the HTTP/REST
surfaces; therecord_by_idresource reads viaql.find(obj, { where:{id}, context }), so RLS/FLS/tenant apply exactly as on REST/data. Re-resolved
per read, so a revoked/expired key stops working on a live session.- Fail-closed — enabling stdio auto-start (
OS_MCP_STDIO_ENABLED=true/
autoStart) without a resolvable key throws and refuses to start. There is no
unscoped fallback and deliberately nosystembypass; full authority is a key
minted on a platform-admin or dedicated service identity.
BREAKING (stdio auto-start only): previously
OS_MCP_STDIO_ENABLED=true
(or the pluginautoStartoption) started stdio with full, unscoped authority
and no credential. It now requiresOS_MCP_STDIO_API_KEY; without it, boot
fails closed. The default-on HTTP surface and any deployment that never enables
stdio auto-start are unaffected. -
83e8f7d: feat(mcp): decouple the stdio auto-start switch from the HTTP surface + surface the MCP endpoint on
os devboot (#3167)The MCP HTTP surface (
/api/v1/mcp) and the long-lived stdio transport used to
share one env var:OS_MCP_SERVER_ENABLED=trueturned the HTTP surface on and
silently auto-started the stdio transport — which bridges the raw metadata service- data engine with no per-request principal (unscoped). An operator setting it to
"make sure MCP is on" got an unscoped transport as a side effect.
@objectstack/types— newresolveMcpStdioAutoStart(). Stdio auto-start is
now its own switch,OS_MCP_STDIO_ENABLED(default off);OS_MCP_SERVER_ENABLED
governs only the HTTP surface. The legacyOS_MCP_SERVER_ENABLED=truetrigger
still starts stdio for one release, flagged as deprecated.=falseis unchanged
(it only ever gated HTTP).@objectstack/mcp—MCPServerPlugin.start()gates stdio on the new switch
and logs a one-time deprecation warning when started via the legacy alias.@objectstack/cli—os devnow prints the MCP endpoint, the agent-skill
URL, and a ready-to-pasteclaude mcp addcommand on boot (gated on the HTTP
surface being on), so the "an agent operates the app it's building" loop is
discoverable at dev time.create-objectstack— the blank scaffold README documents that the app is
itself an MCP server (the serve side), distinct from the consume-side connector.
- data engine with no per-request principal (unscoped). An operator setting it to
-
230358c: feat(mcp):
validate_expressiontool — validate a CEL expression against a schema before authoring (#1928)Adds an agent-callable MCP tool that runs the same build-time expression checks
asobjectstack build, so an AI can validate a formula / predicate / flow
condition while authoring instead of shipping one that silently evaluates to
null. Given{ objectName, expression, site? }it resolves the object's real
schema (field names + types, via the principal-bounddescribeObjectbridge)
and returns:- errors — bare field refs (
amount→record.amount), unknown fields
(with a did-you-mean), unknown functions; - warnings — text/boolean fields misused in arithmetic, date-equality
pitfalls; - inScope — the fields, stdlib functions, and namespace roots available, so
the model can self-correct; - inferredType for a
formulasite.
site(formula|validation|flow_condition|template, default
formula) maps to the validator's role + scope —flow_conditionbinds fields
bare, the rest bindrecord.<field>. Read-only, gated by thedata:readOAuth
scope, and fail-closed onsys_*objects like the other schema tools. This is
the authoring-time surface the guardrail series (#1928) always pointed at;
@objectstack/mcpgains a@objectstack/formuladependency (acyclic; formula is
a leaf). - errors — bare field refs (
Patch Changes
- Updated dependencies [f972574]
- Updated dependencies [6289ec3]
- Updated dependencies [22013aa]
- Updated dependencies [3ad3dd5]
- Updated dependencies [8efa395]
- Updated dependencies [3a18b60]
- Updated dependencies [a8aa34c]
- Updated dependencies [e057f42]
- Updated dependencies [a3823b2]
- Updated dependencies [43a3efb]
- Updated dependencies [524696a]
- Updated dependencies [6b51346]
- Updated dependencies [80273c8]
- Updated dependencies [bfa3c3f]
- Updated dependencies [5e3301d]
- Updated dependencies [dd9f223]
- Updated dependencies [46e876c]
- Updated dependencies [7125007]
- Updated dependencies [5f05de2]
- Updated dependencies [021ba4c]
- Updated dependencies [158aa14]
- Updated dependencies [62a2117]
- Updated dependencies [83e8f7d]
- Updated dependencies [d2723e2]
- Updated dependencies [fefcd54]
- Updated dependencies [beaf2de]
- Updated dependencies [369eb6e]
- Updated dependencies [06ff734]
- Updated dependencies [b659111]
- Updated dependencies [5754a23]
- Updated dependencies [6c270a6]
- Updated dependencies [290e2f0]
- Updated dependencies [668dd17]
- Updated dependencies [8abf133]
- Updated dependencies [e0859b1]
- Updated dependencies [92f5f19]
- Updated dependencies [32899e6]
- Updated dependencies [04ecd4e]
- Updated dependencies [4d5a892]
- Updated dependencies [16cebeb]
- Updated dependencies [86d30af]
- Updated dependencies [8923843]
- Updated dependencies [ea32ec7]
- Updated dependencies [a2795f6]
- Updated dependencies [f16b492]
- Updated dependencies [4b6fde8]
- Updated dependencies [2018df9]
- Updated dependencies [fc5a3a2]
- Updated dependencies [8ff9210]
- @objectstack/spec@16.0.0
- @objectstack/core@16.0.0
- @objectstack/formula@16.0.0
- @objectstack/types@16.0.0
@objectstack/lint@16.0.0
Minor Changes
-
3a18b60: feat(approvals): rename the
roleapprover type toorg_membership_level(#3133)ApproverType.rolewas the last platform surface projecting the reserved word
"role" (ADR-0090 D3). It is not covered by D3's better-auth exception: that
exception protects better-auth's ownsys_member.rolecolumn, which we do
not own —ApproverTypeis our own enum, an authoring surface, and D3 mandates
that the projection of that concept is spelledorg_membership_leveland
labelled "organization membership", never "role".The sentence licensing the leak was also false: ADR-0090 D3 claims
sys_member.roleis "already relabelledorg_membership_levelin the platform
projection", butorg_membership_levelexisted nowhere in the codebase and
ADR-0057 D7 lists that relabel under "Deferred (evidence-gated, P4)". The
projection never landed, so the word reached authors.The name manufactured a real, silent failure — "hotcrm class": every other
surface renamed toposition(sys_role,ShareRecipientType.role,
ctx.roles[]), so{ type: 'role', value: 'sales_manager' }reads as the
legacy spelling of a position. It resolves against the membership tier, finds
no member row, falls back to an inertrole:sales_managerliteral, and the
request waits forever on an approver that cannot exist.- spec:
ApproverTypegainsorg_membership_level;rolestays as a
deprecated alias for one window (a published 15.x flow keeps loading) with
DEPRECATED_APPROVER_TYPES+canonicalApproverType()as the single source
for the mapping. Removed in the next major. - plugin-approvals: resolves on the canonical type and warns on the
deprecated spelling. Thetype:valuefallback literal keeps the authored
spelling — storedsys_approval_approverrows andpending_approversslots
from 15.x carryrole:<v>, and rewriting it would orphan them. - lint:
approval-role-not-membership-tier→approval-approver-not-membership-tier
(the rule id carried the reserved word too), plus a new
approval-approver-type-deprecated. The two are mutually exclusive: a bad
value wins, because prescribingorg_membership_levelfor a position name
would be wrong advice — the fix there isposition.
Authoring
type: 'role'keeps working and now says so out loud. Rewrite it as
org_membership_level; if the value is an org position, the fix isposition. - spec:
-
2ea08ee: Flow trigger observability — kill the four-layer silence around record-change flows that never fire (2026-07-17 third-party eval).
A misauthored auto-launched flow (wrong
objectName, missingrequires: ['automation','triggers'], failing start condition) produced ZERO output at every layer: the engine's own registration/binding logs land inside the CLI's boot-quiet stdout window (which swallows debug/info/warn — only error/fatal reach stderr), and each "didn't happen" path was itself silent. Fixes:- Startup banner
Flows:section (os serve/os dev/os start): flow count, bound-to-trigger count, registered trigger types, draft count — plus loud⚠lines for flows declared with no automation engine enabled (requiresmissing), flows whose trigger type has no registered trigger, and bound record-change flows targeting an unknown object (dead binding). Printed after stdout is restored, so it is immune to the boot-quiet window. - Trigger-fired run failures now log at ERROR (stderr — always visible): the automation engine no longer drops the AutomationResult of a trigger-fired execution; condition-evaluation faults and node failures surface with the flow name. Condition-not-met skips stay at debug (high-frequency, intentional).
RecordChangeTriggerprobes object existence at bind time and warns when a flow'sobjectNamematches no registered object (exact-name matching), instead of silently arming a hook that can never fire.kernel:bootstrappedbinding audit in the automation plugin: warns per enabled-but-unbound triggered flow with the reason, and reports registered/bound/draft counts (AutomationEngine.getTriggerBindingAudit(), extendedgetFlowRuntimeStates()withstatus/triggerType/object).os validateflow-wiring advisories (@objectstack/lintvalidateFlowTriggerReadiness): warns when a record-triggered flow targets an object the stack does not define, and when an auto-triggered flow's status isdraft(authored or defaulted — draft flows still fire; declareactiveorobsolete).- Removed leftover boot-debug writes (
registerApp/AppPlugin/StandaloneStack/AuditPluginstderr noise) that previous debugging of this same silence had left behind.
- Startup banner
-
ea32ec7: feat(formula,lint): advisory type-soundness warnings for formula/predicate expressions (#1928 tier 4)
Closes the last open guardrail from #1928. A
Field.formulaor record-scoped
predicate that uses a text or boolean field with an arithmetic (+ - * / %)
or ordering (< > <= >=) operator against a number faults the runtime
overload and silently evaluates tonull(e.g.record.title * 2,
record.is_active + 1). The build now surfaces this as a non-blocking
warning with the offending field and a corrective message.Honours the ADR-0032 design law — the checker only flags what the runtime
would also fail:- Number / currency / percent / date / datetime fields are declared
dyn, so
the cases the runtime rescues never warn —record.amount / 100(the #1930
registerOperatorfix),record.due == today()and numeric-string / ISO-date
values (the string-hydration retry), and numeric-codedselectoption values. - Equality (
==/!=) is excluded: a heterogeneous equality is runtime-safe
(evaluates tofalse), never a fault.
New
firstTypeMismatch(source, fieldCelTypes, scope)export in
@objectstack/formula(and an optionalfieldTypeshint on
validateExpression);@objectstack/lint'svalidateStackExpressionsthreads
each object's field types into every checked site:- record-scoped sites (
record.<field>) — formula fields, validation rules,
action / hook / sharing predicates; - flattened flow / automation conditions (bare
field) — where flow
variables staydynand are never flagged, and equality stays runtime-safe.
Warnings are advisory in
objectstack build/validate(fatal only under
--strict), matching the tier-3 channel. - Number / currency / percent / date / datetime fields are declared
-
a2795f6: feat(triggers): declarative time-relative trigger — daily sweep instead of fragile date-equality (#1874)
Time-relative business rules ("alert 60 days before a contract's
end_date")
could only be expressed as arecord_changeflow gated on a date-equality
condition likeend_date == daysFromNow(60). That predicate is only evaluated
when the record happens to change, so it fires only if a record is edited on
exactly the threshold day — i.e. almost never, unattended. The robust
alternative was a hand-written cron + range query that every author
re-implemented (contractsrenewal_alert, hrdocument_expiring_soon,
procurementpo_overdue, …).A flow's start node can now declare a
timeRelativedescriptor instead:config: { timeRelative: { object: 'contracts', dateField: 'end_date', offsetDays: [60, 30, 7], // T-minus reminders — fires on each threshold day // — or — withinDays: 30 // "expiring soon" range; negative = overdue lookback filter: { status: 'active' }, // optional, ANDed with the date window }, schedule: { type: 'cron', expression: '0 8 * * *' }, // optional; defaults to daily 08:00 UTC }
The new
time_relativetrigger (shipped in@objectstack/trigger-scheduleas
TimeRelativeTriggerPlugin) sweeps the object on that schedule and launches the
flow once per matching record, with the record on the automation context —
so the start-nodeconditiongate and{record.<field>}interpolation work
exactly as for a record-change flow. Because the window is evaluated every day,
a threshold is never missed regardless of when the record last changed. The
discovery query runs as a system operation (RLS-bypassing) and is capped
(maxRecords, default 1000) so a mis-scoped window can't fan out unboundedly;
per-record failures are isolated so one bad row never aborts the sweep.The automation engine routes a start node carrying
config.timeRelativeto the
time_relativetrigger (ahead of the plainscheduletrigger, whose behavior is
unchanged), andos validategains readiness checks for the new descriptor
(unknown swept object, ambiguous draft status). New authorable spec key:
TimeRelativeTriggerSchema(@objectstack/spec/automation).
Patch Changes
-
524696a: feat(spec)!:
DashboardWidgetSchema.strict()— reject undeclared widget keys (framework#3251)The ADR-0021 analytics endpoint.
DashboardWidgetSchemanow rejects any
undeclared top-level key instead of silently stripping it, moving a whole class
of author error (a hallucinated or legacy key that renders as a silent no-op)
from fallible human review to deterministic CI.options: z.unknown()remains
the escape hatch for renderer-specific extras.A custom error map names the offending key(s) and, when a key is a removed
pre-ADR-0021 inline-analytics key (object/categoryField/valueField/
aggregate, pivotrowField/columnField) or an objectui-internal prop
(component, inlinedata), points the author at the dataset shape
(dataset+dimensions+values).Recorded as protocol-16 migration
step16
(dashboard-widget-strict-unknown-keys), mirroring protocol-15'sstep15
strict flip on the form/page schemas (ADR-0089 D3a). The inline-analytics shape
itself was already removed at protocol 9 (single-form cutover), so there is no
...
@objectstack/knowledge-ragflow@16.0.0
Patch Changes
- Updated dependencies [f972574]
- Updated dependencies [6289ec3]
- Updated dependencies [22013aa]
- Updated dependencies [3ad3dd5]
- Updated dependencies [8efa395]
- Updated dependencies [3a18b60]
- Updated dependencies [a8aa34c]
- Updated dependencies [e057f42]
- Updated dependencies [a3823b2]
- Updated dependencies [43a3efb]
- Updated dependencies [524696a]
- Updated dependencies [bfa3c3f]
- Updated dependencies [5e3301d]
- Updated dependencies [dd9f223]
- Updated dependencies [46e876c]
- Updated dependencies [5f05de2]
- Updated dependencies [021ba4c]
- Updated dependencies [158aa14]
- Updated dependencies [62a2117]
- Updated dependencies [d2723e2]
- Updated dependencies [fefcd54]
- Updated dependencies [beaf2de]
- Updated dependencies [369eb6e]
- Updated dependencies [06ff734]
- Updated dependencies [b659111]
- Updated dependencies [5754a23]
- Updated dependencies [6c270a6]
- Updated dependencies [290e2f0]
- Updated dependencies [668dd17]
- Updated dependencies [8abf133]
- Updated dependencies [e0859b1]
- Updated dependencies [04ecd4e]
- Updated dependencies [4d5a892]
- Updated dependencies [16cebeb]
- Updated dependencies [86d30af]
- Updated dependencies [8923843]
- Updated dependencies [a2795f6]
- Updated dependencies [f16b492]
- Updated dependencies [4b6fde8]
- Updated dependencies [2018df9]
- Updated dependencies [fc5a3a2]
- Updated dependencies [8ff9210]
- @objectstack/spec@16.0.0
- @objectstack/core@16.0.0
- @objectstack/service-knowledge@16.0.0
@objectstack/knowledge-memory@16.0.0
Patch Changes
- Updated dependencies [f972574]
- Updated dependencies [6289ec3]
- Updated dependencies [22013aa]
- Updated dependencies [3ad3dd5]
- Updated dependencies [8efa395]
- Updated dependencies [3a18b60]
- Updated dependencies [a8aa34c]
- Updated dependencies [e057f42]
- Updated dependencies [a3823b2]
- Updated dependencies [43a3efb]
- Updated dependencies [524696a]
- Updated dependencies [bfa3c3f]
- Updated dependencies [5e3301d]
- Updated dependencies [dd9f223]
- Updated dependencies [46e876c]
- Updated dependencies [5f05de2]
- Updated dependencies [021ba4c]
- Updated dependencies [158aa14]
- Updated dependencies [62a2117]
- Updated dependencies [d2723e2]
- Updated dependencies [fefcd54]
- Updated dependencies [beaf2de]
- Updated dependencies [369eb6e]
- Updated dependencies [06ff734]
- Updated dependencies [b659111]
- Updated dependencies [5754a23]
- Updated dependencies [6c270a6]
- Updated dependencies [290e2f0]
- Updated dependencies [668dd17]
- Updated dependencies [8abf133]
- Updated dependencies [e0859b1]
- Updated dependencies [04ecd4e]
- Updated dependencies [4d5a892]
- Updated dependencies [16cebeb]
- Updated dependencies [86d30af]
- Updated dependencies [8923843]
- Updated dependencies [a2795f6]
- Updated dependencies [f16b492]
- Updated dependencies [4b6fde8]
- Updated dependencies [2018df9]
- Updated dependencies [fc5a3a2]
- Updated dependencies [8ff9210]
- @objectstack/spec@16.0.0
- @objectstack/core@16.0.0
- @objectstack/service-knowledge@16.0.0