import { z } from 'zod'; /** * Metadata Repository types — see ADR-0008 §2. * * All shapes are defined as Zod schemas so the same definition serves * runtime validation and static typing (`z.infer`). */ /** * Canonical metadata type names. Aligned with the `MetadataTypeSchema` * enum in `@objectstack/spec/kernel/metadata-plugin.zod.ts`. New types are * added here in lockstep with that file. */ declare const MetadataTypeSchema: z.ZodEnum<{ object: "object"; field: "field"; hook: "hook"; mapping: "mapping"; view: "view"; page: "page"; dashboard: "dashboard"; app: "app"; action: "action"; flow: "flow"; workflow: "workflow"; job: "job"; agent: "agent"; tool: "tool"; skill: "skill"; report: "report"; translation: "translation"; role: "role"; profile: "profile"; permission: "permission"; policy: "policy"; api: "api"; endpoint: "endpoint"; datasource: "datasource"; cube: "cube"; settings: "settings"; email_template: "email_template"; }>; type MetadataType = z.infer; /** * Fully-qualified reference to a metadata item. Identity is `(org, type, name)`. * * Per ADR-0008 v2 (2026-05) the metadata layer no longer carries `project` * or `branch`. Project survives only as an **artifact packaging concept** * (the unit a CLI/CI run compiles into `dist/objectstack.json`); it does * not appear in the runtime customization scope. Branching belongs to Git * (or your VCS of choice) and never propagated cleanly into the runtime * model — so it has been removed entirely. * * Higher layers may default `org='system'` for built-ins. * * `version` is optional: omit to mean "HEAD", supply to pin. */ declare const MetaRefSchema: z.ZodObject<{ org: z.ZodString; type: z.ZodEnum<{ object: "object"; field: "field"; hook: "hook"; mapping: "mapping"; view: "view"; page: "page"; dashboard: "dashboard"; app: "app"; action: "action"; flow: "flow"; workflow: "workflow"; job: "job"; agent: "agent"; tool: "tool"; skill: "skill"; report: "report"; translation: "translation"; role: "role"; profile: "profile"; permission: "permission"; policy: "policy"; api: "api"; endpoint: "endpoint"; datasource: "datasource"; cube: "cube"; settings: "settings"; email_template: "email_template"; }>; name: z.ZodString; version: z.ZodOptional; }, z.core.$strip>; type MetaRef = z.infer; /** * Construct a stable string key from a MetaRef (excluding `version`, * which is mutable). Used as cache keys and log indexes. */ declare function refKey(ref: Pick): string; /** * Full metadata item as stored / returned by the Repository. * * `body` is the **canonical, Zod-normalised** spec (with defaults filled * in). `hash` is `sha256(canonicalize(body))`. Equal hashes imply equal * specs. */ declare const MetadataItemSchema: z.ZodObject<{ ref: z.ZodObject<{ org: z.ZodString; type: z.ZodEnum<{ object: "object"; field: "field"; hook: "hook"; mapping: "mapping"; view: "view"; page: "page"; dashboard: "dashboard"; app: "app"; action: "action"; flow: "flow"; workflow: "workflow"; job: "job"; agent: "agent"; tool: "tool"; skill: "skill"; report: "report"; translation: "translation"; role: "role"; profile: "profile"; permission: "permission"; policy: "policy"; api: "api"; endpoint: "endpoint"; datasource: "datasource"; cube: "cube"; settings: "settings"; email_template: "email_template"; }>; name: z.ZodString; version: z.ZodOptional; }, z.core.$strip>; body: z.ZodRecord; hash: z.ZodString; parentHash: z.ZodNullable; authoredBy: z.ZodNullable; authoredAt: z.ZodString; message: z.ZodOptional; seq: z.ZodNumber; schemaVersion: z.ZodOptional; }, z.core.$strip>; type MetadataItem = z.infer; /** Lightweight header for listing — `body` omitted. */ type MetadataItemHeader = Omit; declare const MetadataOpSchema: z.ZodEnum<{ create: "create"; update: "update"; delete: "delete"; rename: "rename"; publish: "publish"; revert: "revert"; }>; type MetadataOp = z.infer; /** * The single event payload broadcast by the change log. ADR-0008 §2.4. * * For `rename`, `previousName` carries the old machine name. For * `delete`, `hash` is null. The payload is intentionally small — * consumers re-fetch via the cache when they need the full body. */ declare const MetadataEventSchema: z.ZodObject<{ seq: z.ZodNumber; op: z.ZodEnum<{ create: "create"; update: "update"; delete: "delete"; rename: "rename"; publish: "publish"; revert: "revert"; }>; ref: z.ZodObject<{ org: z.ZodString; type: z.ZodEnum<{ object: "object"; field: "field"; hook: "hook"; mapping: "mapping"; view: "view"; page: "page"; dashboard: "dashboard"; app: "app"; action: "action"; flow: "flow"; workflow: "workflow"; job: "job"; agent: "agent"; tool: "tool"; skill: "skill"; report: "report"; translation: "translation"; role: "role"; profile: "profile"; permission: "permission"; policy: "policy"; api: "api"; endpoint: "endpoint"; datasource: "datasource"; cube: "cube"; settings: "settings"; email_template: "email_template"; }>; name: z.ZodString; version: z.ZodOptional; }, z.core.$strip>; hash: z.ZodNullable; parentHash: z.ZodNullable; version: z.ZodOptional; previousName: z.ZodOptional; actor: z.ZodNullable; message: z.ZodOptional; ts: z.ZodString; source: z.ZodString; }, z.core.$strip>; type MetadataEvent = z.infer; /** * Two-tier metadata authorization intent (ADR-0005 extension). * * - `override-artifact`: the write targets an item that ships from a code * package (an artifact). Only permitted when the type opts into * per-org overlay writes via `allowOrgOverride: true`. * - `runtime-only`: the write targets a brand-new item OR an item that * exists only in `sys_metadata` (no artifact backing). Permitted for * types that opt into runtime creation via `allowRuntimeCreate: true`, * even when they explicitly forbid artifact overrides. * * The protocol layer determines the intent by consulting the schema * registry; the repository's `assertAllowed()` enforces it as * defense-in-depth. Defaults to `override-artifact` for backward * compatibility with callers that predate the two-tier model. */ type MetadataWriteIntent = 'override-artifact' | 'runtime-only'; interface PutOptions { /** * Hash this writer believed was at HEAD. `null` means "creating, expect * absence". A mismatch throws ConflictError. */ parentVersion: string | null; /** * Identity of the writer; mirrored to MetadataEvent.actor and stored in * `sys_metadata_history.recorded_by` (a `lookup('sys_user')`). * * **Required but nullable, deliberately** (#4556). Pass `null` — never a * label like `'system'` — when the write has no human actor: a boot * metadata sync, a data migration, a scheduled job. Keeping the property * required rather than optional forces every call site to state which of * the two it is, so a forgotten actor cannot silently become a fake * foreign key in a lookup column. */ actor: string | null; /** Optional human-readable commit message. */ message?: string; /** Optional label for the change log "source" column. */ source?: string; /** Two-tier authorization intent; defaults to `override-artifact`. */ intent?: MetadataWriteIntent; /** * Software-package id to bind this metadata row to (`sys_metadata.package_id`). * Set when authoring inside a Studio package workspace. On create the row is * stamped with this id; on update an existing non-null binding is preserved * (never silently re-bound). Omit/undefined for env-local overlays. */ packageId?: string | null; } interface PutResult { /** New content hash assigned to the spec. */ version: string; /** Sequence number of the emitted MetadataEvent. */ seq: number; /** The committed item (canonicalised). */ item: MetadataItem; } interface DeleteOptions { parentVersion: string; /** Identity of the writer; `null` = system-initiated. See {@link PutOptions.actor}. */ actor: string | null; message?: string; source?: string; /** Two-tier authorization intent; defaults to `override-artifact`. */ intent?: MetadataWriteIntent; } interface DeleteResult { seq: number; } interface ListFilter { org?: string; type?: MetadataType; /** Substring match on `name`; case-sensitive. */ nameContains?: string; /** Pagination cursor; opaque string from a previous response. */ cursor?: string; /** Page size; implementations may clamp. */ limit?: number; } interface WatchFilter { org?: string; type?: MetadataType; /** When omitted, match all names within the scope. */ name?: string; } interface HistoryOptions { /** Lower bound (exclusive) for pagination. */ sinceSeq?: number; limit?: number; } /** * The `MetadataRepository` interface — single point of pluggability for * the metadata storage backend. See ADR-0008 §2.6. * * Implementations: * * - `InMemoryRepository` (this package, for tests & edge) * - `FileSystemRepository` (`@objectstack/metadata`) * - `LayeredRepository` (`@objectstack/metadata`) * - `PostgresRepository` (`@objectstack/metadata-postgres`, M1) * * Implementation contract — what every backend MUST guarantee: * * 1. **Atomic put.** A successful `put()` either fully applies (item * visible to subsequent `get` AND an event present in the log) or * does not apply at all. No half-states. * 2. **Monotonic seq per org.** `seq` is strictly increasing within * `org`. Different orgs have independent sequences. (Repositories * scoped to a single org may treat the entire repo as one log.) * 3. **Optimistic locking.** `put` and `delete` throw `ConflictError` * when `parentVersion` does not match the current HEAD. * 4. **Canonical hashing.** `item.hash === hashSpec(item.body)` — always. * 5. **Event ordering.** Subscribers to `watch()` receive events in * monotonically-increasing `seq` order with no gaps. * 6. **Resumability, and where it stops.** `watch(_, since)` called with a * NUMBER MUST replay all events with `seq > since` before delivering live * events. Called with NO `since`, `watch()` owes **live events only** — * the events that commit after the subscription is established; an * implementation MAY additionally deliver events that had already * committed, but a caller MUST NOT rely on it, and a caller that needs the * already-committed prefix MUST pass a numeric `since` (or read * `history()`). Neither form may deliver the same `seq` twice. * * The second sentence is written down because it was load-bearing while * unwritten. Invariant 6 spoke only of `seq > since`, and with no `since` * there is no such set — so "no `since` replays everything" existed only * as `InMemoryRepository`'s implementation, and the shared contract suite * silently leaned on it. Two of the three implementations shipped today do * replay the whole matching log on a bare `watch(filter)` * (`InMemoryRepository`, `FileSystemRepository`); `SysMetadataRepository` * delivers live events only. That spread is exactly why this is a MAY and * not a MUST in either direction: forbidding the replay would break two * implementations and the consumers that lean on them, requiring it would * flood every `MetadataManager.setRepository()` / `MetadataCache.start()` * — both of which subscribe with no `since` — with the org's entire * history at attach time. What a consumer may *rely* on is the floor, and * the floor is now stated instead of inherited from whichever * implementation was read first. * 7. **Tombstones, not holes.** `delete` produces a `delete` event; * `get` returns null but `history` still shows the lineage. * 8. **Shutdown terminates; it does not emit.** An implementation that offers * a repository-level shutdown (`close()`) MUST end every live `watch()` * iterator: a `next()` parked at that moment settles with `done: true` and * no value, and every later `next()` does the same. That is the identical * observation the consumer's own `iterator.return()` produces, deliberately * — so no consumer has to tell "the repository shut down under me" apart * from "I broke my own loop". Events still queued or unreplayed at that * moment MAY be dropped, on both paths alike. * * **Shutdown MUST NOT be delivered AS an event.** Written as a MUST NOT * because it was tried, and both of its halves were measured (#11021). A * synthetic "we are closing" event is subject to the very filters `watch()` * applies to real ones, so the subscriptions that most need draining are * exactly the ones that drop it: any non-empty `filter` rejects a ref * invented to belong to no org, and any numeric `since` rejects a seq * invented to precede every real one. Those consumers then wait forever, * because the same shutdown unsubscribes them. Meanwhile a consumer whose * filter happens to admit it is not rescued either — it reads a real * metadata change for a ref that never existed (invalidating caches and * re-emitting downstream), and its iterator hangs on the *next* pull * regardless, because delivering an event has never ended one. * * Stated conditionally because `close()` is not on the interface below; * it is offered by some implementations and not others. Where it is * offered, this is what it owes. Measured across today's three, and there * are **no declared exceptions**: `SysMetadataRepository` conforms (#11021); * `FileSystemRepository` conforms (#11127 — its `close()` used to retire * the filesystem watcher and the resync sweep without ever reaching its * event broker, leaving a parked iterator parked for every subscription * shape, `watch({})` included; it now runs each subscription's terminator); * `InMemoryRepository` offers no repository-level shutdown at all, so its * iterators end only through `return()`. * * A new implementation that offers `close()` joins that list or it does not * conform — this row carries the measurement, so an implementation added * without one is the omission, not an exception. */ interface MetadataRepository { /** Read HEAD or a pinned version. Returns null if absent. */ get(ref: MetaRef): Promise; /** * Resolve a historical version by content hash (ADR-0009). * * Returns the `MetadataItem` whose canonical sha256 equals `hash` * for the given ref, or `null` if no such version is recorded. * * Implementations MUST search history (not just HEAD) so that * `executionPinned` types remain resolvable through definition * upgrades. For non-`executionPinned` types, implementations MAY * return `null` if they have GC'd the corresponding history row. */ getByHash(ref: MetaRef, hash: string): Promise; /** * Write a new version. Atomic. * @throws ConflictError if `parentVersion` does not match HEAD. * @throws SchemaValidationError if `spec` fails Zod normalisation. */ put(ref: MetaRef, spec: unknown, opts: PutOptions): Promise; /** * Soft-delete (tombstone). `parentVersion` is required. * @throws ConflictError on parent mismatch. */ delete(ref: MetaRef, opts: DeleteOptions): Promise; /** Enumerate items matching a filter. Implementations may stream. */ list(filter: ListFilter): AsyncIterable; /** Per-item history; events in monotonic `seq` order. */ history(ref: MetaRef, opts?: HistoryOptions): AsyncIterable; /** * Live event stream. The iterator MUST: * * - When `since` is a number: replay all events with `seq > since` * before yielding any new event. * - When `since` is omitted: deliver live events only — the events that * commit after this subscription is established. Events that had * already committed MAY also be delivered, but callers MUST NOT rely * on it; a caller that needs them passes a numeric `since` or reads * `history()`. See invariant 6. * - Stay open until the consumer breaks the loop — or until the * repository shuts down under it, where an implementation offers a * `close()`. Both end the stream the same way: `done: true`, no value, * never a synthetic event standing in for shutdown. See invariant 8. * - Survive transient backend disconnects (implementation's choice * how to resume — Postgres LISTEN reconnect, JSONL tail, etc.). */ watch(filter: WatchFilter, since?: number): AsyncIterable; } /** * Sentinel symbol used by `LayeredRepository` (M0 PR-5) to label which * underlying layer emitted an event. Defined here so the contract is * shared. */ declare const LAYER_SOURCE: unique symbol; export { type DeleteOptions as D, type HistoryOptions as H, type ListFilter as L, type MetaRef as M, type PutOptions as P, type WatchFilter as W, type MetadataRepository as a, type MetadataItem as b, type PutResult as c, type DeleteResult as d, type MetadataItemHeader as e, type MetadataEvent as f, LAYER_SOURCE as g, MetaRefSchema as h, MetadataEventSchema as i, MetadataItemSchema as j, type MetadataOp as k, MetadataOpSchema as l, type MetadataType as m, MetadataTypeSchema as n, type MetadataWriteIntent as o, refKey as r };