Releases: objectstack-ai/framework
@objectstack/observability@5.0.0
Patch Changes
- Updated dependencies [2f9073a]
- @objectstack/spec@5.0.0
@objectstack/objectql@5.1.0
Patch Changes
-
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.approval
also corrected tosupportsVersioning: true(it was wronglyfalse).MetadataRepository.getByHash(ref, hash)added to the interface. Production
implementation inSysMetadataRepositoryresolves historical bodies through
sys_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_history
tables 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 reading
metadata_id— if you are, switch to(organization_id, type, name, version).See ADR-0008 §14 for the full rationale.
-
-
Updated dependencies [75f4ee6]
-
Updated dependencies [823d559]
- @objectstack/spec@5.1.0
- @objectstack/metadata-core@5.1.0
- @objectstack/core@5.1.0
- @objectstack/formula@5.1.0
- @objectstack/types@5.1.0
@objectstack/objectql@5.0.0
Minor Changes
-
5e9dcb4: BREAKING — metadata: remove
projectandbranchfromMetaRefThe metadata layer no longer models project or branch. Customisation is now
scoped purely to organisation. Project remains exclusively as an artifact
packaging concept (theobjectstack.jsonbundle envelope); branching is left
to Git.What changed:
MetaRefis now{ org, type, name, version? }(was
{ org, project, branch, type, name, version? }).refKey()is the two
segment string${org}/${type}/${name}(was five segments).MetadataItem.seqis monotonic per org (was per branch).BranchRef,MergeStrategy,MergeResulttypes and the optional
fork/mergemethods onMetadataRepositoryare removed.ListFilter/WatchFilter/HistoryOptionsno longer acceptproject
orbranch.FileSystemRepositorydisk layout simplified to
<root>/<type>/<name>.json(was<root>/<project>/<branch>/<type>/<name>.json);
change-log path is now.objectstack/.log/main.jsonlregardless of any
branch concept. Constructor no longer acceptsproject/branch.SysMetadataRepository: removedprojectLabel/branchLabeloptions;
thesys_metadataschema'sproject_id/branchcolumns (if present)
are ignored. A future major release willDROPthem.MetadataManager.setRepository(repo, opts)no longer takes an opts object
withbranch.
Migration:
-const ref = { org: 'acme', project: 'crm', branch: 'main', type: 'view', name: 'home' }; +const ref = { org: 'acme', type: 'view', name: 'home' }; -new FileSystemRepository({ root, org: 'acme', project: 'crm', branch: 'main' }); +new FileSystemRepository({ root, org: 'acme' });
Existing
sys_metadatarows continue to load; the deprecated columns are
ignored at read time. -
f139a24: Subscribe
ObjectQLPlugintometadata.subscribe('object', …)so the
SchemaRegistrymerge cache is invalidated and the affected object
re-registered on every object metadata change (ADR-0008 M0 PR-7).Combined with the PR-6 metadata ↔ repository bridge, this closes the
Studio HMR loop end-to-end: editing an object definition (file, REST
write, or Studio inline edit) emits aMetadataEvent, which flows
throughMetadataManager.subscribe('object', …)into ObjectQL, which
drops the cached merged definition and re-fetches the canonical body
from the metadata service. Subsequent reads see the new schema with
no server restart.Additions:
SchemaRegistry.invalidate(fqnOrName)andinvalidateAll()—
public hooks for event-driven cache eviction; contributors are
preserved soresolveObjectrecomputes against the next call.ObjectQLPlugin.start()wires the subscription when the metadata
service exposessubscribe(). The handler invalidates, re-fetches
viametadata.get('object', name), and re-registers with the
originalpackageId/namespace. Deletes only invalidate.ObjectQLPlugin.stop()drains the subscription handles so test
reloads don't leak watchers.
-
2f7e42a: ADR-0008 M0 PR-10b: introduce
SysMetadataRepository— a
MetadataRepositorywrapper over the existingsys_metadatatable.
M0 keeps single-row update semantics (append-only event log is M1
work). Whitelist enforcement, optimistic locking via content hash,
and in-process watch fan-out are all live. Not yet wired into any
production write path — PR-10c will compose it under a
LayeredRepository. -
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.
-
09f005a: PR-10d.5 — Flip default of
useRepositoryWritePathtotrue.saveMetaItemnow routes overlay-allowed metadata types (view, dashboard,
report, email_template) throughSysMetadataRepository.putby default —
every write appends to the change log and emits a watch event with a
monotonicseqfor HMR / replay.Non-overlay-allowed types (
object,flow,agent, ...) still take the
legacy raw-engine path. This preserves control-plane bootstrap behaviour
(which writesobject/flowdefinitions viasaveMetaItemand is
permitted by the outer protocol gate to write any type whenprojectId
is undefined).Opt-out remains available during the deprecation window:
- Constructor:
new ObjectStackProtocolImplementation(engine, …, { useRepositoryWritePath: false }) - Env var:
OBJECTSTACK_USE_REPOSITORY_WRITE_PATH=0
The legacy raw-engine branch for overlay-allowed types is scheduled for
removal in PR-10d.6 once this default has soaked for one release. - Constructor:
Patch Changes
-
4eb9f8c: ADR-0008 M0 PR-10a: pin overlay-whitelist + canonical-hash invariants
before re-expressing the overlay path as a LayeredRepository. No
runtime change — adds 28 regression tests that fail loud if a future
PR weakens the shared-DB tenancy contract or breaks hash stability. -
602cce7: test(objectql): integration coverage for
LayeredRepositorycomposed of
SysMetadataRepository(top, writable overlay) overInMemoryRepository
(bottom, artifact baseline). Verifies read fallthrough, overlay-wins
precedence, write routing, delete behavior, event source tagging across
layers, and merged-list semantics. Part of ADR-0008 PR-10c. -
1e625b8: feat(objectql): hash-compat dry-run probe for the legacy → repository
write-path migration (ADR-0008 PR-10d.1). Pure-functionrunDryRun()plus
a CLI (scripts/dry-run-hash-compat.ts) that audits a snapshot of
sys_metadatafor invalid JSON, non-object bodies, unstable hashes across
canonical round-trip, and duplicate overlay keys. Exits non-zero when
incompatibilities are found. 14 unit tests covering happy paths, error
classifications (invalid_json,non_object_body,unstable_hash,
missing_metadata,duplicate_overlay_key), and boundary conditions
(empty snapshot, deep nesting, unicode). -
6ee42b8: fix(objectql): SysMetadataRepository reuses the existing
checksumcolumn
instead of writing a non-existent_hashcolumn (ADR-0008 PR-10d.2). The
productionsys_metadataschema (packages/platform-objects) already
ships withchecksum: text(64)— perfect for sha256 hex — andversion: numberfor the monotonic counter. No DDL migration is required for
PR-10d.3 cutover; legacy rows with NULL checksum will be lazily
backfilled on first put().Also extends the PR-10d.1 dry-run probe with two new checks
(checksum_missingwarning,checksum_drifterror) and three additional
tests, taking objectql to 325/325 green. -
5cfdc85: PR-10d.4 — REST plumbing for the metadata repository write path.
PUT /api/v1/meta/:type/:name(and the compound:type/:section/:namevariant)
now forwards theIf-Matchheader tosaveMetaItemasparentVersion, and
X-Actor(orreq.user.id) asactor. ETag-style quotes are stripped.- A failed optimistic-lock check surfaces as HTTP 409 with body
{ "error": "...", "code": "metadata_conflict" }(no protocol changes —
sendErroralready honourederror.status+error.code). - Added a real-engine integration test for the repository write path
(protocol-save-meta-repo-path-real-engine.test.ts) — addresses the
PR-10d.3 rubber-duck stub-drift concern by exercising
ObjectStackProtocolImplementation.saveMetaItemthroughnew ObjectQL()
with an inline in-memory driver. Covers insert→update version bump,
parentVersion conflict, checksum length, and plural→singular normalization.
Default behaviour unchanged: the repository write path remains opt-in via
options.useRepositoryWritePath/OBJECTSTACK_USE_REPOSITORY_WRITE_PATH=1.
Flag flip and legacy path removal will follow in a separate post-soak PR. -
7825394: PR-10d.6 — remove
useRepositoryWritePathfeature flag.Overlay-allowed metadata types (
view,dashboard,report,
email_template) now unconditionally route through
SysMetadataRepository.put(change-log + HMRseq). The legacy
raw-engine branch is retained for non-overlay types (object,flow,
agent, etc.) used during control-plane bootstrap, since the repository
assertAllowed()whitelist would reject them.Removed:
ObjectStackProtocolImplementationconstructor option
{ useRepositoryWritePath: boolean }.OBJECTSTACK_USE_REPOSITORY_WRITE_PATHenvironment variable.
There is no opt-out: behavior is now equivalent to the PR-10d.5 default.
-
96ad4df: Fix dev-mode HMR data-reload for
*.view.ts/*.flow.tssource...
@objectstack/objectql@4.2.0
Minor Changes
-
2869891: feat: Optimistic Concurrency Control (OCC) via
If-MatchUpdate and Delete requests now accept an optional version token. When supplied,
the protocol compares it against the record's currentupdated_at(orversion
column when available) and rejects with409 CONCURRENT_UPDATEon mismatch,
preventing silent overwrites when two clients edit the same record.Wire formats (opt-in, all server- and client-backward-compatible):
-
PATCH /data/{object}/{id}— supportsIf-Match: "<token>"header
orexpectedVersion: "<token>"body field (body wins when both present). -
DELETE /data/{object}/{id}— supportsIf-Matchheader or
?expectedVersion=...query param. -
Conflict response:
409 { error, code: 'CONCURRENT_UPDATE', currentVersion, currentRecord }so the client can offer Reload / Overwrite / Cancel UX.Behaviour
-
Missing/empty version → no check (legacy callers unaffected).
-
Record not found during the version probe → no check; the downstream write
produces a normal404. -
Object has no
updated_atcolumn → no check (explicit opt-out for objects
without timestamps). -
Quoted RFC-7232 tokens (
"…") are accepted and unquoted before comparison.Client
client.data.update(resource, id, data, { ifMatch })and
client.data.delete(resource, id, { ifMatch })now forward the token as an
If-Matchheader.Application-level CAS (findOne + compare in protocol.ts) is used in this slice
to avoid touching every storage driver. A small TOCTOU window remains; for the
B2B record-editing latencies this protects against, it is more than sufficient.
Drivers may later be upgraded to atomicWHERE id=? AND updated_at=?writes
for true CAS without changing the public API.Tests: 7 new cases in
protocol-data.test.tscover opt-in, match, mismatch,
quote-stripping, no-timestamps, empty-token, and the delete path.
-
Patch Changes
- Updated dependencies [2869891]
- @objectstack/spec@4.2.0
- @objectstack/core@4.2.0
- @objectstack/formula@4.2.0
- @objectstack/types@4.2.0
@objectstack/objectql@4.1.1
Patch Changes
- @objectstack/spec@4.1.1
- @objectstack/core@4.1.1
- @objectstack/types@4.1.1
- @objectstack/formula@4.1.1
@objectstack/nuxt@5.1.0
@objectstack/nuxt@5.1.0
@objectstack/nuxt@5.0.0
@objectstack/nuxt@4.2.0
@objectstack/nuxt@4.2.0
@objectstack/nuxt@4.1.1
@objectstack/nuxt@4.1.1
@objectstack/nextjs@5.1.0
@objectstack/nextjs@5.1.0