@objectstack/metadata-protocol 11.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +7 -0
- package/dist/build-probes-I3227FYL.cjs +7 -0
- package/dist/build-probes-I3227FYL.cjs.map +1 -0
- package/dist/build-probes-TLWDII7Z.js +7 -0
- package/dist/build-probes-TLWDII7Z.js.map +1 -0
- package/dist/chunk-7LOFAEHA.cjs +161 -0
- package/dist/chunk-7LOFAEHA.cjs.map +1 -0
- package/dist/chunk-HJVPAKGD.js +639 -0
- package/dist/chunk-HJVPAKGD.js.map +1 -0
- package/dist/chunk-JRNTUZG6.cjs +639 -0
- package/dist/chunk-JRNTUZG6.cjs.map +1 -0
- package/dist/chunk-KJGVCNUC.js +161 -0
- package/dist/chunk-KJGVCNUC.js.map +1 -0
- package/dist/index.cjs +4855 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1893 -0
- package/dist/index.d.ts +1893 -0
- package/dist/index.js +4855 -0
- package/dist/index.js.map +1 -0
- package/dist/seed-loader-IFRY33L4.cjs +7 -0
- package/dist/seed-loader-IFRY33L4.cjs.map +1 -0
- package/dist/seed-loader-KNXGLL2V.js +7 -0
- package/dist/seed-loader-KNXGLL2V.js.map +1 -0
- package/package.json +61 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/framework/framework/packages/metadata-protocol/dist/index.cjs","../src/protocol.ts","../src/sys-metadata-repository.ts","../src/metadata-diagnostics.ts"],"names":["item","DEFAULT_METADATA_TYPE_REGISTRY","SINGULAR_TO_PLURAL","SeedLoaderService"],"mappings":"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","file":"/home/runner/work/framework/framework/packages/metadata-protocol/dist/index.cjs","sourcesContent":[null,"// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { ObjectStackProtocol } from '@objectstack/spec/api';\nimport { IDataEngine } from '@objectstack/core';\nimport { readEnvWithDeprecation } from '@objectstack/types';\nimport type { MetadataHostEngine } from './host-engine.js';\nimport { SysMetadataRepository, type SysMetadataEngine } from './sys-metadata-repository.js';\nimport { ConflictError } from '@objectstack/metadata-core';\nimport type {\n BatchUpdateRequest,\n BatchUpdateResponse,\n UpdateManyDataRequest,\n DeleteManyDataRequest,\n InstallPackageRequest,\n InstallPackageResponse\n} from '@objectstack/spec/api';\nimport type { MetadataCacheRequest, MetadataCacheResponse, ServiceInfo, ApiRoutes, WellKnownCapabilities } from '@objectstack/spec/api';\nimport type { IFeedService } from '@objectstack/spec/contracts';\nimport { parseFilterAST, isFilterAST } from '@objectstack/spec/data';\nimport { PLURAL_TO_SINGULAR, SINGULAR_TO_PLURAL } from '@objectstack/spec/shared';\nimport { type FormView, isAggregatedViewContainer } from '@objectstack/spec/ui';\nimport { METADATA_FORM_REGISTRY } from '@objectstack/spec/system';\nimport { DEFAULT_METADATA_TYPE_REGISTRY, getMetadataTypeSchema, getMetadataTypeActions, getMetadataCreateSeed } from '@objectstack/spec/kernel';\nimport {\n extractProtection,\n evaluateLockForWrite,\n evaluateLockForDelete,\n resolveLockState,\n type MetadataLock,\n type MetadataProvenance,\n} from '@objectstack/spec/kernel';\nimport { z } from 'zod';\nimport {\n computeMetadataDiagnostics,\n computeViewReferenceDiagnostics,\n decorateMetadataItem,\n decorateMetadataItems,\n type MetadataDiagnostics,\n} from './metadata-diagnostics.js';\n\n/**\n * Canonical Zod schema per metadata type lives in\n * `@objectstack/spec/kernel/metadata-type-schemas` and is exposed through\n * {@link getMetadataTypeSchema}. Both save-time validation\n * ({@link resolveOverlaySchema}) and the `/meta/types/:type` JSON Schema\n * emitter consult that single source of truth, so adding a new\n * metadata-type schema requires editing exactly one file (or calling\n * `registerMetadataTypeSchema()` from a plugin).\n */\n// (TYPE_TO_SCHEMA removed — use `getMetadataTypeSchema(type)` directly.)\n\n/**\n * Canonical {@link FormView} layout per metadata type. Sourced from the\n * shared {@link METADATA_FORM_REGISTRY} in `@objectstack/spec/system` so\n * the runtime form payload, the i18n extractor, and Studio all read from\n * a single source of truth.\n *\n * Types without an entry render with the auto-generated single-section\n * layout derived from their JSON Schema (acceptable for simple types).\n */\nconst TYPE_TO_FORM: Readonly<Record<string, FormView>> = METADATA_FORM_REGISTRY;\n\n/**\n * Convert a Zod schema to a JSON Schema, returning `undefined` if conversion\n * fails (e.g. unsupported constructs). Cached per schema reference.\n */\nconst _jsonSchemaCache = new WeakMap<z.ZodTypeAny, Record<string, unknown> | null>();\nfunction toJsonSchemaSafe(schema: z.ZodTypeAny): Record<string, unknown> | undefined {\n const cached = _jsonSchemaCache.get(schema);\n if (cached !== undefined) return cached ?? undefined;\n try {\n const result = z.toJSONSchema(schema, { unrepresentable: 'any' }) as Record<string, unknown>;\n _jsonSchemaCache.set(schema, result);\n return result;\n } catch {\n _jsonSchemaCache.set(schema, null);\n return undefined;\n }\n}\n\n/**\n * Hand-crafted fallback JSON Schemas for metadata types whose Zod schema\n * cannot be safely converted via `z.toJSONSchema()` (e.g. due to recursive\n * references or non-representable constructs like `z.lazy()` chains).\n *\n * These mirror the shape consumed by the corresponding `*.form.ts` layouts,\n * so the SchemaForm renderer can still produce a real form (instead of\n * falling back to the raw JSON editor). All fields use lenient types\n * (`string | object | array`) because the widget hint in the form layout\n * is what actually drives the UI control selection — the JSON Schema is\n * only used to (a) seed defaults and (b) report which property names exist.\n */\nconst HAND_CRAFTED_SCHEMAS: Record<string, Record<string, unknown>> = {\n object: {\n type: 'object',\n properties: {\n name: { type: 'string' },\n label: { type: 'string' },\n pluralLabel: { type: 'string' },\n icon: { type: 'string' },\n description: { type: 'string' },\n tags: { type: 'array', items: { type: 'string' } },\n active: { type: 'boolean', default: true },\n isSystem: { type: 'boolean', default: false },\n abstract: { type: 'boolean', default: false },\n datasource: { type: 'string' },\n fields: {\n // Canonical Object.fields is a name-keyed map\n // (Record<string, FieldDefinition>) — insertion order is\n // display order. The SchemaForm engine recognises\n // `additionalProperties` as a Record and dispatches to\n // the `record` form-field renderer (ADR-0007). The form\n // layout in `object.form.ts` declares `type: 'record'`\n // so the inner `additionalProperties` schema is used to\n // shape each value.\n type: 'object',\n default: {},\n additionalProperties: {\n type: 'object',\n properties: {\n name: { type: 'string' },\n label: { type: 'string' },\n type: { type: 'string' },\n required: { type: 'boolean', default: false },\n unique: { type: 'boolean', default: false },\n defaultValue: {},\n description: { type: 'string' },\n },\n required: ['type'],\n },\n },\n capabilities: { type: 'object', additionalProperties: true },\n },\n required: ['name'],\n additionalProperties: true,\n },\n action: {\n type: 'object',\n properties: {\n name: { type: 'string' },\n label: { type: 'string' },\n objectName: { type: 'string' },\n icon: { type: 'string' },\n type: { type: 'string', enum: ['url', 'flow', 'api', 'script'] },\n variant: { type: 'string', enum: ['primary', 'secondary', 'danger', 'ghost', 'outline'] },\n target: { type: 'string' },\n method: { type: 'string', enum: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'] },\n body: {\n type: 'array',\n default: [],\n items: {\n type: 'object',\n properties: {\n line: { type: 'string' },\n },\n },\n },\n params: {\n type: 'array',\n default: [],\n items: {\n type: 'object',\n properties: {\n name: { type: 'string' },\n label: { type: 'string' },\n type: { type: 'string' },\n required: { type: 'boolean', default: false },\n },\n required: ['name'],\n },\n },\n confirmText: { type: 'string' },\n successMessage: { type: 'string' },\n refreshAfter: { type: 'boolean', default: true },\n locations: {\n type: 'array',\n default: [],\n items: {\n type: 'object',\n properties: {\n location: { type: 'string' },\n },\n },\n },\n component: { type: 'string' },\n visible: { type: 'string' },\n disabled: { type: 'string' },\n shortcut: { type: 'string' },\n bulkEnabled: { type: 'boolean', default: false },\n aiExposed: { type: 'boolean', default: false },\n recordIdParam: { type: 'string' },\n recordIdField: { type: 'string' },\n bodyShape: { type: 'string', enum: ['flat', 'nested'] },\n },\n required: ['name', 'label', 'type'],\n additionalProperties: true,\n },\n // Validation rules live inside `object.validations[]`. The canonical\n // ValidationRuleSchema is a discriminated union of 9 variants; the\n // generic SchemaForm renderer treats unions as opaque JSON, so we\n // ship a *flat* form-friendly schema covering the common base\n // properties plus every variant-specific field as optional. Save-time\n // validation is unaffected — the union schema is still authoritative\n // at write time.\n validation: {\n type: 'object',\n properties: {\n // --- Base fields (all variants) ---\n name: { type: 'string', description: 'Unique rule name (snake_case)' },\n label: { type: 'string' },\n description: { type: 'string' },\n type: {\n type: 'string',\n enum: [\n 'script',\n 'unique',\n 'state_machine',\n 'format',\n 'cross_field',\n 'json',\n 'async',\n 'custom',\n 'conditional',\n ],\n default: 'script',\n description: 'Validation variant',\n },\n active: { type: 'boolean', default: true },\n events: {\n type: 'array',\n items: { type: 'string', enum: ['insert', 'update', 'delete'] },\n default: ['insert', 'update'],\n },\n priority: { type: 'number', default: 100, minimum: 0, maximum: 9999 },\n severity: {\n type: 'string',\n enum: ['error', 'warning', 'info'],\n default: 'error',\n },\n message: { type: 'string' },\n tags: { type: 'array', items: { type: 'string' } },\n // --- Variant-specific (all optional, gated by `type`) ---\n condition: {\n type: 'string',\n description: 'CEL predicate (type=script). True ⇒ validation fails.',\n },\n fields: {\n type: 'array',\n items: { type: 'string' },\n description: 'Fields (type=unique / cross_field).',\n },\n scope: { type: 'string', description: 'CEL scope predicate (type=unique).' },\n caseSensitive: { type: 'boolean', default: true },\n field: { type: 'string', description: 'Single field (type=state_machine / format).' },\n transitions: {\n type: 'object',\n additionalProperties: { type: 'array', items: { type: 'string' } },\n description: 'Map { OldState: [AllowedNewStates] } (type=state_machine).',\n },\n regex: { type: 'string', description: 'Regex (type=format).' },\n format: {\n type: 'string',\n enum: ['email', 'url', 'phone', 'json'],\n description: 'Built-in format (type=format).',\n },\n url: { type: 'string', description: 'Endpoint URL (type=async).' },\n handler: { type: 'string', description: 'Handler reference (type=custom).' },\n when: { type: 'string', description: 'Outer condition (type=conditional).' },\n },\n required: ['name', 'type', 'message'],\n additionalProperties: true,\n },\n};\n\n/**\n * Zod schemas used to validate overlay items before they are persisted into\n * `sys_metadata` by {@link ObjectStackProtocolImplementation.saveMetaItem}.\n *\n * Single source of truth: the spec-side {@link getMetadataTypeSchema}\n * registry (`@objectstack/spec/kernel/metadata-type-schemas`). Every\n * metadata type whose payload should round-trip through Studio's\n * generic editor maps to its canonical Zod schema there; this function\n * is a plural→singular adapter on top of it.\n *\n * Validation policy:\n * - `safeParse` is used so we can craft a 422 with structured `issues`.\n * - We do NOT replace the persisted document with `parsed.data`; the\n * original payload is stored verbatim so Studio-only auxiliary fields\n * (e.g. `isPinned`, `isDefault`, `sortOrder`) survive the round-trip.\n * - Types without a registered schema (the wiring-layer types\n * `function`/`service`/`router`, and any plugin types that have not\n * yet called `registerMetadataTypeSchema()`) fall through unvalidated.\n */\nfunction resolveOverlaySchema(type: string, _item: unknown): z.ZodTypeAny | null {\n const singular = PLURAL_TO_SINGULAR[type] ?? type;\n return getMetadataTypeSchema(singular) ?? null;\n}\n\n/**\n * Guarantee a `view` body carries a top-level `name`.\n *\n * {@link ObjectStackProtocolImplementation.getMetaItems} only surfaces a\n * sys_metadata overlay row when its parsed body has a top-level `name` (objects\n * and dashboards include one; some view producers — notably loose `{ list }`\n * fragments — do not, so the view is silently dropped from the object's view\n * list and never appears as a tab). We stamp the save name here, at the single\n * write chokepoint, without otherwise reshaping the document.\n *\n * Deliberately does NOT convert shape: both the `defineView` container form\n * (`{ list, listViews, … }`) and the `{ name, object, viewKind, config }`\n * record form are valid and the console consumes both — reshaping a container\n * into a record risks producing an invalid record (e.g. a non-`<object>.<key>`\n * name). Structural validity is enforced separately by the view metadata schema\n * during the spec-validation step. No-op for non-view types and bodies that\n * already carry a `name`.\n */\nexport function normalizeViewMetadata(type: string, item: unknown, saveName: string): unknown {\n const singular = PLURAL_TO_SINGULAR[type] ?? type;\n if (singular !== 'view') return item;\n if (!item || typeof item !== 'object' || Array.isArray(item)) return item;\n const it = item as Record<string, unknown>;\n return it.name ? it : { ...it, name: saveName };\n}\n\n/**\n * ADR-0010 §3.3 — Overlay the artifact's metadata-protection envelope\n * onto a returned item so artifact-level lock/packageId/provenance\n * always wins over whatever was persisted in the `sys_metadata` overlay\n * row. Returns `item` unchanged when no artifact baseline is available.\n *\n * The artifact's `_lock`, `_lockReason`, `_packageId`, `_packageVersion`,\n * and `_provenance` are the source of truth — an overlay copy may\n * pre-date the artifact's protection declaration and would otherwise\n * mask it.\n */\nfunction mergeArtifactProtection(item: unknown, artifactItem: unknown): unknown {\n if (item === undefined || item === null) return item;\n if (artifactItem === undefined || artifactItem === null) return item;\n const a = artifactItem as Record<string, unknown>;\n if (typeof a !== 'object') return item;\n const out: Record<string, unknown> = { ...(item as Record<string, unknown>) };\n if (a._lock !== undefined) out._lock = a._lock;\n if (a._lockReason !== undefined) out._lockReason = a._lockReason;\n if (a._lockDocsUrl !== undefined) out._lockDocsUrl = a._lockDocsUrl;\n if (a._lockSource !== undefined) out._lockSource = a._lockSource;\n if (a._packageId !== undefined) out._packageId = a._packageId;\n if (a._packageVersion !== undefined) out._packageVersion = a._packageVersion;\n if (a._provenance !== undefined) out._provenance = a._provenance;\n return out;\n}\n\n/**\n * Simple hash function for ETag generation (browser-compatible)\n * Uses a basic hash algorithm instead of crypto.createHash\n */\nfunction simpleHash(str: string): string {\n let hash = 0;\n for (let i = 0; i < str.length; i++) {\n const char = str.charCodeAt(i);\n hash = ((hash << 5) - hash) + char;\n hash = hash & hash; // Convert to 32bit integer\n }\n return Math.abs(hash).toString(16);\n}\n\n/**\n * Thrown by `updateData` / `deleteData` when the caller supplies an\n * `expectedVersion` that does not match the current record's `updated_at`.\n *\n * The HTTP layer maps this to `409 Conflict` with code `CONCURRENT_UPDATE`,\n * and includes both the current server-side version and the current record\n * payload so the client can render an informed conflict-resolution UI\n * (\"Reload latest\" vs. \"Overwrite anyway\").\n *\n * NOTE: This is an *application-level* compare-and-set — not an atomic\n * storage-layer CAS. There is a small TOCTOU window between the version\n * check and the subsequent write. For the conflict frequency this targets\n * (different users seconds-to-minutes apart in B2B record editing) this\n * is more than adequate; a future revision can push the check into the\n * driver's UPDATE statement (`WHERE id=? AND updated_at=?`) for true\n * atomicity.\n */\nexport class ConcurrentUpdateError extends Error {\n readonly code = 'CONCURRENT_UPDATE';\n readonly status = 409;\n readonly currentVersion: string | null;\n readonly currentRecord: unknown;\n constructor(opts: { currentVersion: string | null; currentRecord: unknown; message?: string }) {\n super(opts.message ?? 'Record was modified by another user');\n this.name = 'ConcurrentUpdateError';\n this.currentVersion = opts.currentVersion;\n this.currentRecord = opts.currentRecord;\n }\n}\n\n/**\n * Normalises a version token for comparison. Strips RFC-7232-style quotes\n * (`\"…\"`) that an HTTP `If-Match` header may carry, trims whitespace, and\n * returns null for empty / nullish input.\n */\nfunction normaliseVersionToken(v: unknown): string | null {\n if (v === null || v === undefined) return null;\n const s = String(v).trim();\n if (!s) return null;\n if (s.length >= 2 && s.startsWith('\"') && s.endsWith('\"')) {\n return s.slice(1, -1);\n }\n return s;\n}\n\n// Lifecycle columns the engine always owns; the clone path drops them by NAME\n// so the insert re-stamps fresh values instead of copying the source's. Mirrors\n// record-validator's SKIP_FIELDS (system-injected, never author-supplied).\nconst CLONE_STRIP_FIELDS: readonly string[] = [\n 'id', 'created_at', 'created_by', 'updated_at', 'updated_by',\n];\n\n/**\n * Service Configuration for Discovery\n * Maps service names to their routes and plugin providers\n */\nconst SERVICE_CONFIG: Record<string, { route: string; plugin: string }> = {\n auth: { route: '/api/v1/auth', plugin: 'plugin-auth' },\n automation: { route: '/api/v1/automation', plugin: 'plugin-automation' },\n cache: { route: '/api/v1/cache', plugin: 'plugin-redis' },\n queue: { route: '/api/v1/queue', plugin: 'plugin-bullmq' },\n job: { route: '/api/v1/jobs', plugin: 'job-scheduler' },\n ui: { route: '/api/v1/ui', plugin: 'ui-plugin' },\n workflow: { route: '/api/v1/workflow', plugin: 'plugin-workflow' },\n realtime: { route: '/api/v1/realtime', plugin: 'plugin-realtime' },\n notification: { route: '/api/v1/notifications', plugin: 'plugin-notifications' },\n ai: { route: '/api/v1/ai', plugin: 'plugin-ai' },\n i18n: { route: '/api/v1/i18n', plugin: 'service-i18n' },\n graphql: { route: '/graphql', plugin: 'plugin-graphql' }, // GraphQL uses /graphql by convention (not versioned REST)\n 'file-storage': { route: '/api/v1/storage', plugin: 'plugin-storage' },\n search: { route: '/api/v1/search', plugin: 'plugin-search' },\n};\n\n/**\n * Phase 3a-references: hand-curated reference path registry.\n *\n * Maps a *target* metadata type to the list of *source* type+path tuples\n * that may point at it. Used by {@link findReferencesToMeta} to scan all\n * loaded metadata and surface \"what depends on this?\" before a user\n * deletes or renames an artifact.\n *\n * Path syntax:\n * - `'foo'` → item.foo\n * - `'foo.bar'` → item.foo.bar\n * - `'foo[]'` → each element of array item.foo\n * - `'foo[].bar'` → bar of each element of array item.foo\n * - `'foo{}'` → each value of Record item.foo\n * - `'foo{}.bar'` → bar of each value of Record item.foo\n *\n * Coverage is intentionally narrow — covers the highest-value references\n * for MVP. Add more entries as new editors are built.\n */\nconst REFERENCE_PATHS: Record<string, Array<{ fromType: string; paths: string[]; kind: string }>> = {\n object: [\n { fromType: 'view', paths: ['object', 'objectName'], kind: 'view' },\n { fromType: 'dashboard', paths: ['widgets[].object', 'widgets[].objectName'], kind: 'dashboard widget' },\n { fromType: 'flow', paths: ['object', 'context.object', 'trigger.object', 'targetObject'], kind: 'flow' },\n { fromType: 'workflow', paths: ['object', 'targetObject'], kind: 'workflow' },\n { fromType: 'permission', paths: ['objects[].name', 'objects[].object'], kind: 'permission' },\n { fromType: 'app', paths: ['navItems[].objectName', 'navItems[].object', 'tabs[].objectName', 'tabs[].object'], kind: 'app nav' },\n { fromType: 'page', paths: ['object', 'objectName'], kind: 'page' },\n { fromType: 'report', paths: ['object', 'objectName'], kind: 'report' },\n { fromType: 'action', paths: ['object', 'objectName'], kind: 'action' },\n { fromType: 'validation', paths: ['object', 'objectName'], kind: 'validation' },\n { fromType: 'hook', paths: ['object', 'objectName'], kind: 'hook' },\n { fromType: 'object', paths: ['fields[].referenceTo', 'fields{}.referenceTo', 'fields{}.reference'], kind: 'field reference' },\n ],\n view: [\n { fromType: 'dashboard', paths: ['widgets[].view', 'widgets[].viewName'], kind: 'dashboard widget' },\n { fromType: 'app', paths: ['navItems[].viewName', 'tabs[].viewName'], kind: 'app nav' },\n { fromType: 'page', paths: ['viewName'], kind: 'page' },\n ],\n tool: [\n { fromType: 'agent', paths: ['tools[]', 'tools[].name'], kind: 'agent tool' },\n ],\n skill: [\n { fromType: 'agent', paths: ['skills[]', 'skills[].name'], kind: 'agent skill' },\n ],\n flow: [\n { fromType: 'app', paths: ['navItems[].flowName', 'tabs[].flowName'], kind: 'app nav' },\n ],\n dashboard: [\n { fromType: 'app', paths: ['navItems[].dashboardName', 'tabs[].dashboardName'], kind: 'app nav' },\n ],\n page: [\n { fromType: 'app', paths: ['navItems[].pageName', 'tabs[].pageName'], kind: 'app nav' },\n ],\n};\n\n/**\n * Extract one or more string values from `item` at `path`. Supports\n * `'a.b'` (nested object access) and `'a[].b'` (array element access).\n * Returns an empty array if any segment is missing.\n */\nfunction extractPathValues(item: unknown, path: string): string[] {\n if (!item || typeof item !== 'object') return [];\n const segments = path.split('.');\n let current: unknown[] = [item];\n for (const rawSeg of segments) {\n let kind: 'value' | 'array' | 'record' = 'value';\n let seg = rawSeg;\n if (seg.endsWith('[]')) {\n kind = 'array';\n seg = seg.slice(0, -2);\n } else if (seg.endsWith('{}')) {\n kind = 'record';\n seg = seg.slice(0, -2);\n }\n const next: unknown[] = [];\n for (const node of current) {\n if (!node || typeof node !== 'object') continue;\n let value: unknown;\n if (seg === '') {\n value = node;\n } else {\n value = (node as Record<string, unknown>)[seg];\n }\n if (value === undefined || value === null) continue;\n if (kind === 'array') {\n if (Array.isArray(value)) {\n for (const v of value) next.push(v);\n }\n } else if (kind === 'record') {\n if (Array.isArray(value)) {\n for (const v of value) next.push(v);\n } else if (typeof value === 'object') {\n for (const v of Object.values(value as Record<string, unknown>)) next.push(v);\n }\n } else {\n next.push(value);\n }\n }\n current = next;\n if (current.length === 0) return [];\n }\n // Coerce final values to strings, dropping non-string non-object leaves.\n const out: string[] = [];\n for (const v of current) {\n if (typeof v === 'string' && v.length > 0) out.push(v);\n else if (v && typeof v === 'object' && 'name' in (v as any) && typeof (v as any).name === 'string') {\n out.push((v as any).name);\n }\n }\n return out;\n}\n\n/**\n * Phase 3a-destructive: detect changes between an existing object schema\n * and an incoming overlay that would break runtime data — removed fields,\n * field type narrowing, required toggled on without a default. Returned\n * issues are surfaced as HTTP 409 `destructive_change` unless the caller\n * sets `force: true`, letting the admin UI render a warning dialog before\n * proceeding.\n *\n * Scope is intentionally narrow for MVP: covers the most common\n * data-loss footguns for `object` and `field` types. Subsequent passes\n * can layer in relationship changes, enum-value removals, etc.\n */\n/**\n * Shallow JSON diff used by `diffMetaItem`. Compares the top-level\n * keys of `from` vs `to`; primitive value changes are reported as\n * `changed`, nested objects/arrays that differ structurally are also\n * reported as a single `changed` entry (deep structural diffs are out\n * of scope — Studio renders the full bodies for a side-by-side view).\n */\nfunction diffShallow(\n from: Record<string, unknown>,\n to: Record<string, unknown>,\n): {\n added: Array<{ path: string; value: unknown }>;\n removed: Array<{ path: string; value: unknown }>;\n changed: Array<{ path: string; from: unknown; to: unknown }>;\n} {\n const added: Array<{ path: string; value: unknown }> = [];\n const removed: Array<{ path: string; value: unknown }> = [];\n const changed: Array<{ path: string; from: unknown; to: unknown }> = [];\n const fromKeys = new Set(Object.keys(from ?? {}));\n const toKeys = new Set(Object.keys(to ?? {}));\n for (const k of toKeys) {\n if (!fromKeys.has(k)) {\n added.push({ path: k, value: (to as any)[k] });\n } else {\n const a = (from as any)[k];\n const b = (to as any)[k];\n const aStr = JSON.stringify(a);\n const bStr = JSON.stringify(b);\n if (aStr !== bStr) {\n changed.push({ path: k, from: a, to: b });\n }\n }\n }\n for (const k of fromKeys) {\n if (!toKeys.has(k)) {\n removed.push({ path: k, value: (from as any)[k] });\n }\n }\n return { added, removed, changed };\n}\n\nfunction detectDestructiveObjectChanges(prev: any, next: any): Array<{\n code: string;\n field?: string;\n message: string;\n}> {\n if (!prev || typeof prev !== 'object' || !next || typeof next !== 'object') return [];\n const prevFields = (prev.fields && typeof prev.fields === 'object') ? prev.fields as Record<string, any> : {};\n const nextFields = (next.fields && typeof next.fields === 'object') ? next.fields as Record<string, any> : {};\n\n const issues: Array<{ code: string; field?: string; message: string }> = [];\n\n // Removed fields — silently dropping a column is a data-loss event.\n for (const fname of Object.keys(prevFields)) {\n // Skip system fields — those are managed by applySystemFields and\n // re-injected on every registerObject call; they will look \"removed\"\n // in any user-supplied overlay.\n if (prevFields[fname]?.system) continue;\n if (!(fname in nextFields)) {\n issues.push({\n code: 'field_removed',\n field: fname,\n message: `Field '${fname}' removed — existing data in this column will become inaccessible.`,\n });\n }\n }\n\n // Field type changes — narrowing or incompatible conversions.\n const TYPE_COMPATIBILITY: Record<string, Set<string>> = {\n text: new Set(['textarea', 'markdown', 'html', 'code']),\n number: new Set([]),\n boolean: new Set([]),\n date: new Set(['datetime']),\n datetime: new Set(['date']),\n };\n for (const fname of Object.keys(nextFields)) {\n const prevField = prevFields[fname];\n const nextField = nextFields[fname];\n if (!prevField) continue; // brand-new field — non-destructive\n const prevType = prevField.type;\n const nextType = nextField.type;\n if (prevType && nextType && prevType !== nextType) {\n const compatible = TYPE_COMPATIBILITY[prevType]?.has(nextType);\n if (!compatible) {\n issues.push({\n code: 'field_type_change',\n field: fname,\n message: `Field '${fname}' type changed from '${prevType}' to '${nextType}' — existing values may not convert cleanly.`,\n });\n }\n }\n // required toggled on without a default — new inserts will start\n // to fail validation, and any null rows already in the table will\n // fail on next save.\n if (!prevField.required && nextField.required && nextField.defaultValue === undefined) {\n issues.push({\n code: 'field_required_no_default',\n field: fname,\n message: `Field '${fname}' is now required but has no default value — existing rows with null values may fail validation.`,\n });\n }\n }\n return issues;\n}\n\nexport class ObjectStackProtocolImplementation implements ObjectStackProtocol {\n private engine: MetadataHostEngine;\n private getServicesRegistry?: () => Map<string, any>;\n private getFeedService?: () => IFeedService | undefined;\n /**\n * Project scope applied to sys_metadata reads/writes. When undefined\n * (single-kernel deployments), rows land in / come from the\n * platform-global bucket (`environment_id IS NULL`). When set, every\n * saveMetaItem insert/update and loadMetaFromDb query is filtered by\n * `environment_id = environmentId`, so per-project kernels see only their own\n * metadata even if several projects share the same physical database.\n */\n private environmentId?: string;\n\n /**\n * Lazily-instantiated SysMetadataRepository per organization. Keyed by\n * `${organizationId ?? '__env__'}`. Repositories are stateful — they\n * carry the per-org `seqCounter` and watch subscribers — so we cache\n * them rather than constructing one per call.\n */\n private overlayRepos = new Map<string, SysMetadataRepository>();\n\n constructor(\n engine: IDataEngine,\n getServicesRegistry?: () => Map<string, any>,\n getFeedService?: () => IFeedService | undefined,\n environmentId?: string,\n ) {\n this.engine = engine as MetadataHostEngine;\n this.getServicesRegistry = getServicesRegistry;\n this.getFeedService = getFeedService;\n this.environmentId = environmentId;\n }\n\n /**\n * Lazily obtain a SysMetadataRepository for the given organization.\n * Env-wide overlays (organizationId == null) share a singleton under\n * the `__env__` key.\n */\n private getOverlayRepo(organizationId: string | null): SysMetadataRepository {\n const key = organizationId ?? '__env__';\n let repo = this.overlayRepos.get(key);\n if (!repo) {\n repo = new SysMetadataRepository({\n engine: this.engine as unknown as SysMetadataEngine,\n organizationId,\n orgLabel: organizationId ?? 'env',\n });\n this.overlayRepos.set(key, repo);\n }\n return repo;\n }\n\n /**\n * One-time guard for ensuring the overlay-uniqueness UNIQUE INDEX exists\n * on `sys_metadata`. ADR-0005: scopes overlays by\n * `(type, name, organization_id, environment_id, scope)` for active rows only.\n * Idempotent SQL — safe to attempt on every protocol instance.\n *\n * Inlined here (rather than importing from @objectstack/metadata/migrations)\n * to avoid a circular dependency: metadata already depends on objectql.\n */\n private overlayIndexEnsured = false;\n private async ensureOverlayIndex(): Promise<void> {\n if (this.overlayIndexEnsured) return;\n this.overlayIndexEnsured = true;\n try {\n const engineAny = this.engine as any;\n let driver: any = engineAny?.driver ?? engineAny?.getDriver?.();\n if (!driver && engineAny?.drivers instanceof Map) {\n for (const candidate of engineAny.drivers.values()) {\n if (\n candidate &&\n (typeof (candidate as any).raw === 'function' ||\n typeof (candidate as any).execute === 'function')\n ) {\n driver = candidate;\n break;\n }\n }\n }\n if (!driver) return;\n const exec = async (sql: string): Promise<void> => {\n if (typeof (driver as any).raw === 'function') {\n await (driver as any).raw(sql);\n } else if (typeof (driver as any).execute === 'function') {\n await (driver as any).execute(sql);\n } else {\n throw new Error('driver has neither raw nor execute');\n }\n };\n // ADR-0005 (revised 2026-05) + ADR-0048: per-env DBs replace the old\n // \"per-project\" isolation, so `environment_id` is no longer a\n // discriminator. Overlay uniqueness is `(type, name,\n // organization_id, COALESCE(package_id,''))` filtered to active\n // rows — `package_id` is in the key so two installed packages\n // shipping the same name each get their own overlay, while\n // `COALESCE(...,'')` keeps the package-less (global) rows unique\n // among themselves (a plain unique index would treat NULLs as\n // distinct and allow duplicate globals). Drop the legacy composite\n // index first so the new partial UNIQUE can claim the same name —\n // DROP INDEX IF EXISTS is idempotent.\n try { await exec(\"DROP INDEX IF EXISTS idx_sys_metadata_overlay_active\"); } catch { /* best-effort */ }\n const partialSql =\n \"CREATE UNIQUE INDEX IF NOT EXISTS idx_sys_metadata_overlay_active \" +\n \"ON sys_metadata (type, name, organization_id, COALESCE(package_id, '')) \" +\n \"WHERE state = 'active'\";\n const fallbackSql =\n \"CREATE INDEX IF NOT EXISTS idx_sys_metadata_overlay_active \" +\n \"ON sys_metadata (type, name, organization_id, package_id)\";\n try {\n await exec(partialSql);\n } catch (err: any) {\n const msg = err instanceof Error ? err.message : String(err);\n if (/partial|where clause|syntax/i.test(msg)) {\n try {\n await exec(fallbackSql);\n } catch {\n // ignore — non-essential optimization\n }\n }\n // \"already exists\" or anything else: best-effort\n }\n // Mirror the same partial-UNIQUE for draft rows so a second\n // simultaneous draft cannot be inserted for the same\n // (type,name,org,package). The unique-active index above already\n // guards published rows; the two never collide because the\n // `state` predicate disambiguates them. DROP first so an existing\n // legacy 3-column draft index is replaced in-place (ADR-0048).\n try { await exec(\"DROP INDEX IF EXISTS idx_sys_metadata_overlay_draft\"); } catch { /* best-effort */ }\n const draftPartialSql =\n \"CREATE UNIQUE INDEX IF NOT EXISTS idx_sys_metadata_overlay_draft \" +\n \"ON sys_metadata (type, name, organization_id, COALESCE(package_id, '')) \" +\n \"WHERE state = 'draft'\";\n try {\n await exec(draftPartialSql);\n } catch (err: any) {\n const msg = err instanceof Error ? err.message : String(err);\n if (/partial|where clause|syntax/i.test(msg)) {\n try {\n await exec(\n \"CREATE INDEX IF NOT EXISTS idx_sys_metadata_overlay_draft \" +\n \"ON sys_metadata (type, name, organization_id, package_id)\",\n );\n } catch {\n // ignore — best effort\n }\n }\n }\n } catch {\n // ignore — index is an optimization, not a correctness invariant\n }\n }\n\n /**\n * Exposes the project scope the protocol is bound to. Consumers like\n * the HTTP dispatcher use this to decide whether to trust the process-\n * wide SchemaRegistry or whether they must route a read through the\n * protocol's environment_id-filtered lookup.\n */\n getProjectId(): string | undefined {\n return this.environmentId;\n }\n\n private requireFeedService(): IFeedService {\n const svc = this.getFeedService?.();\n if (!svc) {\n throw new Error('Feed service not available. Install and register service-feed to enable feed operations.');\n }\n return svc;\n }\n\n async getDiscovery() {\n // Get registered services from kernel if available\n const registeredServices = this.getServicesRegistry ? this.getServicesRegistry() : new Map();\n \n // Build dynamic service info with proper typing\n const services: Record<string, ServiceInfo> = {\n // --- Kernel-provided (objectql is an example kernel implementation) ---\n metadata: { enabled: true, status: 'available' as const, route: '/api/v1/meta', provider: 'objectql' },\n data: { enabled: true, status: 'available' as const, route: '/api/v1/data', provider: 'objectql' },\n analytics: { enabled: true, status: 'available' as const, route: '/api/v1/analytics', provider: 'objectql' },\n };\n\n // Check which services are actually registered\n for (const [serviceName, config] of Object.entries(SERVICE_CONFIG)) {\n if (registeredServices.has(serviceName)) {\n // Service is registered and available\n services[serviceName] = {\n enabled: true,\n status: 'available' as const,\n route: config.route,\n provider: config.plugin,\n };\n } else {\n // Service is not registered\n services[serviceName] = {\n enabled: false,\n status: 'unavailable' as const,\n message: `Install ${config.plugin} to enable`,\n };\n }\n }\n\n // Build routes from services — a flat convenience map for client routing\n const serviceToRouteKey: Record<string, keyof ApiRoutes> = {\n auth: 'auth',\n automation: 'automation',\n ui: 'ui',\n workflow: 'workflow',\n realtime: 'realtime',\n notification: 'notifications',\n ai: 'ai',\n i18n: 'i18n',\n graphql: 'graphql',\n 'file-storage': 'storage',\n };\n\n const optionalRoutes: Partial<ApiRoutes> = {\n analytics: '/api/v1/analytics',\n };\n\n // Add routes for available plugin services\n for (const [serviceName, config] of Object.entries(SERVICE_CONFIG)) {\n if (registeredServices.has(serviceName)) {\n const routeKey = serviceToRouteKey[serviceName];\n if (routeKey) {\n optionalRoutes[routeKey] = config.route;\n }\n }\n }\n\n // Add feed service status\n if (registeredServices.has('feed')) {\n services['feed'] = {\n enabled: true,\n status: 'available' as const,\n route: '/api/v1/data',\n provider: 'service-feed',\n };\n } else {\n services['feed'] = {\n enabled: false,\n status: 'unavailable' as const,\n message: 'Install service-feed to enable',\n };\n }\n\n const routes: ApiRoutes = {\n data: '/api/v1/data',\n metadata: '/api/v1/meta',\n ...optionalRoutes,\n };\n\n // Build well-known capabilities from registered services.\n // DiscoverySchema defines capabilities as Record<string, { enabled, features?, description? }>\n // (hierarchical format). We also keep a flat WellKnownCapabilities for backward compat.\n const wellKnown: WellKnownCapabilities = {\n feed: registeredServices.has('feed'),\n comments: registeredServices.has('feed'),\n automation: registeredServices.has('automation'),\n cron: registeredServices.has('job'),\n search: registeredServices.has('search'),\n export: registeredServices.has('automation') || registeredServices.has('queue'),\n chunkedUpload: registeredServices.has('file-storage'),\n };\n\n // Convert flat booleans → hierarchical capability objects\n const capabilities: Record<string, { enabled: boolean; description?: string }> = {};\n for (const [key, enabled] of Object.entries(wellKnown)) {\n capabilities[key] = { enabled };\n }\n\n return {\n version: '1.0',\n apiName: 'ObjectStack API',\n routes,\n services,\n capabilities,\n };\n }\n\n async getMetaTypes() {\n const schemaTypes = this.engine.registry.getRegisteredTypes();\n\n // Also include types from MetadataService (runtime-registered: agent, tool, etc.)\n let runtimeTypes: string[] = [];\n try {\n const services = this.getServicesRegistry?.();\n const metadataService = services?.get('metadata');\n if (metadataService && typeof metadataService.getRegisteredTypes === 'function') {\n runtimeTypes = await metadataService.getRegisteredTypes();\n }\n } catch {\n // MetadataService not available\n }\n\n const allTypes = Array.from(new Set([...schemaTypes, ...runtimeTypes]));\n\n // Phase 3a-1: enrich response with per-type registry metadata so admin\n // UI can render directory pages, filter by domain, decide which types\n // expose write actions, etc. Existing clients keep working — the\n // `types: string[]` field is preserved alongside the new `entries`.\n //\n // Phase 3a-env-writable: `OS_METADATA_WRITABLE` env var (comma\n // separated singular type names) flips `allowOrgOverride` on listed\n // types so admins can self-serve. The same env var is consulted by\n // `isOverlayAllowed()` at write time — they must stay in sync.\n const writableOverrides = ObjectStackProtocolImplementation.envWritableTypes();\n const registryByType = new Map(\n DEFAULT_METADATA_TYPE_REGISTRY.map((e) => [e.type, e] as const)\n );\n\n const entries = allTypes.map((type) => {\n const singular = (PLURAL_TO_SINGULAR[type] ?? type) as string;\n // Phase 3a-schema: emit a JSON Schema per type so the generic\n // metadata admin UI can render real forms (no more raw-JSON\n // textareas for new resources). The canonical schema for every\n // built-in (and plugin-registered) metadata type lives in the\n // central `getMetadataTypeSchema()` registry; we delegate so\n // Studio's editor and the runtime overlay validator stay in\n // lock-step (one source of truth).\n const zodSchema = getMetadataTypeSchema(singular);\n const schema = (zodSchema ? toJsonSchemaSafe(zodSchema) : undefined)\n ?? HAND_CRAFTED_SCHEMAS[singular];\n const form = TYPE_TO_FORM[singular];\n // Phase 2: the authoritative minimal create seed (single source of\n // truth in @objectstack/spec). Studio/CLI derive create defaults\n // from this via /meta/types instead of re-inventing them.\n const createSeed = getMetadataCreateSeed(singular);\n\n // Type-level actions: merge the registry's declarative actions\n // with any plugin-registered overlay (`registerMetadataTypeActions`).\n // This is the single accessor — a host plugin (e.g. the private\n // datasource-admin backend) contributes its `test_connection`\n // button here, co-located with the route handler it calls, so the\n // button only appears when the backend that serves it is installed.\n const typeActions = getMetadataTypeActions(singular);\n\n const base = registryByType.get(singular as any);\n if (base) {\n const isEnvOverridden = writableOverrides.has(singular);\n return {\n ...base,\n type: singular,\n schemaId: singular, // API client expects schemaId field\n allowOrgOverride: base.allowOrgOverride || isEnvOverridden,\n overrideSource: isEnvOverridden && !base.allowOrgOverride\n ? 'env' as const\n : 'registry' as const,\n schema,\n form,\n ...(createSeed !== undefined ? { createSeed } : {}),\n // Override the spread `base.actions` with the merged view\n // (declarative + plugin-registered). Omit when empty to\n // preserve the prior \"no actions key\" response shape.\n ...(typeActions.length ? { actions: typeActions } : {}),\n };\n }\n // Runtime-registered type with no registry entry — synthesise a\n // minimal descriptor so the UI can still surface it.\n return {\n type: singular,\n schemaId: singular, // API client expects schemaId field\n label: singular,\n description: undefined,\n filePatterns: [],\n supportsOverlay: false,\n allowOrgOverride: writableOverrides.has(singular),\n allowRuntimeCreate: true,\n supportsVersioning: false,\n executionPinned: false,\n loadOrder: 1000,\n domain: 'system' as const,\n overrideSource: writableOverrides.has(singular) ? 'env' as const : 'registry' as const,\n schema,\n form,\n ...(createSeed !== undefined ? { createSeed } : {}),\n // Plugin-registered actions on a type with no registry entry.\n ...(typeActions.length ? { actions: typeActions } : {}),\n };\n }).sort((a, b) => {\n if (a.domain !== b.domain) return a.domain.localeCompare(b.domain);\n return a.type.localeCompare(b.type);\n });\n\n return { types: allTypes, entries };\n }\n\n /**\n * Sweep all (or filtered) metadata types and report entries that\n * fail spec validation. Powers the Studio governance view\n * (`GET /api/v1/meta/diagnostics`) and `os doctor`-style CLI\n * checks.\n *\n * `severity` defaults to `'error'` — only entries with at least\n * one Zod error issue are returned. `'warning'` includes\n * everything we surface (warnings are reserved for a future lint\n * layer on top of spec validation).\n *\n * `type` may be either a singular (`'view'`) or plural (`'views'`)\n * identifier; the underlying `getMetaItems` already normalises.\n *\n * Implementation note: leverages the `_diagnostics` already\n * decorated onto items by `getMetaItems()` to avoid running\n * `safeParse()` twice. For types whose schema is unregistered we\n * skip silently (they cannot be validated and should not appear\n * as \"valid\" either — they are simply opaque to this report).\n */\n async getMetaDiagnostics(request: {\n type?: string;\n severity?: 'error' | 'warning';\n organizationId?: string;\n packageId?: string;\n } = {}): Promise<{\n entries: Array<{ type: string; name: string; diagnostics: MetadataDiagnostics }>;\n total: number;\n scannedTypes: number;\n scannedItems: number;\n /**\n * Per-type aggregate stats — count of items and the list of\n * packages contributing to each type. Computed in the same\n * sweep so the Studio directory page can render tile counts\n * and a package filter in one round-trip.\n */\n stats: Record<string, { count: number; locked: number; packages: string[] }>;\n }> {\n const includeWarnings = request.severity === 'warning';\n const targetTypes = request.type\n ? [request.type]\n : DEFAULT_METADATA_TYPE_REGISTRY\n .filter((e) => getMetadataTypeSchema(e.type))\n .map((e) => e.type);\n\n const entries: Array<{ type: string; name: string; diagnostics: MetadataDiagnostics }> = [];\n const stats: Record<string, { count: number; locked: number; packages: string[] }> = {};\n let scannedItems = 0;\n\n for (const t of targetTypes) {\n let listed: any;\n try {\n listed = await this.getMetaItems({\n type: t,\n organizationId: request.organizationId,\n packageId: request.packageId,\n } as any);\n } catch {\n // Type not listable in this kernel scope — skip.\n continue;\n }\n const items: any[] = Array.isArray(listed?.items)\n ? listed.items\n : Array.isArray(listed)\n ? listed\n : [];\n const pkgSet = new Set<string>();\n let lockedCount = 0;\n for (const item of items) {\n scannedItems += 1;\n const pkg = (item?._packageId ?? null) as string | null;\n if (pkg) pkgSet.add(pkg);\n const lock = item?._lock as string | undefined;\n if (lock && lock !== 'none') lockedCount += 1;\n const diag: MetadataDiagnostics | undefined =\n item?._diagnostics ?? computeMetadataDiagnostics(t, item);\n if (!diag) continue;\n if (diag.valid && !includeWarnings) continue;\n if (diag.valid && includeWarnings && !diag.warnings?.length) continue;\n entries.push({\n type: t,\n name: typeof item?.name === 'string' ? item.name : '<unknown>',\n diagnostics: diag,\n });\n }\n stats[t] = { count: items.length, locked: lockedCount, packages: [...pkgSet].sort() };\n }\n\n return {\n entries,\n total: entries.length,\n scannedTypes: targetTypes.length,\n scannedItems,\n stats,\n };\n }\n\n async getMetaItems(request: { type: string; packageId?: string; organizationId?: string; previewDrafts?: boolean }) {\n const { packageId } = request;\n let items: unknown[] = [];\n\n // Unscoped kernels (control plane): read everything from SchemaRegistry.\n // Scoped (project) kernels: skip user-project entries in SchemaRegistry to\n // prevent cross-project leakage, but DO include scope:'system' packages\n // (plugin-auth, plugin-security, plugin-audit, …) — those are globally\n // shared and must be visible at every project's meta endpoint.\n if (this.environmentId === undefined) {\n items = [...this.engine.registry.listItems(request.type, packageId)];\n // Normalize singular/plural using explicit mapping\n if (items.length === 0) {\n const alt = PLURAL_TO_SINGULAR[request.type] ?? SINGULAR_TO_PLURAL[request.type];\n if (alt) items = [...this.engine.registry.listItems(alt, packageId)];\n }\n } else {\n // For project kernels: the SchemaRegistry is owned by THIS\n // kernel's ObjectQL instance (not shared across projects in the\n // process), so we can safely include every package — system\n // plugins (auth/security/audit) and the project's own app\n // package alike. The `_packageId` tag added by `listItems`\n // (registry.ts) is preserved for the sidebar to compute the\n // correct navigation URL.\n items = [...this.engine.registry.listItems(request.type, packageId)];\n if (items.length === 0) {\n const alt = PLURAL_TO_SINGULAR[request.type] ?? SINGULAR_TO_PLURAL[request.type];\n if (alt) items = [...this.engine.registry.listItems(alt, packageId)];\n }\n }\n\n // Always consult the DB so metadata persisted by the seeder /\n // bulkRegister shows up even when the registry already has unrelated\n // entries (the previous fallback-only logic meant per-env metadata\n // was never surfaced whenever system-bridged items populated the\n // registry). Deduplicate against whatever the registry returned.\n //\n // ADR-0005 (revised 2026-05): isolation is now per-organization, since\n // each env has its own physical DB. We surface both org-scoped overlays\n // (when an active org is provided) and env-wide (organization_id IS NULL)\n // overlays; org-scoped rows win on name collision.\n try {\n const orgId = (request as any).organizationId as string | undefined;\n const queryByOrg = async (oid: string | null): Promise<any[]> => {\n const whereClause: Record<string, unknown> = {\n type: request.type,\n state: 'active',\n organization_id: oid,\n };\n if (packageId) whereClause.package_id = packageId;\n let rs = await this.engine.find('sys_metadata', { where: whereClause });\n if ((!rs || rs.length === 0)) {\n const alt = PLURAL_TO_SINGULAR[request.type] ?? SINGULAR_TO_PLURAL[request.type];\n if (alt) {\n const altWhere: Record<string, unknown> = { type: alt, state: 'active', organization_id: oid };\n if (packageId) altWhere.package_id = packageId;\n rs = await this.engine.find('sys_metadata', { where: altWhere });\n }\n }\n return rs ?? [];\n };\n const envWideRecords = await queryByOrg(null);\n const orgRecords = orgId ? await queryByOrg(orgId) : [];\n // org-specific rows override env-wide rows on name collision\n const mergedMap = new Map<string, any>();\n for (const r of envWideRecords) mergedMap.set(r.name, r);\n for (const r of orgRecords) mergedMap.set(r.name, r);\n const records = Array.from(mergedMap.values());\n if (records && records.length > 0) {\n const byName = new Map<string, any>();\n for (const existing of items) {\n const entry = existing as any;\n if (entry && typeof entry === 'object' && 'name' in entry) {\n byName.set(entry.name, entry);\n }\n }\n for (const record of records) {\n const data = typeof record.metadata === 'string'\n ? JSON.parse(record.metadata)\n : record.metadata;\n if (data && typeof data === 'object' && 'name' in data) {\n // Surface the persisted software-package binding so the\n // sidebar package filter and provenance classification\n // see overlay rows the same way they see registry items.\n const recPkg = (record as { package_id?: string | null }).package_id ?? undefined;\n if (recPkg && (data as any)._packageId === undefined) {\n (data as any)._packageId = recPkg;\n }\n byName.set(data.name, data);\n }\n // Only hydrate the global registry for unscoped calls —\n // scoped project entries must not leak process-wide.\n // Graft the artifact's protection envelope onto the\n // overlay body BEFORE registering: the plain-key entry\n // written here shadows the packaged artifact on\n // `registry.getItem`, and a bare overlay body would\n // strip `_lock`/`_packageId`/`_provenance` from every\n // registry-direct reader (ADR-0010 §3.3 — an overlay\n // must never loosen a packaged lock).\n if (this.environmentId === undefined && data && typeof data === 'object') {\n const artifact = this.lookupArtifactItem(request.type, (data as any).name);\n this.engine.registry.registerItem(\n request.type,\n mergeArtifactProtection(data, artifact),\n 'name' as any,\n );\n }\n }\n items = Array.from(byName.values());\n }\n } catch {\n // DB not available — fall through with whatever we already have.\n }\n\n // ADR-0033 draft-overlay preview: when the caller opts in (admin-gated\n // upstream — see http-dispatcher), overlay `state='draft'` rows on top of\n // the active result so the rendered console can preview pending changes\n // BEFORE publish (instead of only reading them as a JSON diff). Draft rows\n // WIN over active on name collision, and draft-only items (e.g. a brand-new\n // AI-authored object) surface too. Each overlaid item is tagged `_draft:true`\n // so the UI can badge it and show the \"PREVIEW — drafts\" banner. We do NOT\n // hydrate the SchemaRegistry from drafts — drafts must never leak into the\n // process-wide registry or to non-preview reads.\n if (request.previewDrafts) {\n try {\n const orgId = (request as any).organizationId as string | undefined;\n const queryDrafts = async (oid: string | null): Promise<any[]> => {\n const whereClause: Record<string, unknown> = { type: request.type, state: 'draft', organization_id: oid };\n if (packageId) whereClause.package_id = packageId;\n let rs = await this.engine.find('sys_metadata', { where: whereClause });\n if (!rs || rs.length === 0) {\n const alt = PLURAL_TO_SINGULAR[request.type] ?? SINGULAR_TO_PLURAL[request.type];\n if (alt) {\n const altWhere: Record<string, unknown> = { type: alt, state: 'draft', organization_id: oid };\n if (packageId) altWhere.package_id = packageId;\n rs = await this.engine.find('sys_metadata', { where: altWhere });\n }\n }\n return rs ?? [];\n };\n const draftRecords = [...(await queryDrafts(null)), ...(orgId ? await queryDrafts(orgId) : [])];\n if (draftRecords.length > 0) {\n const byName = new Map<string, any>();\n for (const existing of items) {\n const entry = existing as any;\n if (entry && typeof entry === 'object' && 'name' in entry) byName.set(entry.name, entry);\n }\n for (const record of draftRecords) {\n const data = typeof record.metadata === 'string' ? JSON.parse(record.metadata) : record.metadata;\n if (data && typeof data === 'object' && 'name' in data) {\n const recPkg = (record as { package_id?: string | null }).package_id ?? undefined;\n if (recPkg && (data as any)._packageId === undefined) (data as any)._packageId = recPkg;\n (data as any)._draft = true;\n byName.set(data.name, data);\n }\n }\n items = Array.from(byName.values());\n }\n } catch {\n // DB unavailable — serve the active result unchanged.\n }\n }\n\n // Merge with MetadataService (runtime-registered items: agents, tools, etc.)\n try {\n const services = this.getServicesRegistry?.();\n const metadataService = services?.get('metadata');\n if (metadataService && typeof metadataService.list === 'function') {\n let runtimeItems = await metadataService.list(request.type);\n // When filtering by packageId, only include runtime items that\n // belong to the requested package. MetadataService.list() returns\n // items from ALL packages, so we must filter here to respect the\n // package scope requested by the caller (e.g., Studio sidebar).\n if (packageId && runtimeItems && runtimeItems.length > 0) {\n runtimeItems = runtimeItems.filter((item: any) => item?._packageId === packageId);\n }\n if (runtimeItems && runtimeItems.length > 0) {\n // Merge, avoiding duplicates by name\n const itemMap = new Map<string, any>();\n for (const item of items) {\n const entry = item as any;\n if (entry && typeof entry === 'object' && 'name' in entry) {\n itemMap.set(entry.name, entry);\n }\n }\n for (const item of runtimeItems) {\n const entry = item as any;\n if (entry && typeof entry === 'object' && 'name' in entry) {\n // Do not overwrite entries already present in the\n // map: those came from sys_metadata (customization\n // overlays) or the SchemaRegistry and must win\n // over the MetadataService's artifact baseline.\n // Without this guard, saved per-org dashboard /\n // view overlays disappear from list endpoints on\n // refresh (detail endpoint kept showing the\n // overlay because it uses a different code path).\n if (!itemMap.has(entry.name)) {\n itemMap.set(entry.name, entry);\n }\n }\n }\n items = Array.from(itemMap.values());\n }\n }\n } catch {\n // MetadataService not available or doesn't support this type\n }\n\n // Hide metadata owned by a disabled package. `listItems` already drops\n // disabled-package items from the SchemaRegistry, but the DB overlay and\n // MetadataService merges above can re-introduce them (e.g. an app/view\n // persisted in sys_metadata). Re-apply the filter on the final merged\n // set so a disabled package's metadata stops surfacing in the console.\n // Never filter `package` (the Packages page must list disabled packages\n // to re-enable them) nor `object`/`objects` (filtering objects would\n // break data queries that depend on their schema).\n if (\n request.type !== 'package' &&\n request.type !== 'object' &&\n request.type !== 'objects'\n ) {\n items = (items as any[]).filter(\n (it) => !this.engine.registry.isPackageDisabled((it as any)?._packageId),\n );\n }\n\n // Canonical-shape exposure (ADR-0017, \"Object has-many View\"): a\n // `defineView` document is kept in the registry under the bare\n // `<object>` key for defensive single-item reads, but it is NOT a\n // first-class, independently addressable view — the registrar expands\n // it into independent ViewItems (each carrying `viewKind` + `config`).\n // Never surface the aggregated `{ list, form, listViews }` container\n // through enumeration so every list consumer (Studio metadata list,\n // REST `GET /meta/view`, AI schema retriever) sees exactly one\n // canonical entry per named view and never the legacy wrapper shape.\n if (request.type === 'view' || request.type === 'views') {\n items = (items as any[]).filter((it) => !isAggregatedViewContainer(it));\n }\n\n // Merge registered navigation contributions into each served app\n // (ADR-0029 D7). The setup app is a shell of empty group anchors;\n // platform-objects and capability plugins inject their menu entries as\n // contributions, merged lazily on read. REST app endpoints read through\n // this path (not registry.getAllApps), so the merge must happen here too\n // or every contributed group renders empty.\n if (request.type === 'app' || request.type === 'apps') {\n items = (items as any[]).map((app) => this.engine.registry.applyNavContributions(app));\n }\n\n return {\n type: request.type,\n items: decorateMetadataItems(\n request.type,\n (items as any[]).map((it) => {\n // ADR-0048 — scope the artifact lookup to THIS item's owning\n // package so a same-name collision grafts each item's own\n // protection envelope, not the first-registered package's.\n // (`requested` packageId, when the whole list is scoped,\n // takes priority; else the item's own `_packageId`.)\n const a = this.lookupArtifactItem(\n request.type,\n (it as any)?.name,\n packageId ?? ((it as any)?._packageId as string | undefined),\n );\n return mergeArtifactProtection(it, a) as any;\n }),\n ),\n };\n }\n\n async getMetaItem(request: { type: string, name: string, packageId?: string, organizationId?: string, state?: 'active' | 'draft', previewDrafts?: boolean }) {\n let item: unknown;\n const orgId = request.organizationId;\n // Studio's editor opens a draft buffer with `state: 'draft'`;\n // runtime loaders omit it and get the live published row.\n const readState: 'active' | 'draft' = request.state === 'draft' ? 'draft' : 'active';\n\n // ADR-0033 draft-overlay preview (non-strict): when the caller opts in\n // (admin-gated upstream), prefer a `state='draft'` row if one exists, else\n // fall back to the active read below. This differs from the strict\n // `state:'draft'` mode, which 404s (`no_draft`) when no draft exists — the\n // render path must degrade to the published value, not error. The draft\n // item is tagged `_draft:true` so the UI can badge it.\n if (request.previewDrafts && readState !== 'draft') {\n try {\n const findDraft = async (oid: string | null): Promise<any | undefined> => {\n // ADR-0048 prefer-local (parity with the active-read overlay below).\n const lookup = async (t: string): Promise<any | undefined> => {\n const base: Record<string, unknown> = {\n type: t, name: request.name, state: 'draft', organization_id: oid,\n };\n if (request.packageId) {\n const scoped = await this.engine.findOne('sys_metadata', {\n where: { ...base, package_id: request.packageId },\n });\n if (scoped) return scoped;\n // ADR-0048 — global (package-less) draft only, never\n // another package's draft.\n return await this.engine.findOne('sys_metadata', {\n where: { ...base, package_id: null },\n });\n }\n return await this.engine.findOne('sys_metadata', { where: base });\n };\n const rec = await lookup(request.type);\n if (rec) return rec;\n const alt = PLURAL_TO_SINGULAR[request.type] ?? SINGULAR_TO_PLURAL[request.type];\n if (alt) return await lookup(alt);\n return undefined;\n };\n const draftRec = (orgId ? await findDraft(orgId) : undefined) ?? await findDraft(null);\n if (draftRec) {\n const draftItem = typeof draftRec.metadata === 'string'\n ? JSON.parse(draftRec.metadata)\n : draftRec.metadata;\n if (draftItem && typeof draftItem === 'object') {\n const recPkg = (draftRec as { package_id?: string | null }).package_id ?? undefined;\n if (recPkg && (draftItem as any)._packageId === undefined) (draftItem as any)._packageId = recPkg;\n (draftItem as any)._draft = true;\n }\n return { type: request.type, name: request.name, item: decorateMetadataItem(request.type, draftItem) };\n }\n } catch {\n // DB unavailable — fall through to the active read.\n }\n }\n\n // 1. Customization overlay lookup (sys_metadata).\n // Per ADR-0005 (revised), org-scoped row wins; env-wide\n // (organization_id IS NULL) row is the fallback before falling\n // through to the in-memory registry / MetadataService.\n try {\n const findOverlay = async (oid: string | null): Promise<any | undefined> => {\n // ADR-0048 prefer-local: when a package id is supplied and two\n // installed packages ship the same type/name, prefer the row owned\n // by that package before falling back to first-match (package-less\n // query). This mirrors `SchemaRegistry.getItem(type, name, pkg)`.\n const lookup = async (t: string): Promise<any | undefined> => {\n const base: Record<string, unknown> = {\n type: t,\n name: request.name,\n state: readState,\n organization_id: oid,\n };\n if (request.packageId) {\n const scoped = await this.engine.findOne('sys_metadata', {\n where: { ...base, package_id: request.packageId },\n });\n if (scoped) return scoped;\n // ADR-0048 — no package-owned overlay; fall back to the\n // GLOBAL (package-less) overlay only. Must NOT match a\n // different package's row, or a collision would serve\n // package B's customization for a package A read.\n return await this.engine.findOne('sys_metadata', {\n where: { ...base, package_id: null },\n });\n }\n // No package context (legacy/runtime reader) — match any.\n return await this.engine.findOne('sys_metadata', { where: base });\n };\n const rec = await lookup(request.type);\n if (rec) return rec;\n const alt = PLURAL_TO_SINGULAR[request.type] ?? SINGULAR_TO_PLURAL[request.type];\n if (alt) return await lookup(alt);\n return undefined;\n };\n const record = (orgId ? await findOverlay(orgId) : undefined)\n ?? await findOverlay(null);\n if (record) {\n item = typeof record.metadata === 'string'\n ? JSON.parse(record.metadata)\n : record.metadata;\n // Surface the persisted software-package binding (parity with\n // the list path in getMetaItems) so provenance/UI can read it.\n const recPkg = (record as { package_id?: string | null }).package_id ?? undefined;\n if (recPkg && item && typeof item === 'object' && (item as any)._packageId === undefined) {\n (item as any)._packageId = recPkg;\n }\n }\n } catch {\n // DB not available — fall through to registry / MetadataService\n }\n\n // Draft reads stop here — they intentionally do NOT fall through\n // to the runtime registry / MetadataService (which only know\n // about published values). When the draft row is missing we\n // throw `no_draft` (HTTP 404) so the REST contract is identical\n // to `POST /publish` on an empty slot: clients use a single\n // status code to decide \"no pending edit\" without sniffing\n // envelope shape. See ADR-0005 §draft-lifecycle.\n if (readState === 'draft') {\n if (item === undefined) {\n const err: any = new Error(\n `[no_draft] No pending draft exists for ${request.type}/${request.name}.`,\n );\n err.code = 'no_draft';\n err.status = 404;\n throw err;\n }\n return { type: request.type, name: request.name, item: decorateMetadataItem(request.type, item) };\n }\n\n // 2. MetadataService (runtime-registered items: HMR-updated view/page/\n // dashboard/agent/tool, plus FilesystemLoader-sourced items). This\n // is consulted BEFORE the in-memory SchemaRegistry because the\n // registry is a boot-time cache populated by `loadMetadataFromService`\n // and is NOT invalidated on `MetadataManager.register()` (which is\n // how the CLI dev watcher pushes recompiled metadata into the\n // running server). Without this ordering, edits to `*.view.ts`\n // source files appear to take effect (MetadataManager learns the\n // new value) but reads continue to return the stale registry copy.\n if (item === undefined) {\n try {\n const services = this.getServicesRegistry?.();\n const metadataService = services?.get('metadata');\n if (metadataService && typeof metadataService.get === 'function') {\n // Thread the caller's package id (ADR-0048) so a single-item\n // fetch is package-scoped: when two installed packages ship the\n // same type/name, the facade prefers the requester's own item.\n const fromService = await metadataService.get(request.type, request.name, request.packageId);\n if (fromService !== undefined && fromService !== null) {\n item = fromService;\n } else {\n const alt = PLURAL_TO_SINGULAR[request.type] ?? SINGULAR_TO_PLURAL[request.type];\n if (alt) {\n const altFromService = await metadataService.get(alt, request.name, request.packageId);\n if (altFromService !== undefined && altFromService !== null) {\n item = altFromService;\n }\n }\n }\n }\n } catch {\n // MetadataService not available — fall through\n }\n }\n\n // 3. In-memory SchemaRegistry (artifact-loaded out-of-box values, and\n // items that bypass MetadataService — e.g. some object-schema\n // extension chains registered by AppPlugin directly).\n // Both control-plane (unscoped) and project kernels consult the\n // registry. The previous guard that skipped the registry for\n // project kernels was meant to prevent cross-project leakage at\n // the LIST level — but for a single-item lookup the kernel's own\n // `engine.registry` is project-local (each ObjectQL instance has\n // its own SchemaRegistry), so reading from it is safe and\n // necessary. Without this, project-kernel callers of\n // `GET /api/v1/meta/object/<name>` 404 even though the object is\n // registered and visible via the list endpoint.\n if (item === undefined) {\n item = this.engine.registry.getItem(request.type, request.name, request.packageId);\n if (item === undefined) {\n const alt = PLURAL_TO_SINGULAR[request.type] ?? SINGULAR_TO_PLURAL[request.type];\n if (alt) item = this.engine.registry.getItem(alt, request.name, request.packageId);\n }\n }\n\n // Merge registered navigation contributions into a served app\n // (ADR-0029 D7) — parity with the getMetaItems list path so a\n // single-app fetch (GET /meta/app/<name>) also sees the contributed\n // menu entries, not just the empty group-anchor shell.\n if ((request.type === 'app' || request.type === 'apps') && item) {\n item = this.engine.registry.applyNavContributions(item);\n }\n\n // ADR-0010 §3.3 — artifact-level protection (lock/packageId) always\n // wins over any overlay row. The metadata service may return a\n // persisted overlay copy that pre-dates the artifact's `_lock`\n // declaration; we must consult the in-memory artifact registry\n // directly and let its protection envelope override.\n // ADR-0048 — scope the artifact lookup to the requested package so a\n // same-name collision grafts the OWNING package's protection envelope\n // (`_packageId`/`_lock`), not whichever package registered first.\n const artifactItem = this.lookupArtifactItem(request.type, request.name, request.packageId);\n let decorated = decorateMetadataItem(\n request.type,\n mergeArtifactProtection(item, artifactItem),\n );\n // ADR-0047 — list views additionally get reference-integrity\n // diagnostics (userFilters/tabs fields must exist on the source\n // object, kanban groupBy must be select-like). Zod cannot see\n // across documents; merge the cross-document errors into the\n // same `_diagnostics` envelope. Defensive: a failed lookup must\n // never break a read.\n if ((request.type === 'view' || request.type === 'views') && decorated && typeof decorated === 'object') {\n try {\n const viewDoc = decorated as Record<string, any>;\n const sourceObject = viewDoc?.object\n ?? viewDoc?.data?.object\n ?? viewDoc?.objectName\n ?? viewDoc?.list?.data?.object;\n const objectDef = typeof sourceObject === 'string'\n ? this.engine.registry.getObject(sourceObject)\n : undefined;\n if (objectDef) {\n const refs = computeViewReferenceDiagnostics(viewDoc, objectDef as any);\n if (!refs.valid) {\n const prior = viewDoc._diagnostics;\n decorated = {\n ...viewDoc,\n _diagnostics: {\n valid: false,\n errors: [\n ...(prior && prior.valid === false && Array.isArray(prior.errors) ? prior.errors : []),\n ...(refs.errors ?? []),\n ],\n },\n } as typeof decorated;\n }\n }\n } catch { /* reference diagnostics are best-effort */ }\n }\n // ADR-0010 — surface lock/provenance flags so Studio can render\n // the correct affordances without a second round trip.\n const artifactBacked = this.isArtifactBacked(request.type, request.name);\n const lockState = resolveLockState(decorated, artifactBacked);\n return {\n type: request.type,\n name: request.name,\n item: decorated,\n lock: lockState.lock,\n ...(lockState.lockReason !== undefined ? { lockReason: lockState.lockReason } : {}),\n ...(lockState.lockSource !== undefined ? { lockSource: lockState.lockSource } : {}),\n ...(lockState.lockDocsUrl !== undefined ? { lockDocsUrl: lockState.lockDocsUrl } : {}),\n ...(lockState.provenance !== undefined ? { provenance: lockState.provenance } : {}),\n ...(lockState.packageId !== undefined ? { packageId: lockState.packageId } : {}),\n ...(lockState.packageVersion !== undefined ? { packageVersion: lockState.packageVersion } : {}),\n editable: lockState.editable,\n deletable: lockState.deletable,\n resettable: lockState.resettable,\n };\n }\n\n /**\n * Phase 3a-layered-get: return the 3 layers of a metadata item\n * separately — `code` (artifact-loaded baseline), `overlay` (per-org\n * customisation row, if any), and `effective` (what `getMetaItem`\n * would return, i.e. overlay-wins merge).\n *\n * Drives the \"Code default vs Overlay vs Effective\" diff tab in the\n * generic Metadata Resource Edit page. Admins can see exactly what\n * was customised and reset selectively.\n *\n * `code` is null if no artifact baseline exists; `overlay` is null if\n * no sys_metadata row exists for the requested scope; `effective` is\n * never null when either layer exists.\n */\n async getMetaItemLayered(request: {\n type: string;\n name: string;\n packageId?: string;\n organizationId?: string;\n }): Promise<{\n type: string;\n name: string;\n code: unknown | null;\n overlay: unknown | null;\n overlayScope: 'org' | 'env' | null;\n effective: unknown | null;\n /**\n * Load-time validation result for the effective payload — same\n * shape attached to getMetaItems/getMetaItem by\n * decorateMetadataItem. Undefined for types without a registered\n * Zod schema (function/service/router). Lets the Studio edit\n * page surface invalid-metadata banners + inline field errors\n * without a second round-trip.\n */\n _diagnostics?: MetadataDiagnostics;\n // ── ADR-0010 protection envelope ──\n lock: MetadataLock;\n lockReason?: string;\n lockSource?: 'artifact' | 'package' | 'env-forced' | 'overlay';\n lockDocsUrl?: string;\n provenance?: MetadataProvenance;\n packageId?: string;\n packageVersion?: string;\n editable: boolean;\n deletable: boolean;\n resettable: boolean;\n }> {\n const orgId = request.organizationId;\n\n // ── code layer: MetadataService.get + registry, BYPASSING overlay ──\n let code: unknown | null = null;\n try {\n const services = this.getServicesRegistry?.();\n const metadataService = services?.get('metadata');\n if (metadataService && typeof metadataService.get === 'function') {\n // ADR-0048 — package-scope the code layer so a same-name\n // collision resolves to the requested package's artifact.\n let fromService = await metadataService.get(request.type, request.name, request.packageId);\n if (fromService === undefined || fromService === null) {\n const alt = PLURAL_TO_SINGULAR[request.type] ?? SINGULAR_TO_PLURAL[request.type];\n if (alt) fromService = await metadataService.get(alt, request.name, request.packageId);\n }\n if (fromService !== undefined && fromService !== null) code = fromService;\n }\n } catch {\n // ignore\n }\n if (code === null) {\n // Prefer the artifact-only lookup so an overlay row hydrated\n // into the registry's plain key can't masquerade as the \"code\n // default\" layer; fall back to getItem for runtime-only items.\n let regItem = this.lookupArtifactItem(request.type, request.name, request.packageId)\n ?? this.engine.registry.getItem(request.type, request.name, request.packageId);\n if (regItem === undefined) {\n const alt = PLURAL_TO_SINGULAR[request.type] ?? SINGULAR_TO_PLURAL[request.type];\n if (alt) regItem = this.engine.registry.getItem(alt, request.name, request.packageId);\n }\n if (regItem !== undefined) code = regItem;\n }\n\n // ── overlay layer: sys_metadata row (org-scoped wins, then env-wide) ──\n let overlay: unknown | null = null;\n let overlayScope: 'org' | 'env' | null = null;\n try {\n const findOverlay = async (oid: string | null) => {\n // ADR-0048 prefer-local: when a package is supplied, the row\n // owned by that package wins over a package-less first match.\n const lookup = async (t: string) => {\n const base: Record<string, unknown> = {\n type: t, name: request.name, state: 'active', organization_id: oid,\n };\n if (request.packageId) {\n const scoped = await this.engine.findOne('sys_metadata', {\n where: { ...base, package_id: request.packageId },\n });\n if (scoped) return scoped;\n // ADR-0048 — fall back to the GLOBAL (package-less)\n // overlay only, never another package's row.\n return await this.engine.findOne('sys_metadata', {\n where: { ...base, package_id: null },\n });\n }\n return await this.engine.findOne('sys_metadata', { where: base });\n };\n let rec = await lookup(request.type);\n if (!rec) {\n const alt = PLURAL_TO_SINGULAR[request.type] ?? SINGULAR_TO_PLURAL[request.type];\n if (alt) rec = await lookup(alt);\n }\n return rec;\n };\n if (orgId) {\n const rec = await findOverlay(orgId);\n if (rec) {\n overlay = typeof rec.metadata === 'string' ? JSON.parse(rec.metadata) : rec.metadata;\n overlayScope = 'org';\n }\n }\n if (overlay === null) {\n const rec = await findOverlay(null);\n if (rec) {\n overlay = typeof rec.metadata === 'string' ? JSON.parse(rec.metadata) : rec.metadata;\n overlayScope = 'env';\n }\n }\n } catch {\n // DB unavailable — overlay stays null\n }\n\n const effective: unknown | null = overlay ?? code;\n\n const _diagnostics =\n effective !== null && effective !== undefined\n ? computeMetadataDiagnostics(request.type, effective)\n : undefined;\n\n // ADR-0010 — surface lock/provenance flags so the Studio editor\n // can render the correct affordances without a second round trip.\n const artifactBacked = this.isArtifactBacked(request.type, request.name);\n // Lock resolution: artifact wins over overlay, matching getEffectiveLock.\n const lockSource: any = code ?? overlay ?? {};\n const lockState = resolveLockState(lockSource, artifactBacked);\n\n return {\n type: request.type,\n name: request.name,\n code,\n overlay,\n overlayScope,\n effective,\n ...(_diagnostics ? { _diagnostics } : {}),\n lock: lockState.lock,\n ...(lockState.lockReason !== undefined ? { lockReason: lockState.lockReason } : {}),\n ...(lockState.lockSource !== undefined ? { lockSource: lockState.lockSource } : {}),\n ...(lockState.lockDocsUrl !== undefined ? { lockDocsUrl: lockState.lockDocsUrl } : {}),\n ...(lockState.provenance !== undefined ? { provenance: lockState.provenance } : {}),\n ...(lockState.packageId !== undefined ? { packageId: lockState.packageId } : {}),\n ...(lockState.packageVersion !== undefined ? { packageVersion: lockState.packageVersion } : {}),\n editable: lockState.editable,\n deletable: lockState.deletable,\n resettable: lockState.resettable,\n };\n }\n\n /**\n * ADR-0010 §3.6 / Phase 4.1 — read the metadata-protection audit log\n * for a single item. Returns the most-recent rows of\n * `sys_metadata_audit` for this (type, name) tuple, sorted newest\n * first. Refused (`denied`) and forced (`forced`) writes both appear\n * here — they never reach the `history` endpoint, which only tracks\n * successful body snapshots.\n *\n * The table is provisioned by `platform-objects` and is the\n * compliance surface for the lock-enforcement story. When the\n * environment has not yet provisioned the table (legacy install\n * prior to ADR-0010) the call returns `{ events: [] }` instead of\n * raising, keeping the Studio tab harmless.\n */\n async auditMetaItem(request: {\n type: string;\n name: string;\n organizationId?: string | null;\n limit?: number;\n }): Promise<{\n events: Array<{\n id: unknown;\n occurredAt: string;\n actor: string;\n source: string | null;\n operation: 'save' | 'publish' | 'rollback' | 'delete' | 'reset';\n outcome: 'allowed' | 'denied' | 'forced';\n code: string;\n lockState: MetadataLock | null;\n lockOverridden: boolean;\n requestId: string | null;\n note: string | null;\n }>;\n }> {\n const singular = PLURAL_TO_SINGULAR[request.type] ?? request.type;\n const limit = Math.min(\n Math.max(1, request.limit ?? 100),\n 500,\n );\n try {\n // Org-scoped lookup: include rows for the specific org AND\n // env-wide (organization_id IS NULL) rows so the editor\n // sees both tenant overlays and env-level package writes.\n const where: Record<string, unknown> = {\n type: singular,\n name: request.name,\n };\n const rows = await this.engine.find('sys_metadata_audit', {\n where,\n orderBy: [{ field: 'occurred_at', direction: 'desc' }],\n limit,\n } as any);\n const events = (Array.isArray(rows) ? rows : []).map((r: any) => ({\n id: r.id,\n occurredAt:\n typeof r.occurred_at === 'string'\n ? r.occurred_at\n : r.occurred_at instanceof Date\n ? r.occurred_at.toISOString()\n : String(r.occurred_at ?? ''),\n actor: String(r.actor ?? 'system'),\n source: r.source ?? null,\n operation: r.operation,\n outcome: r.outcome,\n code: String(r.code ?? ''),\n lockState: (r.lock_state ?? null) as MetadataLock | null,\n lockOverridden: Boolean(r.lock_overridden),\n requestId: r.request_id ?? null,\n note: r.note ?? null,\n }));\n return { events };\n } catch (err: any) {\n // Table not provisioned (legacy env) or driver doesn't\n // expose `find` — return empty rather than 500ing the tab.\n console.warn(\n `[Protocol] auditMetaItem read failed for ${request.type}/${request.name}: ${err?.message ?? err}`,\n );\n return { events: [] };\n }\n }\n\n async getUiView(request: { object: string, type: 'list' | 'form' }) {\n const schema = this.engine.registry.getObject(request.object);\n if (!schema) throw new Error(`Object ${request.object} not found`);\n\n const fields = schema.fields || {};\n const fieldKeys = Object.keys(fields);\n\n if (request.type === 'list') {\n // Intelligent Column Selection\n // 1. Always include 'name' or name-like fields\n // 2. Limit to 6 columns by default\n const priorityFields = ['name', 'title', 'label', 'subject', 'email', 'status', 'type', 'category', 'created_at'];\n \n let columns = fieldKeys.filter(k => priorityFields.includes(k));\n \n // If few priority fields, add others until 5\n if (columns.length < 5) {\n const remaining = fieldKeys.filter(k => !columns.includes(k) && k !== 'id' && !fields[k].hidden);\n columns = [...columns, ...remaining.slice(0, 5 - columns.length)];\n }\n \n // Sort columns by priority then alphabet or schema order\n // For now, just keep them roughly in order they appear in schema or priority list\n \n return {\n list: {\n type: 'grid' as const,\n object: request.object,\n label: schema.label || schema.name,\n columns: columns.map(f => ({\n field: f,\n label: fields[f]?.label || f,\n sortable: true\n })),\n sort: fields['created_at'] ? ([{ field: 'created_at', order: 'desc' }] as any) : undefined,\n searchableFields: columns.slice(0, 3) // Make first few textual columns searchable\n }\n };\n } else {\n // Form View Generation\n // Simple single-section layout for now\n const formFields = fieldKeys\n .filter(k => k !== 'id' && k !== 'created_at' && k !== 'updated_at' && !fields[k].hidden)\n .map(f => ({\n field: f,\n label: fields[f]?.label,\n required: fields[f]?.required,\n readonly: fields[f]?.readonly,\n type: fields[f]?.type,\n // Default to 2 columns for most, 1 for textareas\n colSpan: (fields[f]?.type === 'textarea' || fields[f]?.type === 'html') ? 2 : 1\n }));\n\n return {\n form: {\n type: 'simple' as const,\n object: request.object,\n label: `Edit ${schema.label || schema.name}`,\n sections: [\n {\n label: 'General Information',\n columns: 2 as const,\n collapsible: false,\n collapsed: false,\n fields: formFields\n }\n ]\n }\n };\n }\n }\n\n async findData(request: { object: string, query?: any, context?: any }) {\n const options: any = { ...request.query };\n // Forward the dispatcher's ExecutionContext so RBAC/RLS middleware\n // can apply per-request enforcement. The protocol layer is purely\n // a normalizer — it must never strip security context.\n if (request.context !== undefined) {\n options.context = request.context;\n }\n\n // ====================================================================\n // Normalize legacy params → QueryAST standard (where/fields/orderBy/offset/expand)\n // ====================================================================\n\n // OData-style `$`-prefixed params → bare aliases that the rest of\n // this function knows how to normalize. Without this step, params\n // like `?$top=2&$orderby=...` survive into the catch-all\n // implicit-filter pass below and get merged into `where` as\n // bogus field-equality predicates (e.g. `where.$top = \"2\"`),\n // which silently returns zero rows for every list endpoint.\n for (const [dollar, bare] of [\n ['$top', 'top'],\n ['$skip', 'skip'],\n ['$orderby', 'orderBy'],\n ['$select', 'select'],\n ['$count', 'count'],\n ['$search', 'search'],\n ['$searchFields', 'searchFields'],\n ] as const) {\n if (options[dollar] != null && options[bare] == null) {\n options[bare] = options[dollar];\n }\n delete options[dollar];\n }\n\n // Numeric fields — normalize top → limit, skip → offset\n if (options.top != null) {\n options.limit = Number(options.top);\n delete options.top;\n }\n if (options.skip != null) {\n options.offset = Number(options.skip);\n delete options.skip;\n }\n if (options.limit != null) options.limit = Number(options.limit);\n if (options.offset != null) options.offset = Number(options.offset);\n\n // Select → fields: comma-separated string → array\n if (typeof options.select === 'string') {\n options.fields = options.select.split(',').map((s: string) => s.trim()).filter(Boolean);\n } else if (Array.isArray(options.select)) {\n options.fields = options.select;\n }\n if (options.select !== undefined) delete options.select;\n\n // Sort/orderBy → orderBy: string → SortNode[] array\n const sortValue = options.orderBy ?? options.sort;\n if (typeof sortValue === 'string') {\n const parsed = sortValue.split(',').map((part: string) => {\n const trimmed = part.trim();\n if (trimmed.startsWith('-')) {\n return { field: trimmed.slice(1), order: 'desc' as const };\n }\n const [field, order] = trimmed.split(/\\s+/);\n return { field, order: (order?.toLowerCase() === 'desc' ? 'desc' : 'asc') as 'asc' | 'desc' };\n }).filter((s: any) => s.field);\n options.orderBy = parsed;\n } else if (Array.isArray(sortValue)) {\n options.orderBy = sortValue;\n }\n delete options.sort;\n\n // Filter/filters/$filter → where: normalize all filter aliases\n const filterValue = options.filter ?? options.filters ?? options.$filter ?? options.where;\n delete options.filter;\n delete options.filters;\n delete options.$filter;\n\n if (filterValue !== undefined) {\n let parsedFilter = filterValue;\n // JSON string → object\n if (typeof parsedFilter === 'string') {\n try { parsedFilter = JSON.parse(parsedFilter); } catch { /* keep as-is */ }\n }\n // Filter AST array → FilterCondition object\n if (isFilterAST(parsedFilter)) {\n parsedFilter = parseFilterAST(parsedFilter);\n }\n options.where = parsedFilter;\n }\n\n // Populate/expand/$expand → expand (Record<string, QueryAST>)\n const populateValue = options.populate;\n const expandValue = options.$expand ?? options.expand;\n const expandNames: string[] = [];\n if (typeof populateValue === 'string') {\n expandNames.push(...populateValue.split(',').map((s: string) => s.trim()).filter(Boolean));\n } else if (Array.isArray(populateValue)) {\n expandNames.push(...populateValue);\n }\n if (!expandNames.length && expandValue) {\n if (typeof expandValue === 'string') {\n expandNames.push(...expandValue.split(',').map((s: string) => s.trim()).filter(Boolean));\n } else if (Array.isArray(expandValue)) {\n expandNames.push(...expandValue);\n }\n }\n delete options.populate;\n delete options.$expand;\n // Clean up non-object expand (e.g. string) BEFORE the Record conversion\n // below, so that populate-derived names can create the expand Record even\n // when a legacy string expand was also present.\n if (typeof options.expand !== 'object' || options.expand === null) {\n delete options.expand;\n }\n // Only set expand if not already an object (advanced usage)\n if (expandNames.length > 0 && !options.expand) {\n options.expand = {} as Record<string, any>;\n for (const rel of expandNames) {\n options.expand[rel] = { object: rel };\n }\n }\n\n // Boolean fields\n for (const key of ['distinct', 'count']) {\n if (options[key] === 'true') options[key] = true;\n else if (options[key] === 'false') options[key] = false;\n }\n \n // Flat field filters: REST-style query params like ?id=abc&status=open\n // After extracting all known query parameters, any remaining keys are\n // treated as implicit field-level equality filters merged into `where`.\n const knownParams = new Set([\n 'top', 'limit', 'offset',\n 'orderBy',\n 'fields',\n 'where',\n 'expand',\n 'distinct', 'count',\n 'aggregations', 'groupBy',\n 'search', 'searchFields', 'context', 'cursor',\n ]);\n if (!options.where) {\n const implicitFilters: Record<string, unknown> = {};\n for (const key of Object.keys(options)) {\n if (!knownParams.has(key)) {\n implicitFilters[key] = options[key];\n delete options[key];\n }\n }\n if (Object.keys(implicitFilters).length > 0) {\n options.where = implicitFilters;\n }\n }\n \n // Route to engine.aggregate() when the query has GROUP BY / aggregations.\n // engine.find() does not do in-memory aggregation fallback, so without\n // this branch a spec-shape aggregate request would silently return\n // ungrouped raw rows on drivers (e.g. SqlDriver) that don't natively\n // honor groupBy/aggregations in find().\n const hasGroupBy = Array.isArray(options.groupBy) && options.groupBy.length > 0;\n const hasAggregations = Array.isArray(options.aggregations) && options.aggregations.length > 0;\n if (hasGroupBy || hasAggregations) {\n const records = await this.engine.aggregate(request.object, {\n where: options.where,\n groupBy: options.groupBy,\n aggregations: options.aggregations,\n context: options.context,\n } as any);\n // Apply limit client-side (EngineAggregateOptions doesn't carry limit).\n // `records` is the full grouped set, so its length IS the real total\n // and `hasMore` follows from whether the slice dropped any groups.\n const limited = typeof options.limit === 'number' && options.limit > 0\n ? records.slice(0, options.limit)\n : records;\n return {\n object: request.object,\n records: limited,\n total: records.length,\n hasMore: limited.length < records.length,\n };\n }\n\n const records = await this.engine.find(request.object, options);\n // Pagination metadata. When a `limit` is present the response is a single\n // page, so `records.length` is the page size — NOT the match total. Run a\n // count over the same `where` so the client can render total pages and know\n // whether more pages remain (true server-side pagination). Without a limit\n // the full result set is returned, so its length already IS the total.\n //\n // engine.count() only honors `where`; a `search`/`distinct` query can't be\n // reproduced by it, so for those we skip the count and fall back to a\n // page-local estimate (a full page implies there may be more) rather than\n // reporting a wrong total.\n const pageLimit = typeof options.limit === 'number' && options.limit > 0 ? options.limit : undefined;\n const pageOffset = typeof options.offset === 'number' && options.offset > 0 ? options.offset : 0;\n let total = records.length;\n let hasMore = false;\n if (pageLimit !== undefined) {\n const countable = options.search == null && options.distinct == null;\n if (countable) {\n try {\n total = await this.engine.count(request.object, {\n where: options.where,\n context: options.context,\n } as any);\n } catch {\n // engine.count() has its own find().length fallback; if it still\n // throws, degrade to a page-local total rather than failing the list.\n total = pageOffset + records.length;\n }\n hasMore = pageOffset + records.length < total;\n } else {\n hasMore = records.length === pageLimit;\n total = pageOffset + records.length + (hasMore ? 1 : 0);\n }\n }\n return {\n object: request.object,\n records,\n total,\n hasMore,\n };\n }\n\n async getData(request: { object: string, id: string, expand?: string | string[], select?: string | string[], context?: any }) {\n const queryOptions: any = {\n where: { id: request.id }\n };\n if (request.context !== undefined) {\n queryOptions.context = request.context;\n }\n\n // Support fields for single-record retrieval\n if (request.select) {\n queryOptions.fields = typeof request.select === 'string'\n ? request.select.split(',').map((s: string) => s.trim()).filter(Boolean)\n : request.select;\n }\n\n // Support expand for single-record retrieval\n if (request.expand) {\n const expandNames = typeof request.expand === 'string'\n ? request.expand.split(',').map((s: string) => s.trim()).filter(Boolean)\n : request.expand;\n queryOptions.expand = {} as Record<string, any>;\n for (const rel of expandNames) {\n queryOptions.expand[rel] = { object: rel };\n }\n }\n\n const result = await this.engine.findOne(request.object, queryOptions);\n if (result) {\n return {\n object: request.object,\n id: request.id,\n record: result\n };\n }\n const err = new Error(`Record ${request.id} not found in ${request.object}`) as Error & {\n code?: string;\n status?: number;\n object?: string;\n };\n err.code = 'RECORD_NOT_FOUND';\n err.status = 404;\n err.object = request.object;\n throw err;\n }\n\n async createData(request: { object: string, data: any, context?: any }) {\n const result = await this.engine.insert(\n request.object,\n request.data,\n request.context !== undefined ? { context: request.context } as any : undefined,\n );\n return {\n object: request.object,\n id: result.id,\n record: result\n };\n }\n\n /**\n * Clone a record — read the source, drop engine-owned columns, and\n * insert a fresh copy. Gated by the object's `enable.clone` capability\n * (default `true`; only an explicit `enable.clone === false` disables it).\n *\n * Shallow by design: it duplicates the record's own scalar/business field\n * values, not its related child records. The insert path re-stamps audit\n * columns, regenerates `autonumber` fields, and recomputes derived\n * (`formula`/`summary`) fields, so the copy is a valid new row rather than\n * a byte-identical twin. Caller-supplied `overrides` are applied last and\n * win over the copied values — the natural place to set a new `name`,\n * clear a unique field, or reset status before insert.\n */\n async cloneData(request: { object: string, id: string, overrides?: Record<string, any>, context?: any }) {\n const schema: any = this.engine.registry.getObject(request.object);\n if (!schema) {\n const err: any = new Error(`Object '${request.object}' not found`);\n err.code = 'OBJECT_NOT_FOUND';\n err.status = 404;\n err.object = request.object;\n throw err;\n }\n // `enable.clone` defaults to true in the spec; treat an absent block /\n // absent flag as enabled and only block on an explicit `false`.\n if (schema.enable?.clone === false) {\n const err: any = new Error(`Cloning is disabled for object '${request.object}'`);\n err.code = 'CLONE_DISABLED';\n err.status = 403;\n err.object = request.object;\n throw err;\n }\n\n const ctx = request.context;\n const ctxOpt = ctx !== undefined ? { context: ctx } : undefined;\n\n const source = await this.engine.findOne(\n request.object,\n { where: { id: request.id }, ...(ctxOpt as any) } as any,\n );\n if (!source) {\n const err: any = new Error(`Record ${request.id} not found in ${request.object}`);\n err.code = 'RECORD_NOT_FOUND';\n err.status = 404;\n err.object = request.object;\n throw err;\n }\n\n // Copy the source, then strip the columns the engine owns so the insert\n // path re-derives them rather than carrying the source's values over.\n const data: Record<string, any> = { ...source };\n for (const f of CLONE_STRIP_FIELDS) delete data[f];\n const fields: Record<string, any> = schema.fields || {};\n for (const [name, def] of Object.entries(fields)) {\n if (!def) continue;\n // Engine-/automation-owned values: injected system/audit columns,\n // engine-generated autonumbers, and computed formula/summary fields.\n if ((def as any).system === true\n || (def as any).type === 'autonumber'\n || (def as any).type === 'formula'\n || (def as any).type === 'summary') {\n delete data[name];\n }\n }\n // Caller overrides win (new name, cleared unique field, reset status…).\n if (request.overrides && typeof request.overrides === 'object') {\n Object.assign(data, request.overrides);\n }\n\n const result = await this.engine.insert(request.object, data, ctxOpt as any);\n return {\n object: request.object,\n id: result.id,\n sourceId: request.id,\n record: result,\n };\n }\n\n async updateData(request: { object: string, id: string, data: any, expectedVersion?: string, context?: any }) {\n await this.assertVersionMatch(request.object, request.id, request.expectedVersion, request.context);\n const opts: any = { where: { id: request.id } };\n if (request.context !== undefined) opts.context = request.context;\n const result = await this.engine.update(request.object, request.data, opts);\n return {\n object: request.object,\n id: request.id,\n record: result\n };\n }\n\n async deleteData(request: { object: string, id: string, expectedVersion?: string, context?: any }) {\n await this.assertVersionMatch(request.object, request.id, request.expectedVersion, request.context);\n const opts: any = { where: { id: request.id } };\n if (request.context !== undefined) opts.context = request.context;\n await this.engine.delete(request.object, opts);\n return {\n object: request.object,\n id: request.id,\n success: true\n };\n }\n\n /**\n * Optimistic Concurrency Control gate shared by updateData/deleteData.\n *\n * When the caller passes a non-empty `expectedVersion` token (typically\n * the `updated_at` value they read), this fetches the current record\n * and compares its `updated_at` against the token. Mismatch → throw\n * `ConcurrentUpdateError` which the REST layer maps to 409.\n *\n * Behaviour:\n * - Empty/missing token → no check (opt-in semantics; existing callers\n * that haven't yet adopted OCC are unaffected).\n * - Record not found → no check; downstream `engine.update` will\n * surface the usual `RECORD_NOT_FOUND` 404. We intentionally do not\n * treat \"missing record\" as a concurrency conflict.\n * - Record has no `updated_at` field (timestamps disabled) → no check.\n * Logging would be noisy here; OCC is opt-in and the absence of a\n * version column is an explicit \"this object doesn't support OCC\"\n * signal.\n */\n private async assertVersionMatch(\n object: string,\n id: string,\n expectedVersion: string | undefined,\n context: any\n ): Promise<void> {\n const expected = normaliseVersionToken(expectedVersion);\n if (!expected) return;\n const findOpts: any = { where: { id } };\n if (context !== undefined) findOpts.context = context;\n const current = await this.engine.findOne(object, findOpts);\n if (!current) return;\n const currentVersion = normaliseVersionToken((current as any).updated_at);\n if (!currentVersion) return;\n if (currentVersion !== expected) {\n throw new ConcurrentUpdateError({\n currentVersion,\n currentRecord: current,\n message: `Record ${object}/${id} was modified by another user (current version ${currentVersion}, expected ${expected})`,\n });\n }\n }\n\n // ==========================================\n // Global Search (M10.5)\n // ==========================================\n /**\n * Cross-object substring search across all registered objects that opt in\n * via `enable.searchable !== false` and `enable.apiEnabled !== false`.\n * Searches text-like fields (text/textarea/email/url/phone/markdown/html/string)\n * whose `searchable: true` flag is set, falling back to the object's\n * `displayNameField` (or `name`) when no fields are explicitly searchable.\n *\n * The query is split into whitespace-separated terms; each term must match\n * (case-insensitive LIKE) at least one searchable field. RBAC/RLS is\n * enforced by forwarding the caller's `context` to `engine.find` so users\n * only see records they are entitled to read.\n */\n async searchAll(request: {\n q: string;\n objects?: string[];\n limit?: number;\n perObject?: number;\n context?: any;\n }): Promise<{\n query: string;\n hits: Array<{\n object: string;\n id: string;\n title: string;\n snippet?: string;\n record: any;\n }>;\n totalObjects: number;\n totalHits: number;\n truncated: boolean;\n }> {\n const q = (request.q ?? '').trim();\n if (!q) {\n return { query: '', hits: [], totalObjects: 0, totalHits: 0, truncated: false };\n }\n\n const overallLimit = Math.max(1, Math.min(100, Number(request.limit ?? 20)));\n const perObject = Math.max(1, Math.min(25, Number(request.perObject ?? 5)));\n const objectsFilter = request.objects && request.objects.length\n ? new Set(request.objects)\n : null;\n\n // Tokenise: each token must match (LIKE %term%) at least one searchable field\n const terms = q.split(/\\s+/).filter(Boolean).slice(0, 8);\n\n const allObjects = (this.engine as any).registry?.getAllObjects?.() ?? [];\n const hits: Array<{ object: string; id: string; title: string; snippet?: string; record: any }> = [];\n let objectsScanned = 0;\n\n for (const obj of allObjects) {\n if (hits.length >= overallLimit) break;\n if (!obj?.name) continue;\n if (objectsFilter && !objectsFilter.has(obj.name)) continue;\n\n // Skip platform/system tables and opt-outs\n const enable = obj.enable ?? {};\n if (enable.searchable === false) continue;\n if (enable.apiEnabled === false) continue;\n // Skip noisy system tables by name prefix\n if (obj.name.startsWith('sys_audit_log')\n || obj.name.startsWith('sys_activity')\n || obj.name.startsWith('sys_session')\n || obj.name.startsWith('sys_presence')\n || obj.name.startsWith('sys_metadata')\n || obj.name.startsWith('sys_account')) {\n continue;\n }\n\n const fieldsRaw = obj.fields;\n const fields: Array<{ name: string; type: string; searchable?: boolean }> =\n Array.isArray(fieldsRaw)\n ? fieldsRaw\n : (fieldsRaw && typeof fieldsRaw === 'object'\n ? Object.entries(fieldsRaw).map(([name, f]: [string, any]) => ({ name, ...(f || {}) }))\n : []);\n const TEXT_TYPES = new Set(['text', 'textarea', 'string', 'email', 'url', 'phone', 'markdown', 'html']);\n const fieldByName = new Map(fields.map(f => [f.name, f]));\n const hasField = (n: string) => fieldByName.has(n);\n // Resolve title for a record using titleFormat → displayNameField →\n // common conventional fields → id. titleFormat supports simple\n // `{field}` placeholders (the `template` dialect); unresolved\n // placeholders fall through to the next strategy.\n const titleFormatSource = (obj.titleFormat && (obj.titleFormat.source || obj.titleFormat))\n || undefined;\n const renderTitle = (row: any): string => {\n if (typeof titleFormatSource === 'string') {\n let allResolved = true;\n const rendered = titleFormatSource.replace(/\\{\\{?\\s*([a-zA-Z0-9_.]+)\\s*\\}?\\}/g, (_m, key) => {\n const v = row[key];\n if (v == null || v === '') { allResolved = false; return ''; }\n return String(v);\n }).trim();\n if (rendered && allResolved) return rendered;\n if (rendered) return rendered.replace(/\\s+-\\s+$/, '').replace(/^\\s+-\\s+/, '').trim() || row.id;\n }\n const candidates = [\n obj.displayNameField,\n 'name', 'full_name', 'title', 'subject', 'label', 'company',\n ].filter((c): c is string => typeof c === 'string' && hasField(c));\n for (const c of candidates) {\n const v = row[c];\n if (v != null && String(v).trim()) return String(v);\n }\n const fn = row.first_name, ln = row.last_name;\n if (fn || ln) return `${fn ?? ''} ${ln ?? ''}`.trim();\n return String(row.id);\n };\n\n const titleFieldName = obj.displayNameField\n || (hasField('name') ? 'name' : undefined)\n || (hasField('title') ? 'title' : undefined)\n || fields.find(f => TEXT_TYPES.has(f.type))?.name;\n\n let searchableFields = fields\n .filter(f => f && TEXT_TYPES.has(f.type) && f.searchable === true)\n .map(f => f.name as string);\n\n // Fallback: if no field is explicitly searchable, scan the title field\n if (searchableFields.length === 0 && titleFieldName) {\n searchableFields = [titleFieldName];\n }\n if (searchableFields.length === 0) continue;\n\n objectsScanned++;\n\n // Build AND-of-OR filter: every term must hit at least one field.\n // ObjectQL exposes case-insensitive substring matching via `$contains`.\n const andClauses = terms.map(term => ({\n $or: searchableFields.map(f => ({ [f]: { $contains: term } })),\n }));\n const where = andClauses.length === 1 ? andClauses[0] : { $and: andClauses };\n\n try {\n const opts: any = {\n where,\n limit: perObject,\n orderBy: [{ field: 'updated_at', direction: 'desc' }],\n };\n if (request.context !== undefined) opts.context = request.context;\n\n const rows = await this.engine.find(obj.name, opts);\n for (const row of rows || []) {\n if (hits.length >= overallLimit) break;\n const title = renderTitle(row);\n // Build snippet from first searchable field that contains a term\n let snippet: string | undefined;\n for (const f of searchableFields) {\n const v = row[f];\n if (typeof v === 'string' && v) {\n const lc = v.toLowerCase();\n const idx = terms.map(t => lc.indexOf(t.toLowerCase())).find(i => i >= 0);\n if (idx != null && idx >= 0) {\n const start = Math.max(0, idx - 30);\n const end = Math.min(v.length, idx + 90);\n snippet = (start > 0 ? '…' : '') + v.slice(start, end) + (end < v.length ? '…' : '');\n break;\n }\n }\n }\n hits.push({\n object: obj.name,\n id: row.id,\n title,\n snippet,\n record: row,\n });\n }\n } catch {\n // RBAC denial or driver hiccup — skip silently per object\n continue;\n }\n }\n\n return {\n query: q,\n hits,\n totalObjects: objectsScanned,\n totalHits: hits.length,\n truncated: hits.length >= overallLimit,\n };\n }\n\n // ==========================================\n // Metadata Caching\n // ==========================================\n\n async getMetaItemCached(request: { type: string, name: string, cacheRequest?: MetadataCacheRequest, locale?: string }): Promise<MetadataCacheResponse> {\n try {\n // Delegate to getMetaItem so the customization-overlay read order\n // (sys_metadata → registry → MetadataService) is honoured here too\n // (ADR-0005). Without this, cached reads silently bypass overlays.\n const result = await this.getMetaItem({ type: request.type, name: request.name });\n const item = (result as any)?.item;\n\n if (!item) {\n throw new Error(`Metadata item ${request.type}/${request.name} not found`);\n }\n\n // Calculate ETag (simple hash of the stringified metadata).\n //\n // The ETag MUST vary by locale. The REST layer translates the\n // response body *after* this validator check, so an ETag computed\n // only from the (untranslated) content would let a language switch\n // match the prior `If-None-Match` and return `304 Not Modified`\n // carrying a stale-locale body — labels/headers stuck in the old\n // language until a hard refresh (issue #1319). Folding the resolved\n // locale into the hash gives each locale a distinct validator.\n const content = JSON.stringify(item);\n const hash = simpleHash(request.locale ? `${request.locale}\u0000${content}` : content);\n const etag = { value: hash, weak: false };\n\n // Check If-None-Match header\n if (request.cacheRequest?.ifNoneMatch) {\n const clientEtag = request.cacheRequest.ifNoneMatch.replace(/^\"(.*)\"$/, '$1').replace(/^W\\/\"(.*)\"$/, '$1');\n if (clientEtag === hash) {\n // Return 304 Not Modified\n return {\n notModified: true,\n etag,\n };\n }\n }\n\n // Return full metadata with cache headers\n return {\n data: item,\n etag,\n lastModified: new Date().toISOString(),\n cacheControl: {\n // Metadata is invalidated by publish, so freshness must be\n // gated by the ETag validator — not a TTL. `no-cache` lets\n // clients store the body but forces an `If-None-Match`\n // revalidation on every use: a cheap 304 when unchanged,\n // fresh fields the instant a publish bumps the ETag. The old\n // `max-age=3600` pinned the schema for up to an hour, so the\n // AI-build \"New\" form kept rendering pre-publish fields until\n // the TTL lapsed (no revalidation in between). `private` also\n // keeps per-tenant metadata out of shared CDN/proxy caches.\n directives: ['private', 'no-cache'],\n },\n notModified: false,\n };\n } catch (error: any) {\n throw error;\n }\n }\n\n // ==========================================\n // Batch Operations\n // ==========================================\n\n async batchData(request: { object: string, request: BatchUpdateRequest }): Promise<BatchUpdateResponse> {\n const { object, request: batchReq } = request;\n const { operation, records, options } = batchReq;\n const results: Array<{ id?: string; success: boolean; error?: string; record?: any }> = [];\n let succeeded = 0;\n let failed = 0;\n\n for (const record of records) {\n try {\n switch (operation) {\n case 'create': {\n const created = await this.engine.insert(object, record.data || record);\n results.push({ id: created.id, success: true, record: created });\n succeeded++;\n break;\n }\n case 'update': {\n if (!record.id) throw new Error('Record id is required for update');\n const updated = await this.engine.update(object, record.data || {}, { where: { id: record.id } });\n results.push({ id: record.id, success: true, record: updated });\n succeeded++;\n break;\n }\n case 'upsert': {\n // Try update first, then create if not found\n if (record.id) {\n try {\n const existing = await this.engine.findOne(object, { where: { id: record.id } });\n if (existing) {\n const updated = await this.engine.update(object, record.data || {}, { where: { id: record.id } });\n results.push({ id: record.id, success: true, record: updated });\n } else {\n const created = await this.engine.insert(object, { id: record.id, ...(record.data || {}) });\n results.push({ id: created.id, success: true, record: created });\n }\n } catch {\n const created = await this.engine.insert(object, { id: record.id, ...(record.data || {}) });\n results.push({ id: created.id, success: true, record: created });\n }\n } else {\n const created = await this.engine.insert(object, record.data || record);\n results.push({ id: created.id, success: true, record: created });\n }\n succeeded++;\n break;\n }\n case 'delete': {\n if (!record.id) throw new Error('Record id is required for delete');\n await this.engine.delete(object, { where: { id: record.id } });\n results.push({ id: record.id, success: true });\n succeeded++;\n break;\n }\n default:\n results.push({ id: record.id, success: false, error: `Unknown operation: ${operation}` });\n failed++;\n }\n } catch (err: any) {\n results.push({ id: record.id, success: false, error: err.message });\n failed++;\n if (options?.atomic) {\n // Abort remaining operations on first failure in atomic mode\n break;\n }\n if (!options?.continueOnError) {\n break;\n }\n }\n }\n\n return {\n success: failed === 0,\n operation,\n total: records.length,\n succeeded,\n failed,\n results: options?.returnRecords !== false ? results : results.map(r => ({ id: r.id, success: r.success, error: r.error })),\n } as BatchUpdateResponse;\n }\n \n async createManyData(request: { object: string, records: any[] }): Promise<any> {\n const records = await this.engine.insert(request.object, request.records);\n return {\n object: request.object,\n records,\n count: records.length\n };\n }\n \n async updateManyData(request: UpdateManyDataRequest): Promise<BatchUpdateResponse> {\n const { object, records, options } = request;\n const results: Array<{ id?: string; success: boolean; error?: string; record?: any }> = [];\n let succeeded = 0;\n let failed = 0;\n\n for (const record of records) {\n try {\n const updated = await this.engine.update(object, record.data, { where: { id: record.id } });\n results.push({ id: record.id, success: true, record: updated });\n succeeded++;\n } catch (err: any) {\n results.push({ id: record.id, success: false, error: err.message });\n failed++;\n if (!options?.continueOnError) {\n break;\n }\n }\n }\n\n return {\n success: failed === 0,\n operation: 'update',\n total: records.length,\n succeeded,\n failed,\n results,\n } as BatchUpdateResponse;\n }\n\n async analyticsQuery(request: any): Promise<any> {\n // Map AnalyticsQuery (cube-style) to engine aggregation.\n // cube name maps to object name; measures → aggregations; dimensions → groupBy.\n const { query, cube } = request;\n const object = cube;\n\n // Build groupBy from dimensions\n const groupBy = query.dimensions || [];\n\n // Build aggregations from measures\n // Measures can be simple field names like \"count\" or \"field_name.sum\"\n // Or cube-defined measure names. We support: field.function or just function(field).\n const aggregations: Array<{ field: string; method: string; alias: string }> = [];\n if (query.measures) {\n for (const measure of query.measures) {\n // Support formats: \"count\", \"amount.sum\", \"revenue.avg\"\n if (measure === 'count' || measure === 'count_all') {\n aggregations.push({ field: '*', method: 'count', alias: 'count' });\n } else if (measure.includes('.')) {\n const [field, method] = measure.split('.');\n aggregations.push({ field, method, alias: `${field}_${method}` });\n } else {\n // Treat as count of the field\n aggregations.push({ field: measure, method: 'sum', alias: measure });\n }\n }\n }\n\n // Build filter from analytics filters\n let filter: any = undefined;\n if (query.filters && query.filters.length > 0) {\n const conditions: any[] = query.filters.map((f: any) => {\n const op = this.mapAnalyticsOperator(f.operator);\n if (f.values && f.values.length === 1) {\n return { [f.member]: { [op]: f.values[0] } };\n } else if (f.values && f.values.length > 1) {\n return { [f.member]: { $in: f.values } };\n }\n return { [f.member]: { [op]: true } };\n });\n filter = conditions.length === 1 ? conditions[0] : { $and: conditions };\n }\n\n // Execute via engine.aggregate (which delegates to driver.find with groupBy/aggregations)\n const rows = await this.engine.aggregate(object, {\n where: filter,\n groupBy: groupBy.length > 0 ? groupBy : undefined,\n aggregations: aggregations.length > 0\n ? aggregations.map(a => ({ function: a.method as any, field: a.field, alias: a.alias }))\n : [{ function: 'count' as any, alias: 'count' }],\n });\n\n // Build field metadata\n const fields = [\n ...groupBy.map((d: string) => ({ name: d, type: 'string' })),\n ...aggregations.map(a => ({ name: a.alias, type: 'number' })),\n ];\n\n return {\n success: true,\n data: {\n rows,\n fields,\n },\n };\n }\n\n async getAnalyticsMeta(request: any): Promise<any> {\n // Auto-generate cube metadata from registered objects in SchemaRegistry.\n // Each object becomes a cube; number fields → measures; other fields → dimensions.\n const objects = this.engine.registry.listItems('object');\n const cubeFilter = request?.cube;\n\n const cubes: any[] = [];\n for (const obj of objects) {\n const schema = obj as any;\n if (cubeFilter && schema.name !== cubeFilter) continue;\n\n const measures: Record<string, any> = {};\n const dimensions: Record<string, any> = {};\n const fields = schema.fields || {};\n\n // Always add a count measure\n measures['count'] = {\n name: 'count',\n label: 'Count',\n type: 'count',\n sql: '*',\n };\n\n for (const [fieldName, fieldDef] of Object.entries(fields)) {\n const fd = fieldDef as any;\n const fieldType = fd.type || 'text';\n\n if (['number', 'currency', 'percent'].includes(fieldType)) {\n // Numeric fields become both measures and dimensions\n measures[`${fieldName}_sum`] = {\n name: `${fieldName}_sum`,\n label: `${fd.label || fieldName} (Sum)`,\n type: 'sum',\n sql: fieldName,\n };\n measures[`${fieldName}_avg`] = {\n name: `${fieldName}_avg`,\n label: `${fd.label || fieldName} (Avg)`,\n type: 'avg',\n sql: fieldName,\n };\n dimensions[fieldName] = {\n name: fieldName,\n label: fd.label || fieldName,\n type: 'number',\n sql: fieldName,\n };\n } else if (['date', 'datetime'].includes(fieldType)) {\n dimensions[fieldName] = {\n name: fieldName,\n label: fd.label || fieldName,\n type: 'time',\n sql: fieldName,\n granularities: ['day', 'week', 'month', 'quarter', 'year'],\n };\n } else if (['boolean'].includes(fieldType)) {\n dimensions[fieldName] = {\n name: fieldName,\n label: fd.label || fieldName,\n type: 'boolean',\n sql: fieldName,\n };\n } else {\n // text, select, lookup, etc. → dimension\n dimensions[fieldName] = {\n name: fieldName,\n label: fd.label || fieldName,\n type: 'string',\n sql: fieldName,\n };\n }\n }\n\n cubes.push({\n name: schema.name,\n title: schema.label || schema.name,\n description: schema.description,\n sql: schema.name,\n measures,\n dimensions,\n public: true,\n });\n }\n\n return {\n success: true,\n data: { cubes },\n };\n }\n\n private mapAnalyticsOperator(op: string): string {\n const map: Record<string, string> = {\n equals: '$eq',\n notEquals: '$ne',\n contains: '$contains',\n notContains: '$notContains',\n gt: '$gt',\n gte: '$gte',\n lt: '$lt',\n lte: '$lte',\n set: '$ne',\n notSet: '$eq',\n };\n return map[op] || '$eq';\n }\n\n async triggerAutomation(_request: any): Promise<any> {\n throw new Error('triggerAutomation requires plugin-automation service. Install and register a plugin that provides the \"automation\" service.');\n }\n\n async deleteManyData(request: DeleteManyDataRequest): Promise<any> {\n // This expects deleting by IDs.\n return this.engine.delete(request.object, {\n where: { id: { $in: request.ids } },\n ...request.options\n });\n }\n\n /**\n * Metadata types that are customer-overridable via {@link saveMetaItem}/\n * {@link deleteMetaItem} in project-kernel mode. Derived from the canonical\n * registry in {@link DEFAULT_METADATA_TYPE_REGISTRY}: a type opts in by\n * setting `allowOrgOverride: true` on its registry entry. The set is\n * augmented with the plural form of every singular so callers using REST\n * conventions (`/api/v1/meta/views/...`) get the same gate. See ADR-0005\n * §\"Whitelist enforcement\" for the rationale and the per-type rollout\n * checklist.\n */\n private static readonly OVERLAY_ALLOWED_TYPES: ReadonlySet<string> = (() => {\n const out = new Set<string>();\n for (const entry of DEFAULT_METADATA_TYPE_REGISTRY) {\n if (!entry.allowOrgOverride) continue;\n out.add(entry.type);\n const plural = SINGULAR_TO_PLURAL[entry.type];\n if (plural) out.add(plural);\n }\n return out;\n })();\n\n /**\n * Phase 3a-env-writable: parse `OS_METADATA_WRITABLE` once.\n * Comma-separated singular type names. When the env var is set, the\n * listed types get treated as `allowOrgOverride: true` regardless of\n * their static registry entry. This is the runtime escape hatch admins\n * use to enable Studio-side editing of types whose protocol-level flag\n * is still false (object, field, permission, …).\n *\n * Memoised at first call. Tests can override by clearing the cache via\n * {@link ObjectStackProtocolImplementation.resetEnvWritableCache}.\n */\n private static _envWritableTypes: Set<string> | null = null;\n private static envWritableTypes(): ReadonlySet<string> {\n if (this._envWritableTypes !== null) return this._envWritableTypes;\n const raw = readEnvWithDeprecation('OS_METADATA_WRITABLE', 'OBJECTSTACK_METADATA_WRITABLE') || '';\n const set = new Set<string>();\n for (const tok of raw.split(',')) {\n const t = tok.trim();\n if (!t) continue;\n const singular = PLURAL_TO_SINGULAR[t] ?? t;\n set.add(singular);\n const plural = SINGULAR_TO_PLURAL[singular];\n if (plural) set.add(plural);\n }\n this._envWritableTypes = set;\n return set;\n }\n\n /** Test hook — clear the memoised env-writable cache. */\n static resetEnvWritableCache(): void {\n this._envWritableTypes = null;\n }\n\n /**\n * Types that opt into runtime creation of brand-new items (ADR-0005\n * extension — two-tier model). A type may have\n * `allowOrgOverride: false` (cannot overlay artifact-shipped items)\n * yet still set `allowRuntimeCreate: true` (users can author new\n * items in `sys_metadata`). The two flags are orthogonal; see\n * {@link isArtifactBacked} for how the protocol decides which gate\n * applies to a given save/delete.\n */\n /**\n * Set of type names that have a static entry in\n * `DEFAULT_METADATA_TYPE_REGISTRY`. Anything outside this set is\n * runtime-registered (plugin-provided types like `theme`, `api`,\n * `connector`) — the listing endpoint at `getMetaTypes()` synthesises\n * those with `allowRuntimeCreate: true`, so this gate must agree.\n */\n private static readonly STATIC_REGISTRY_TYPES: ReadonlySet<string> = (() => {\n const out = new Set<string>();\n for (const entry of DEFAULT_METADATA_TYPE_REGISTRY) {\n out.add(entry.type);\n const plural = SINGULAR_TO_PLURAL[entry.type];\n if (plural) out.add(plural);\n }\n return out;\n })();\n\n private static readonly RUNTIME_CREATE_ALLOWED_TYPES: ReadonlySet<string> = (() => {\n const out = new Set<string>();\n for (const entry of DEFAULT_METADATA_TYPE_REGISTRY) {\n if (!entry.allowRuntimeCreate) continue;\n out.add(entry.type);\n const plural = SINGULAR_TO_PLURAL[entry.type];\n if (plural) out.add(plural);\n }\n return out;\n })();\n\n /** Normalize plural→singular before consulting the allow-list. */\n private static isOverlayAllowed(type: string): boolean {\n const singular = PLURAL_TO_SINGULAR[type] ?? type;\n if (this.OVERLAY_ALLOWED_TYPES.has(singular)\n || this.OVERLAY_ALLOWED_TYPES.has(type)) {\n return true;\n }\n const env = this.envWritableTypes();\n return env.has(singular) || env.has(type);\n }\n\n /** Does this type permit creating brand-new (artifact-free) items? */\n private static isRuntimeCreateAllowed(type: string): boolean {\n const singular = PLURAL_TO_SINGULAR[type] ?? type;\n if (this.RUNTIME_CREATE_ALLOWED_TYPES.has(singular)\n || this.RUNTIME_CREATE_ALLOWED_TYPES.has(type)) {\n return true;\n }\n // Runtime-registered types (no static registry entry) are\n // synthesised by getMetaTypes() with allowRuntimeCreate=true;\n // mirror that here so /api/v1/meta and PUT /api/v1/meta agree.\n if (!this.STATIC_REGISTRY_TYPES.has(singular)\n && !this.STATIC_REGISTRY_TYPES.has(type)) {\n return true;\n }\n return false;\n }\n\n /**\n * Does an artifact (npm-package-loaded) item exist at `(type, name)`?\n *\n * The schema registry's `_packageId` tag is set only when\n * `registerItem(..., packageId)` is called with a truthy packageId\n * — and only artifact loaders do that. DB-rehydrated items\n * (sys_metadata rows registered back into the registry by\n * `getMetaItems` / `loadMetaFromDb`) call `registerItem` without a\n * packageId, so they carry no `_packageId` and are correctly\n * excluded here.\n *\n * Used by the two-tier authorization model to distinguish\n * \"overlaying a packaged item\" (requires `allowOrgOverride`) from\n * \"authoring a DB-only item\" (requires only `allowRuntimeCreate`).\n */\n private isArtifactBacked(type: string, name: string): boolean {\n // `lookupArtifactItem` only returns items whose `_packageId` marks a\n // genuine code package (the `'sys_metadata'` rehydration sentinel is\n // excluded), and — via `SchemaRegistry.getArtifactItem` — is immune\n // to plain-key shadows hydrated from overlay rows.\n return this.lookupArtifactItem(type, name) !== undefined;\n }\n\n // ───────────────────────────────────────────────────────────────────\n // ADR-0010 — metadata protection (Phase 1: L3 item-level lock)\n // ───────────────────────────────────────────────────────────────────\n\n /**\n * Look up an item from the artifact registry across both the requested\n * type and its singular/plural twin. Returns `undefined` when the\n * registry is unavailable or the item is not artifact-backed.\n */\n private lookupArtifactItem(type: string, name: string, currentPackageId?: string): unknown {\n const registry = (this.engine as any)?.registry;\n if (!registry) return undefined;\n const singular = PLURAL_TO_SINGULAR[type] ?? type;\n // Prefer the artifact-only lookup: it scans composite\n // (`<packageId>:<name>`) entries first, so an overlay row hydrated\n // into the plain key (getMetaItems / loadMetaFromDb) can never\n // shadow the packaged artifact's protection envelope (ADR-0010\n // §3.3 — pre-fix, that shadow made a `_lock: full` app read back\n // as unlocked after PUT+GET until restart). `currentPackageId`\n // (ADR-0048) makes that scan package-scoped (prefer-local).\n if (typeof registry.getArtifactItem === 'function') {\n return registry.getArtifactItem(singular, name, currentPackageId)\n ?? registry.getArtifactItem(type, name, currentPackageId);\n }\n // Partial registry mocks in tests — fall back to getItem and apply\n // the same package-provenance filter inline.\n if (typeof registry.getItem !== 'function') return undefined;\n const item = registry.getItem(singular, name, currentPackageId) ?? registry.getItem(type, name, currentPackageId);\n if (!item || !(item as any)._packageId || (item as any)._packageId === 'sys_metadata') {\n return undefined;\n }\n return item;\n }\n\n /**\n * True when `packageId` is a **writable base** — a DB-backed package an\n * org or the AI may author *new* metadata into (ADR-0070 D2). The two\n * read-only kinds return `false`:\n *\n * • **Booted code packages** — they register a manifest into the engine\n * at startup (`registerApp` → `engine.manifests`); their items are\n * code-shipped artifacts. Only `allowOrgOverride` overlays are allowed\n * (ADR-0005), never fresh authored items.\n * • **Installed / platform packages** — manifest `scope` is `system` or\n * `cloud` (marketplace / platform-delivered).\n *\n * A project-scoped DB package, or a bare ADR-0048 *authoring-workspace* id\n * with no registered manifest, is writable.\n *\n * NOTE: the code-package signal is the engine manifest map ONLY — we\n * deliberately do NOT fall back to \"owns ≥1 registered object\" (the old\n * `isLoadedPackage` heuristic). A writable base accrues registered objects\n * once its drafts publish, and that must never flip the base to read-only\n * — that is the exact #2252 read-only-after-publish trap this ADR removes.\n */\n private isWritablePackage(packageId: string | null | undefined): boolean {\n if (!packageId) return false;\n const engine = this.engine as any;\n // Booted code package → read-only artifact source.\n if (engine?.manifests?.has?.(packageId)) return false;\n // Installed / platform package → read-only by manifest scope.\n const scope = engine?.registry?.getPackage?.(packageId)?.manifest?.scope;\n if (scope === 'system' || scope === 'cloud') return false;\n // Project-scoped base, or unregistered authoring-workspace id → writable.\n return true;\n }\n\n /**\n * Resolve the effective `_lock` for an item by consulting the\n * artifact registry first, then the persisted overlay row. Artifact\n * always wins — by design, an overlay cannot loosen a packaged\n * lock (ADR-0010 §3.3).\n *\n * Returns `'none'` when nothing is locked, which is the common\n * case. Safe to call when `environmentId` is undefined (control-\n * plane bootstrap) — the lock check is only meaningful in tenant\n * scope and the caller is expected to also gate on `environmentId`.\n */\n private async getEffectiveLock(\n type: string,\n name: string,\n organizationId: string | null | undefined,\n ): Promise<{\n lock: MetadataLock;\n lockReason: string | undefined;\n lockSource: 'artifact' | 'overlay' | undefined;\n }> {\n // 1. Artifact wins. `lookupArtifactItem` is shadow-immune: a\n // sys_metadata overlay row hydrated into the registry's plain\n // key cannot mask the packaged artifact's `_lock` envelope.\n const artifactItem = this.lookupArtifactItem(type, name) as any;\n if (artifactItem) {\n const p = extractProtection(artifactItem);\n if (p.lock !== 'none') {\n return { lock: p.lock, lockReason: p.lockReason, lockSource: 'artifact' };\n }\n }\n // 2. Overlay row.\n try {\n const where: Record<string, unknown> = {\n type,\n name,\n state: 'active',\n organization_id: organizationId ?? null,\n };\n const row = await this.engine.findOne('sys_metadata', { where });\n if (row) {\n const body = typeof row.metadata === 'string' ? JSON.parse(row.metadata) : row.metadata;\n const p = extractProtection(body);\n if (p.lock !== 'none') {\n return { lock: p.lock, lockReason: p.lockReason, lockSource: 'overlay' };\n }\n }\n } catch {\n // DB unavailable — fall through to 'none'.\n }\n return { lock: 'none', lockReason: undefined, lockSource: undefined };\n }\n\n /**\n * Best-effort audit-row writer (ADR-0010 §3.6). Failures here are\n * logged but never block the underlying decision: an environment\n * without the audit table provisioned (legacy installs before this\n * ADR landed) still answers normal API calls, just without the\n * compliance trail. Phase 2 will make the audit table a hard\n * dependency.\n */\n private async recordMetadataAudit(entry: {\n type: string;\n name: string;\n organizationId?: string | null;\n operation: 'save' | 'publish' | 'rollback' | 'delete' | 'reset';\n outcome: 'allowed' | 'denied' | 'forced';\n code: string;\n lockState?: MetadataLock;\n lockOverridden?: boolean;\n actor?: string;\n source?: string;\n requestId?: string;\n note?: string;\n }): Promise<void> {\n try {\n await this.engine.insert('sys_metadata_audit', {\n occurred_at: new Date().toISOString(),\n actor: entry.actor ?? 'system',\n source: entry.source ?? 'protocol',\n type: PLURAL_TO_SINGULAR[entry.type] ?? entry.type,\n name: entry.name,\n organization_id: entry.organizationId ?? null,\n operation: entry.operation,\n outcome: entry.outcome,\n code: entry.code,\n lock_state: entry.lockState ?? 'none',\n lock_overridden: entry.lockOverridden ?? false,\n request_id: entry.requestId ?? null,\n note: entry.note ?? null,\n } as any);\n } catch (err: any) {\n // Don't promote audit-table failures to API errors. Log so\n // operators can spot a misconfigured deployment.\n console.warn(\n `[Protocol] sys_metadata_audit write failed for ${entry.type}/${entry.name}: ${err?.message ?? err}`,\n );\n }\n }\n\n /**\n * Phase 1 L3 enforcement for write operations (save / publish /\n * rollback). Returns null on allow. Returns the structured `Error`\n * the caller should `throw` on deny — also records the denial in\n * the audit log so refused attempts are visible in compliance\n * reports (refused writes never reach sys_metadata_history).\n */\n private async assertLockAllowsWrite(args: {\n type: string;\n name: string;\n organizationId?: string;\n operation: 'save' | 'publish' | 'rollback';\n actor?: string;\n source?: string;\n requestId?: string;\n }): Promise<Error | null> {\n if (this.environmentId === undefined) return null;\n const state = await this.getEffectiveLock(args.type, args.name, args.organizationId ?? null);\n const refusal = evaluateLockForWrite(state.lock);\n if (!refusal) return null;\n const reason = state.lockReason ?? refusal.reason;\n const err = new Error(\n `[item_locked] ${args.type}/${args.name} is locked (_lock=${state.lock}${state.lockSource ? `, source=${state.lockSource}` : ''}). `\n + `${reason} — See ADR-0010 §3.3.`,\n );\n (err as any).code = 'item_locked';\n (err as any).status = 403;\n (err as any).lock = state.lock;\n (err as any).lockReason = reason;\n await this.recordMetadataAudit({\n type: args.type,\n name: args.name,\n organizationId: args.organizationId ?? null,\n operation: args.operation,\n outcome: 'denied',\n code: 'item_locked',\n lockState: state.lock,\n actor: args.actor,\n source: args.source ?? `protocol.${args.operation}MetaItem`,\n requestId: args.requestId,\n note: reason,\n });\n return err;\n }\n\n /** Counterpart of {@link assertLockAllowsWrite} for delete. */\n private async assertLockAllowsDelete(args: {\n type: string;\n name: string;\n organizationId?: string;\n actor?: string;\n source?: string;\n requestId?: string;\n }): Promise<Error | null> {\n if (this.environmentId === undefined) return null;\n const state = await this.getEffectiveLock(args.type, args.name, args.organizationId ?? null);\n const refusal = evaluateLockForDelete(state.lock);\n if (!refusal) return null;\n const reason = state.lockReason ?? refusal.reason;\n const err = new Error(\n `[item_locked] ${args.type}/${args.name} is locked (_lock=${state.lock}${state.lockSource ? `, source=${state.lockSource}` : ''}). `\n + `${reason} — See ADR-0010 §3.3.`,\n );\n (err as any).code = 'item_locked';\n (err as any).status = 403;\n (err as any).lock = state.lock;\n (err as any).lockReason = reason;\n await this.recordMetadataAudit({\n type: args.type,\n name: args.name,\n organizationId: args.organizationId ?? null,\n operation: 'delete',\n outcome: 'denied',\n code: 'item_locked',\n lockState: state.lock,\n actor: args.actor,\n source: args.source ?? 'protocol.deleteMetaItem',\n requestId: args.requestId,\n note: reason,\n });\n return err;\n }\n\n /**\n * Mirror an object-type overlay write into the in-memory engine\n * registry so subsequent CRUD finds the new schema. Idempotent and\n * safe to call after a successful persistence call. For the legacy\n * write path this is invoked BEFORE persistence (historical behavior\n * preserved); for the PR-10d.3 repository path it is invoked only\n * AFTER `put()` resolves successfully, so a failed write — DB error,\n * optimistic-lock conflict, validation failure — never leaks a\n * stale schema into the registry.\n */\n private applyObjectRegistryMutation(request: { type: string; name: string; item?: any }): void {\n if (request.type !== 'object' && request.type !== 'objects') return;\n this.engine.registry.registerItem(request.type, request.item, 'name');\n try {\n this.engine.registry.registerObject(request.item as any, 'sys_metadata');\n } catch (err: any) {\n console.warn(\n `[Protocol] registerObject failed for ${request.name}: ${err?.message ?? err}`,\n );\n }\n }\n\n /**\n * Heal the in-memory registry after a metadata reset (overlay-row\n * delete) on control-plane kernels. Two layers:\n *\n * 1. Drop the plain-key runtime shadow so the packaged artifact\n * (registered under `<packageId>:<name>`) becomes the visible\n * value again. The shadow is written by the overlay-hydration\n * paths (`getMetaItems` / `loadMetaFromDb`) and — pre-fix —\n * survived the reset until restart, leaving stale overlay\n * content (and a stripped `_lock` envelope) in every\n * registry-direct read (ADR-0010 §3.3).\n * 2. When no composite-key artifact exists, fall back to the\n * MetadataService baseline (FilesystemLoader-sourced types) and\n * re-register it, preserving the historical refresh behaviour\n * for items the SchemaRegistry never held as artifacts.\n *\n * Best-effort: a failure must never block the delete that already\n * succeeded; the next full reload fixes the registry anyway.\n */\n private async restoreArtifactRegistryView(type: string, name: string): Promise<void> {\n try {\n const registry: any = this.engine.registry;\n let healed = false;\n if (typeof registry.removeRuntimeShadow === 'function') {\n const singular = PLURAL_TO_SINGULAR[type] ?? type;\n healed = registry.removeRuntimeShadow(singular, name);\n if (type !== singular) {\n healed = registry.removeRuntimeShadow(type, name) || healed;\n }\n }\n if (healed) return;\n // MetadataService re-registration is control-plane-only — it\n // preserves the historical refresh semantics gated on\n // `environmentId === undefined` at the original call sites.\n if (this.environmentId !== undefined) return;\n const services = this.getServicesRegistry?.();\n const metadataService = services?.get('metadata');\n if (metadataService && typeof metadataService.get === 'function') {\n const artifactItem = await metadataService.get(type, name);\n if (artifactItem !== undefined) {\n this.engine.registry.registerItem(type, artifactItem, 'name');\n }\n }\n } catch {\n // Best-effort registry refresh; next read fixes it anyway\n }\n }\n\n /**\n * Ensure a just-PUBLISHED object's physical table exists so it is usable\n * for data CRUD immediately — without a server restart. Registering the\n * object (above) only updates the in-memory registry; the table is created\n * by the driver's schema sync, which otherwise only runs at boot. Without\n * this, inserting into a freshly-published object fails with \"no such\n * table\" (surfaced as `object_not_found`) until the next restart.\n * Best-effort + non-fatal: drivers without DDL (or read-only datasources)\n * simply no-op, and a sync failure must not abort the publish.\n */\n private async ensureObjectStorage(type: string, name: string): Promise<void> {\n if (type !== 'object' && type !== 'objects') return;\n try {\n await this.engine.syncObjectSchema(name);\n } catch (err: any) {\n console.warn(`[Protocol] table sync failed for object '${name}': ${err?.message ?? err}`);\n }\n }\n\n /**\n * Inverse of {@link ensureObjectStorage}: drop an object's physical table.\n * DESTRUCTIVE — deletes the table and all its rows. Only invoked when a\n * delete explicitly opts into storage teardown (see {@link deleteMetaItem}'s\n * `dropStorage`), so publishing an object solely to preview it can be undone\n * without leaving an orphan table. Best-effort: a failure is logged, not\n * thrown — the metadata delete already succeeded, and a stray table is\n * reclaimed by the next sync/drop rather than blocking the delete.\n */\n private async dropObjectStorage(type: string, name: string): Promise<void> {\n if (type !== 'object' && type !== 'objects') return;\n try {\n await this.engine.dropObjectSchema(name);\n } catch (err: any) {\n console.warn(`[Protocol] table drop failed for object '${name}': ${err?.message ?? err}`);\n }\n }\n\n /**\n * Guard for storage teardown on delete. Drops a physical table only when\n * the caller opted in AND it is safe: object types only (others have no\n * table), active state only (drafts were never materialised), and never a\n * `sys_`-prefixed platform table.\n */\n private shouldDropStorage(type: string, name: string, dropStorage: boolean | undefined, state: 'active' | 'draft'): boolean {\n if (!dropStorage) return false;\n const singular = PLURAL_TO_SINGULAR[type] ?? type;\n if (singular !== 'object') return false;\n if (state !== 'active') return false;\n if (name.startsWith('sys_')) return false;\n return true;\n }\n\n async saveMetaItem(request: { type: string, name: string, item?: any, organizationId?: string, parentVersion?: string | null, actor?: string, force?: boolean, mode?: 'draft' | 'publish', packageId?: string | null }) {\n if (!request.item) {\n throw new Error('Item data is required');\n }\n // Per-item lifecycle (ADR-0005 §\"Drafts\"). Default is `'publish'`\n // (legacy semantics — save goes straight live) to keep callers\n // that predate the draft/publish split working. Studio's\n // designer surface opts into staged drafts by sending\n // `?mode=draft`; the `POST /publish` endpoint then promotes it.\n const mode: 'draft' | 'publish' = request.mode === 'draft' ? 'draft' : 'publish';\n\n // ADR-0005 (extended — two-tier model): project-kernel customization is\n // gated by per-item provenance, not just the type-level flag.\n //\n // • Item exists as a packaged artifact → require `allowOrgOverride`\n // (writing here would overlay code-shipped behaviour; gated for\n // security on executable types like hook/trigger/validation).\n // • Item does NOT exist as an artifact → require `allowRuntimeCreate`\n // OR `allowOrgOverride`. This lets users author brand-new hooks /\n // validations / triggers without unlocking the artifact-shadowing\n // capability. Returns `not_creatable` (vs `not_overridable`) so\n // the UI can present a tailored message.\n if (this.environmentId !== undefined) {\n const overlayAllowed = ObjectStackProtocolImplementation.isOverlayAllowed(request.type);\n const runtimeCreateAllowed = ObjectStackProtocolImplementation.isRuntimeCreateAllowed(request.type);\n const artifactBacked = this.isArtifactBacked(request.type, request.name);\n if (artifactBacked && !overlayAllowed) {\n const err = new Error(\n `[not_overridable] Metadata item '${request.type}/${request.name}' is provided by a code package `\n + `and the type has not opted into per-org overlay writes (allowOrgOverride=false). `\n + `Edit the source artifact and redeploy, or set OS_METADATA_WRITABLE to grant a runtime escape hatch. `\n + `See docs/adr/0005-metadata-customization-overlay.md.`\n );\n (err as any).code = 'not_overridable';\n (err as any).status = 403;\n throw err;\n }\n if (!artifactBacked && !overlayAllowed && !runtimeCreateAllowed) {\n const err = new Error(\n `[not_creatable] Metadata type '${request.type}' does not allow runtime creation `\n + `(allowRuntimeCreate=false, allowOrgOverride=false). New items of this type must be defined in source code.`\n );\n (err as any).code = 'not_creatable';\n (err as any).status = 403;\n throw err;\n }\n\n // ADR-0010 L3 — per-item lock. Artifact `_lock` (or persisted\n // overlay `_lock`) blocks save independent of the L1 type-level\n // flag. Records the denial in `sys_metadata_audit` before\n // throwing so refused attempts are visible in compliance reports.\n const lockErr = await this.assertLockAllowsWrite({\n type: request.type,\n name: request.name,\n ...(request.organizationId ? { organizationId: request.organizationId } : {}),\n operation: 'save',\n ...(request.actor ? { actor: request.actor } : {}),\n source: 'protocol.saveMetaItem',\n });\n if (lockErr) throw lockErr;\n }\n\n // Phase 3a-destructive: for object/field writes, diff against the\n // current schema and 409 if the change would drop data — unless the\n // caller has acknowledged the risk with `force: true`. The admin UI\n // surfaces the structured `issues` payload in a confirmation dialog.\n const singularType = PLURAL_TO_SINGULAR[request.type] ?? request.type;\n if (!request.force && (singularType === 'object' || singularType === 'field')) {\n try {\n const existing = await this.getMetaItem({\n type: request.type,\n name: request.name,\n ...(request.organizationId ? { organizationId: request.organizationId } : {}),\n } as any);\n const prev = (existing as any)?.item;\n if (prev) {\n const issues = detectDestructiveObjectChanges(prev, request.item);\n if (issues.length > 0) {\n const summary = issues.slice(0, 3).map((i) => i.message).join('; ');\n const err = new Error(\n `[destructive_change] ${request.type}/${request.name} would drop or transform existing data: ${summary}`\n + (issues.length > 3 ? ` (+${issues.length - 3} more)` : '')\n + ` — re-submit with ?force=true to proceed.`\n );\n (err as any).code = 'destructive_change';\n (err as any).status = 409;\n (err as any).issues = issues;\n throw err;\n }\n }\n } catch (err: any) {\n if (err?.code === 'destructive_change') throw err;\n // Other errors during the diff lookup are non-fatal —\n // they just skip the safety check.\n }\n }\n\n // Defense-in-depth: reject the layered *read* envelope as a write body.\n //\n // `getMetaItemLayered` returns a 3-state diagnostic shape\n // `{ type, name, code, overlay, overlayScope, effective, ... }` for the\n // Studio designer's `?layers=true` GET. That envelope is NOT a metadata\n // body — but a designer surface that lacks a dedicated editor for a\n // given type can accidentally PUT the envelope straight back, which (if\n // the per-type Zod schema below is unavailable — e.g. a type with no\n // registered schema, or a stale `@objectstack/spec` build that predates\n // the type being added to the registry) would persist an all-null stub\n // and surface as a metadata diagnostic error in the admin UI. The\n // simultaneous presence of `code`, `overlay`, `overlayScope`, and\n // `effective` is unique to the layered envelope and never appears in a\n // real metadata body, so we reject it here regardless of type/schema.\n {\n const it = request.item as Record<string, unknown>;\n const looksLikeLayeredEnvelope =\n it && typeof it === 'object' && !Array.isArray(it)\n && 'code' in it && 'overlay' in it && 'overlayScope' in it && 'effective' in it;\n if (looksLikeLayeredEnvelope) {\n const err = new Error(\n `[invalid_metadata] ${request.type}/${request.name}: the request body is a layered read `\n + `envelope ({ code, overlay, overlayScope, effective }), not a metadata body. `\n + `Unwrap and send the effective/overlay document instead — the layered shape is read-only `\n + `(GET ?layers=true) and must never be persisted.`\n );\n (err as any).code = 'invalid_metadata';\n (err as any).status = 422;\n throw err;\n }\n }\n\n // Normalize loose `view` bodies to the canonical record shape BEFORE\n // validation + persistence, so no producer (AI tools, hand-authoring,\n // Studio) can persist a view that validates but the console can't bind\n // or render (missing top-level name/object/viewKind). See\n // {@link normalizeViewMetadata}.\n request.item = normalizeViewMetadata(request.type, request.item, request.name);\n\n // Spec-conformance check: if a Zod schema is registered for this\n // overlay type (see OVERLAY_VALIDATION_SCHEMAS), validate the payload\n // before persisting. We surface invalid payloads as `422\n // invalid_metadata` with structured Zod issues so the Studio form can\n // highlight the offending field. The original `item` is kept verbatim\n // — `parsed.data` would strip Studio-only auxiliary fields (e.g.\n // isPinned, isDefault, sortOrder) that intentionally ride along with\n // the overlay document. ADR-0005 §\"Validation\".\n {\n const schema = resolveOverlaySchema(request.type, request.item);\n if (schema) {\n const parsed = schema.safeParse(request.item);\n if (!parsed.success) {\n const issues = parsed.error.issues.map((i: z.ZodIssue) => ({\n path: i.path.join('.'),\n message: i.message,\n code: i.code,\n }));\n const summary = issues.slice(0, 3)\n .map((i: { path: string; message: string }) => `${i.path || '<root>'}: ${i.message}`)\n .join('; ');\n const err = new Error(\n `[invalid_metadata] ${request.type}/${request.name} failed spec validation: ${summary}`\n + (issues.length > 3 ? ` (+${issues.length - 3} more)` : '')\n );\n (err as any).code = 'invalid_metadata';\n (err as any).status = 422;\n (err as any).issues = issues;\n throw err;\n }\n }\n }\n\n // 1. Update the in-memory registry (runtime cache) ONLY for the\n // `object` type — schema definitions feed engine.syncSchema and\n // must be reflected immediately for CRUD to work. For all other\n // metadata types (view, dashboard, ...) we deliberately do NOT\n // mutate the artifact-loaded registry — sys_metadata is the\n // authoritative overlay store and `getMetaItem` consults it\n // first (ADR-0005). Mutating the registry here would create a\n // \"stale overlay\" hazard: `deleteMetaItem` cannot restore the\n // original artifact value because it was overwritten in-place.\n // 1. (deferred) — Object-type runtime-registry mutation used to happen\n // here unconditionally. Moved to AFTER successful persistence\n // (PR-10d.3 rubber-duck #3): a failed put() — DB error, optimistic\n // conflict, validation — must not leave a stale object schema in\n // the in-memory registry. See `applyObjectRegistryMutation` below.\n\n // 2. Persist to sys_metadata as a customization overlay row.\n // ADR-0005 (revised 2026-05): isolation key is `organization_id`\n // (each env = its own DB, so environment_id is redundant). Org-scoped\n // rows belong to the active organization in the request; env-wide\n // overlays are written with organization_id = NULL.\n await this.ensureOverlayIndex();\n\n // ADR-0008 — overlay-allowed metadata types ALWAYS route through the\n // repository write path: every mutation appends to the change log\n // and emits a watch event with a monotonic `seq` (which Studio /\n // browser clients consume for HMR). Non-overlay-allowed types\n // (`object`, `flow`, `agent`, ...) take the legacy raw-engine path\n // below — this preserves the control-plane bootstrap semantic where\n // `saveMetaItem` is permitted by the outer protocol gate to write\n // any metadata type when `environmentId` is undefined (the repository's\n // `assertAllowed()` would 403 those writes).\n //\n // PR-10d.6 (this PR) removed the `useRepositoryWritePath` flag.\n // For overlay-allowed types the repo path is no longer opt-out-able.\n //\n // Callers that omit `parentVersion` get backward-compatible\n // \"last-write-wins\" semantics: we read the current row's checksum\n // and use it as the parent, so the conflict check tautologically\n // passes (best-effort — racy under concurrent writes; explicit\n // optimistic-lock is opt-in via `parentVersion`).\n // Callers that pass an explicit `parentVersion` (e.g. Studio after\n // reading an item) get true optimistic-lock conflict detection\n // surfaced as a 409.\n const singularTypeForRepo = PLURAL_TO_SINGULAR[request.type] ?? request.type;\n const overlayAllowedForRepo = ObjectStackProtocolImplementation.isOverlayAllowed(singularTypeForRepo);\n const runtimeCreateAllowedForRepo = ObjectStackProtocolImplementation.isRuntimeCreateAllowed(singularTypeForRepo);\n const useRepoPath = overlayAllowedForRepo || runtimeCreateAllowedForRepo;\n if (useRepoPath) {\n const artifactBacked = this.isArtifactBacked(singularTypeForRepo, request.name);\n const intent: 'override-artifact' | 'runtime-only' = artifactBacked\n ? 'override-artifact'\n : 'runtime-only';\n // D1 (ADR-0070) — a brand-new, DB-only (\"runtime-only\") metadata\n // item MUST resolve to a WRITABLE base. Binding it to a read-only\n // code/installed package makes it read back as \"code-provided\" and\n // lock read-only after publish (the #2252 bug). We used to silently\n // coerce such a binding to `null`, but that scattered orphans into a\n // package-less bucket with no container to delete; ADR-0070 replaces\n // the coercion with an actionable rejection so the authoring surface\n // (Studio / AI) redirects the user to pick or create a base first.\n //\n // Left untouched (the binding survives):\n // • `override-artifact` writes — an org overlay OF a packaged item\n // must keep pointing at the package it customizes (ADR-0005).\n // • a project-scoped base, or a bare ADR-0048 authoring-workspace\n // id — both are writable; `isWritablePackage` returns true.\n // A `null` packageId is still accepted here (legacy org-overlay\n // destination); ADR-0070 D5 retires it once the surfaces always\n // resolve a base and the orphan migration has run.\n if (\n intent === 'runtime-only' &&\n request.packageId != null &&\n !this.isWritablePackage(request.packageId)\n ) {\n const err = new Error(\n `[writable_package_required] Cannot author ${singularTypeForRepo}/${request.name} into `\n + `'${request.packageId}': it is a read-only code/installed package, not a writable base. `\n + `Create or select a writable base (package) first, then retry. `\n + `See docs/adr/0070-package-first-authoring.md.`,\n );\n (err as any).code = 'writable_package_required';\n (err as any).status = 422;\n (err as any).packageId = request.packageId;\n throw err;\n }\n const orgId = request.organizationId ?? null;\n const repo = this.getOverlayRepo(orgId);\n const ref = {\n type: singularTypeForRepo,\n name: request.name,\n org: orgId ?? 'env',\n } as Parameters<typeof repo.put>[0];\n let parentVersion: string | null;\n if (request.parentVersion !== undefined) {\n parentVersion = request.parentVersion;\n } else {\n // Parent is scoped to the lifecycle we're about to write:\n // a draft's parent is the current draft hash (or null\n // for the first draft); a publish's parent is the\n // current published hash. ADR-0048 — scope to the same\n // package the upsert targets so a collision's other-package\n // row is never read as this item's parent.\n const current = await repo.get(ref, {\n state: mode === 'draft' ? 'draft' : 'active',\n packageId: request.packageId ?? null,\n });\n parentVersion = current?.hash ?? null;\n }\n try {\n const result = await repo.put(ref, request.item, {\n parentVersion,\n actor: request.actor ?? 'system',\n source: 'protocol.saveMetaItem',\n intent,\n state: mode === 'draft' ? 'draft' : 'active',\n ...(request.packageId !== undefined ? { packageId: request.packageId } : {}),\n });\n // Persistence succeeded — NOW it's safe to mutate the\n // in-memory object registry. If put() had thrown, the\n // registry would still reflect the prior state. Drafts\n // are NOT live: don't propagate them into the runtime\n // object registry (would defeat the staging buffer).\n if (mode === 'publish') {\n this.applyObjectRegistryMutation(request);\n await this.ensureObjectStorage(request.type, request.name);\n }\n // ADR-0010 — success audit (best-effort).\n await this.recordMetadataAudit({\n type: request.type,\n name: request.name,\n organizationId: orgId,\n operation: 'save',\n outcome: 'allowed',\n code: 'ok',\n ...(request.actor ? { actor: request.actor } : {}),\n source: 'protocol.saveMetaItem',\n note: mode === 'draft' ? 'draft' : 'active',\n });\n return {\n success: true,\n version: result.version,\n seq: result.seq,\n state: mode === 'draft' ? 'draft' : 'active',\n message: orgId\n ? `Saved customization overlay (org=${orgId}, state=${mode === 'draft' ? 'draft' : 'active'}) — type=${request.type}, name=${request.name} [seq=${result.seq}]`\n : `Saved customization overlay (env-wide, state=${mode === 'draft' ? 'draft' : 'active'}) — type=${request.type}, name=${request.name} [seq=${result.seq}]`,\n };\n } catch (err: any) {\n if (err instanceof ConflictError) {\n const conflict = new Error(\n `[metadata_conflict] ${request.type}/${request.name} has been modified since you loaded it. `\n + `Expected parent ${err.expectedParent ?? 'null'} but current is ${err.actualHead ?? 'null'}.`,\n );\n (conflict as any).code = 'metadata_conflict';\n (conflict as any).status = 409;\n (conflict as any).expectedParent = err.expectedParent;\n (conflict as any).actualHead = err.actualHead;\n throw conflict;\n }\n throw err;\n }\n }\n\n // Legacy raw-engine path — taken when the type is NOT overlay-allowed\n // (control-plane bootstrap of `object`/`flow`/etc. when `environmentId` is\n // undefined). This branch is intentionally retained: the repository\n // write path's `assertAllowed()` would 403 these types. There is no\n // change-log / HMR machinery for non-overlay metadata because\n // control-plane mutations are bootstrap-only and not subject to\n // per-org overlay semantics.\n //\n // Note: the registry mutation for the legacy path happens BEFORE\n // persistence (preserved historical behaviour). The overlay-allowed\n // path moved it to AFTER persistence in PR-10d.3 (rubber-duck #3).\n this.applyObjectRegistryMutation(request);\n\n try {\n const now = new Date().toISOString();\n const orgId = request.organizationId ?? null;\n const scopedWhere: Record<string, unknown> = {\n type: request.type,\n name: request.name,\n organization_id: orgId,\n state: 'active',\n };\n const existing = await this.engine.findOne('sys_metadata', {\n where: scopedWhere,\n });\n\n if (existing) {\n const updateRow: Record<string, unknown> = {\n metadata: JSON.stringify(request.item),\n updated_at: now,\n version: (existing.version || 0) + 1,\n state: 'active',\n };\n // Preserve an existing non-null package binding; only fill when\n // unset (mirror of SysMetadataRepository.put semantics).\n const existingPkg = (existing as { package_id?: string | null }).package_id ?? null;\n const nextPkg = existingPkg ?? request.packageId ?? null;\n if (nextPkg !== null) updateRow.package_id = nextPkg;\n await this.engine.update('sys_metadata', updateRow, {\n where: { id: existing.id }\n });\n } else {\n // Use crypto.randomUUID() when available (modern browsers and Node ≥ 14.17);\n // fall back to a time+random ID for older or restricted environments.\n const id = typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'\n ? crypto.randomUUID()\n : `meta_${Date.now()}_${Math.random().toString(36).slice(2)}`;\n const row: Record<string, unknown> = {\n id,\n name: request.name,\n type: request.type,\n // `scope` enum is ['system','platform','user']; per-org\n // overlays use 'platform' as the informational tag. The\n // authoritative isolation key is `organization_id`.\n scope: 'platform',\n metadata: JSON.stringify(request.item),\n state: 'active',\n version: 1,\n created_at: now,\n updated_at: now,\n organization_id: orgId,\n };\n if (request.packageId) row.package_id = request.packageId;\n await this.engine.insert('sys_metadata', row);\n }\n\n return {\n success: true,\n message: orgId\n ? `Saved customization overlay (org=${orgId}) — type=${request.type}, name=${request.name}`\n : `Saved customization overlay (env-wide) — type=${request.type}, name=${request.name}`,\n };\n } catch (dbError: any) {\n // DB write failed — surface as an error rather than silently\n // succeeding (regression from the pre-ADR-0005 \"silent loss\" bug).\n console.error(\n `[Protocol] sys_metadata persistence failed for ${request.type}/${request.name}: ${dbError.message}`,\n );\n const err = new Error(\n `Failed to persist customization overlay to sys_metadata: ${dbError.message}. `\n + `In-memory registry was updated but will be lost on restart.`,\n );\n (err as any).code = 'overlay_persistence_failed';\n (err as any).status = 500;\n throw err;\n }\n }\n\n /**\n * Yield the durable change-log for a single metadata item — every\n * put/delete recorded in `sys_metadata_history` for `(org, type, name)`,\n * in event_seq order. Powers the Studio \"History\" tab and any\n * client-side audit timeline.\n *\n * Returns `[]` for non-overlay-allowed types (the legacy raw-engine\n * path doesn't record history) instead of throwing — callers can treat\n * \"no history\" uniformly.\n */\n async historyMetaItem(request: {\n type: string;\n name: string;\n organizationId?: string;\n sinceSeq?: number;\n limit?: number;\n }): Promise<{ events: import('@objectstack/metadata-core').MetadataEvent[] }> {\n const singularType = PLURAL_TO_SINGULAR[request.type] ?? request.type;\n if (!ObjectStackProtocolImplementation.isOverlayAllowed(singularType)\n && !ObjectStackProtocolImplementation.isRuntimeCreateAllowed(singularType)) {\n return { events: [] };\n }\n const orgId = request.organizationId ?? null;\n const repo = this.getOverlayRepo(orgId);\n const ref = {\n type: singularType,\n name: request.name,\n org: orgId ?? 'env',\n } as Parameters<typeof repo.history>[0];\n\n const events: import('@objectstack/metadata-core').MetadataEvent[] = [];\n const opts: { sinceSeq?: number; limit?: number } = {};\n if (request.sinceSeq !== undefined) opts.sinceSeq = request.sinceSeq;\n if (request.limit !== undefined) opts.limit = request.limit;\n for await (const ev of repo.history(ref, opts)) events.push(ev);\n return { events };\n }\n\n /**\n * Promote the pending draft overlay to the live (`active`) row.\n * Records a history event with `op='publish'`. 404 (`[no_draft]`)\n * when there is nothing to publish.\n */\n async publishMetaItem(request: {\n type: string;\n name: string;\n organizationId?: string;\n actor?: string;\n message?: string;\n /**\n * INTERNAL — `publishPackageDrafts` publishes many drafts and batch-applies\n * every seed body in ONE loader pass afterwards (cross-seed references need\n * multi-pass over the whole set), so it suppresses the per-item apply here.\n */\n _skipSeedApply?: boolean;\n }): Promise<{\n success: boolean;\n version: string;\n seq: number;\n message?: string;\n /**\n * Present when a `seed` draft was published: the result of materializing\n * its rows. Publishing the metadata ALWAYS succeeds independently — a\n * seed-load problem is surfaced here, never thrown, so callers (and UIs)\n * must check `seedApplied.success` instead of assuming data went live.\n */\n seedApplied?: {\n success: boolean;\n inserted: number;\n updated: number;\n error?: string;\n errors?: unknown[];\n };\n }> {\n const singularType = PLURAL_TO_SINGULAR[request.type] ?? request.type;\n if (!ObjectStackProtocolImplementation.isOverlayAllowed(singularType)\n && !ObjectStackProtocolImplementation.isRuntimeCreateAllowed(singularType)) {\n const err: any = new Error(\n `[not_overridable] Metadata type '${request.type}' is not draftable — no overlay/runtime-create permission.`,\n );\n err.code = 'not_overridable';\n err.status = 403;\n throw err;\n }\n // ADR-0010 L3 — lock blocks publish too (publishing is a write).\n const _publishLockErr = await this.assertLockAllowsWrite({\n type: request.type,\n name: request.name,\n ...(request.organizationId ? { organizationId: request.organizationId } : {}),\n operation: 'publish',\n ...(request.actor ? { actor: request.actor } : {}),\n source: 'protocol.publishMetaItem',\n });\n if (_publishLockErr) throw _publishLockErr;\n await this.ensureOverlayIndex();\n const orgId = request.organizationId ?? null;\n const repo = this.getOverlayRepo(orgId);\n const artifactBacked = this.isArtifactBacked(singularType, request.name);\n const intent: 'override-artifact' | 'runtime-only' = artifactBacked\n ? 'override-artifact' : 'runtime-only';\n const ref = {\n type: singularType,\n name: request.name,\n org: orgId ?? 'env',\n } as Parameters<typeof repo.promoteDraft>[0];\n try {\n const result = await repo.promoteDraft(ref, {\n actor: request.actor ?? 'system',\n source: 'protocol.publishMetaItem',\n ...(request.message ? { message: request.message } : {}),\n intent,\n });\n // Drafts skipped the registry mutation; on publish we now\n // refresh the runtime object registry so live behaviour\n // catches up immediately (matches saveMetaItem's\n // post-persistence registry update path).\n this.applyObjectRegistryMutation({\n type: request.type,\n name: request.name,\n item: result.item.body,\n });\n // Create the object's table now so it's CRUD-able without a restart.\n await this.ensureObjectStorage(request.type, request.name);\n const response: {\n success: boolean;\n version: string;\n seq: number;\n message?: string;\n seedApplied?: { success: boolean; inserted: number; updated: number; error?: string; errors?: unknown[] };\n } = {\n success: true,\n version: result.version,\n seq: result.seq,\n message: `Published draft — type=${request.type}, name=${request.name} [seq=${result.seq}]`,\n };\n // Publishing a `seed` is what makes its rows live — materialize them\n // NOW (best-effort, never fails the publish) so every publish path\n // (per-ref REST publish, the home banner, package publish-drafts)\n // lands data, not just metadata. The body is already in hand from\n // the promote — no read-back, so no org-scope resolution pitfalls.\n if (singularType === 'seed' && !request._skipSeedApply) {\n response.seedApplied = await this.applySeedBodies([result.item.body], orgId);\n }\n return response;\n } catch (err: any) {\n if (err instanceof ConflictError) {\n const conflict: any = new Error(\n `[metadata_conflict] ${request.type}/${request.name} published row advanced while you held the draft. `\n + `Expected parent ${err.expectedParent ?? 'null'} but current is ${err.actualHead ?? 'null'}.`,\n );\n conflict.code = 'metadata_conflict';\n conflict.status = 409;\n conflict.expectedParent = err.expectedParent;\n conflict.actualHead = err.actualHead;\n throw conflict;\n }\n throw err;\n }\n }\n\n /**\n * Materialize published `seed` bodies into data rows via the SeedLoaderService\n * (externalId-keyed upsert, multi-pass for cross-seed references). Passing ALL\n * of a publish's seed bodies in ONE call lets a child seed reference a parent\n * seed's rows regardless of publish order. Best-effort: any failure is\n * returned, never thrown — publishing metadata must not be blocked by a data\n * problem, but the caller surfaces `seedApplied` so the failure is LOUD.\n */\n private async applySeedBodies(\n bodies: unknown[],\n organizationId: string | null,\n ): Promise<{ success: boolean; inserted: number; updated: number; error?: string; errors?: unknown[] }> {\n try {\n const seeds = bodies.filter(\n (b: any) => b && typeof b.object === 'string' && Array.isArray(b.records),\n );\n if (seeds.length === 0) {\n return { success: false, inserted: 0, updated: 0, error: 'seed apply: no readable seed bodies' };\n }\n const { SeedLoaderService } = await import('./seed-loader.js');\n const { SeedLoaderRequestSchema } = await import('@objectstack/spec/data');\n // The loader only needs `getObject` from IMetadataService (dependency\n // graph + field introspection); satisfy it from the protocol's own\n // metadata reads so no kernel service lookup is required.\n const metadataAdapter = {\n getObject: async (name: string) => {\n const wrapper: any = await (this as any).getMetaItem({\n type: 'object',\n name,\n ...(organizationId ? { organizationId } : {}),\n });\n return wrapper?.item ?? wrapper ?? null;\n },\n };\n const loader = new SeedLoaderService(\n this.engine as any,\n metadataAdapter as any,\n console as any,\n );\n const request = SeedLoaderRequestSchema.parse({\n seeds,\n config: {\n defaultMode: 'upsert',\n multiPass: true,\n ...(organizationId ? { organizationId } : {}),\n },\n });\n const r = await loader.load(request);\n return {\n success: r.success,\n inserted: r.summary.totalInserted,\n updated: r.summary.totalUpdated,\n ...(r.errors?.length ? { errors: r.errors } : {}),\n };\n } catch (e: any) {\n return { success: false, inserted: 0, updated: 0, error: e?.message ?? 'seed apply failed' };\n }\n }\n\n /**\n * List pending DRAFT metadata (ADR-0033) for the org, optionally narrowed\n * by `packageId` and/or `type`. The list reads of `getMetaItems` only see\n * the ACTIVE registry; this exposes what an AI authored but a human hasn't\n * published yet, so the console can show a \"pending changes\" surface and a\n * just-built app package isn't displayed as empty. No body is returned.\n */\n async listDrafts(request?: {\n packageId?: string;\n type?: string;\n organizationId?: string;\n }): Promise<{\n drafts: Array<{\n type: string;\n name: string;\n packageId: string | null;\n updatedAt: string | null;\n updatedBy: string | null;\n }>;\n }> {\n await this.ensureOverlayIndex();\n const orgId = request?.organizationId ?? null;\n const repo = this.getOverlayRepo(orgId);\n const drafts = await repo.listDrafts({\n ...(request?.type ? { type: PLURAL_TO_SINGULAR[request.type] ?? request.type } : {}),\n ...(request?.packageId ? { packageId: request.packageId } : {}),\n });\n return { drafts };\n }\n\n /**\n * Publish every pending DRAFT bound to a package in one shot (ADR-0033) —\n * the \"publish whole app\" action. Promotes each draft→active by reusing the\n * per-item {@link publishMetaItem} primitive (which runs the overridable /\n * lock guards and refreshes the runtime registry), so this needs NO\n * `metadata` service (unlike `MetadataService.publishPackage`, which reads\n * the in-memory registry and 503s when that service is absent). Per-item\n * failures are collected and do NOT abort the rest.\n */\n async publishPackageDrafts(request: {\n packageId: string;\n organizationId?: string;\n actor?: string;\n /** ADR-0067 — commit message (for AI turns: the user's instruction). */\n message?: string;\n /** ADR-0067 — AI model that authored the turn (absent for human/CLI). */\n aiModel?: string;\n }): Promise<{\n success: boolean;\n publishedCount: number;\n failedCount: number;\n published: Array<{ type: string; name: string; version: string }>;\n failed: Array<{ type: string; name: string; error: string; code?: string }>;\n /** Aggregate result of materializing every published `seed` (absent when no seeds). */\n seedApplied?: { success: boolean; inserted: number; updated: number; error?: string; errors?: unknown[] };\n /**\n * ADR-0038 L3 — post-publish runtime probe report (absent when nothing\n * was publishable). One real read per published artifact: seeded\n * objects must have rows, views must be readable, dashboard widgets'\n * dataset selections must execute and return data. `issues` carries\n * BuildIssue-shaped findings (layer 'runtime') for the agent / chat\n * health surfaces; probes never fail the publish itself.\n */\n probes?: import('./build-probes.js').BuildProbeReport;\n /** ADR-0067 — id of the commit this publish recorded (absent if nothing published). */\n commitId?: string;\n }> {\n await this.ensureOverlayIndex();\n const orgId = request.organizationId ?? null;\n const repo = this.getOverlayRepo(orgId);\n const drafts = await repo.listDrafts({ packageId: request.packageId });\n\n const published: Array<{ type: string; name: string; version: string }> = [];\n const failed: Array<{ type: string; name: string; error: string; code?: string }> = [];\n\n // Structure first, seeds LAST — a seed's rows can only land after its\n // object's table exists (publishMetaItem creates it). Within the seeds we\n // batch-apply every body in ONE loader pass below (multi-pass reference\n // resolution across the whole set), so per-item apply is suppressed.\n const ordered = [\n ...drafts.filter((d) => d.type !== 'seed'),\n ...drafts.filter((d) => d.type === 'seed'),\n ];\n const seedBodies: unknown[] = [];\n\n // ADR-0067 — capture each artifact's PRE-publish state so this turn can\n // be recorded as ONE revertible commit. existedBefore=false → the commit\n // creates it (revert = soft-remove); true → it edits an existing artifact\n // (revert = restoreVersion(prevVersion)). Best-effort: a capture failure\n // just omits that item from the revert plan, never blocks the publish.\n const commitItems: Array<{ type: string; name: string; existedBefore: boolean; prevVersion: number | null }> = [];\n for (const d of ordered) {\n try {\n const activeRow = (await this.engine.findOne('sys_metadata', {\n where: { organization_id: orgId, type: d.type, name: d.name, state: 'active' },\n })) as { version?: number } | null;\n commitItems.push({\n type: d.type,\n name: d.name,\n existedBefore: !!activeRow,\n prevVersion: activeRow && typeof activeRow.version === 'number' ? activeRow.version : null,\n });\n } catch {\n commitItems.push({ type: d.type, name: d.name, existedBefore: false, prevVersion: null });\n }\n }\n const publishedSeqs: number[] = [];\n\n for (const d of ordered) {\n try {\n if (d.type === 'seed') {\n // Capture the body BEFORE promote (the draft row is deleted by\n // the promote, and a post-publish read-back has org-scope\n // resolution pitfalls — reading the draft is unambiguous).\n const ref = { type: d.type, name: d.name, org: orgId ?? 'env' } as unknown as Parameters<typeof repo.get>[0];\n const draft = await repo.get(ref, { state: 'draft' });\n if (draft?.body) seedBodies.push(draft.body);\n }\n const r = await this.publishMetaItem({\n type: d.type,\n name: d.name,\n ...(request.organizationId ? { organizationId: request.organizationId } : {}),\n ...(request.actor ? { actor: request.actor } : {}),\n message: `publish app package '${request.packageId}'`,\n _skipSeedApply: true,\n });\n published.push({ type: d.type, name: d.name, version: r.version });\n if (typeof r.seq === 'number') publishedSeqs.push(r.seq);\n } catch (e: any) {\n failed.push({\n type: d.type,\n name: d.name,\n error: e?.message ?? 'publish failed',\n ...(e?.code ? { code: e.code } : {}),\n });\n }\n }\n\n const seedApplied = seedBodies.length > 0 ? await this.applySeedBodies(seedBodies, orgId) : undefined;\n\n // ADR-0038 L3: exercise what was just published — one real read per\n // artifact — so \"Published!\" can never again mean \"and silently\n // broken\". Best-effort by design: a probe crash is swallowed (the\n // publish already happened and must report as such), and findings ride\n // the response for the agent / chat health card to act on.\n let probes: import('./build-probes.js').BuildProbeReport | undefined;\n if (published.length > 0) {\n try {\n const { runBuildProbes } = await import('./build-probes.js');\n const analytics = this.getServicesRegistry?.().get('analytics');\n probes = await runBuildProbes({\n engine: this.engine as any,\n getItem: async (type, name) => {\n const wrapper: any = await (this as any).getMetaItem({\n type,\n name,\n ...(orgId ? { organizationId: orgId } : {}),\n });\n return wrapper?.item ?? wrapper ?? undefined;\n },\n published,\n ...(analytics && typeof analytics.queryDataset === 'function' ? { analytics } : {}),\n organizationId: orgId,\n });\n } catch {\n probes = undefined;\n }\n }\n\n // ADR-0067 — record this turn as ONE commit (best-effort; never fails\n // the publish). Only artifacts that actually published are in the revert\n // plan, so a partial publish reverts exactly what landed.\n let commit: { commitId: string } | null = null;\n if (published.length > 0) {\n const publishedKeys = new Set(published.map((p) => `${p.type}/${p.name}`));\n commit = await this.recordPackageCommit({\n orgId,\n packageId: request.packageId,\n operation: 'apply',\n ...(request.message ? { message: request.message } : {}),\n ...(request.actor ? { actor: request.actor } : {}),\n ...(request.aiModel ? { aiModel: request.aiModel } : {}),\n items: commitItems.filter((it) => publishedKeys.has(`${it.type}/${it.name}`)),\n ...(publishedSeqs.length\n ? { eventSeqStart: Math.min(...publishedSeqs), eventSeqEnd: Math.max(...publishedSeqs) }\n : {}),\n });\n }\n\n return {\n success: failed.length === 0 && published.length > 0,\n publishedCount: published.length,\n failedCount: failed.length,\n published,\n failed,\n ...(seedApplied ? { seedApplied } : {}),\n ...(probes ? { probes } : {}),\n ...(commit ? { commitId: commit.commitId } : {}),\n };\n }\n\n /**\n * Discard every pending DRAFT bound to a package — the NON-destructive\n * inverse of {@link publishPackageDrafts}. Drops only `state='draft'` rows\n * (via the per-item delete primitive), reverting the package to its last\n * published baseline; active/published metadata and physical tables are\n * left untouched.\n *\n * Use case: \"I edited this app for a while and it turned out worse than\n * before — abandon all my changes.\" Routes through the sys_metadata path\n * (no metadata-service dependency, unlike `POST /packages/:id/revert`).\n */\n async discardPackageDrafts(request: {\n packageId: string;\n organizationId?: string;\n actor?: string;\n }): Promise<{\n success: boolean;\n discardedCount: number;\n failedCount: number;\n discarded: Array<{ type: string; name: string }>;\n failed: Array<{ type: string; name: string; error: string; code?: string }>;\n }> {\n await this.ensureOverlayIndex();\n const orgId = request.organizationId ?? null;\n const repo = this.getOverlayRepo(orgId);\n const drafts = await repo.listDrafts({ packageId: request.packageId });\n\n const discarded: Array<{ type: string; name: string }> = [];\n const failed: Array<{ type: string; name: string; error: string; code?: string }> = [];\n\n for (const d of drafts) {\n try {\n await this.deleteMetaItem({\n type: d.type,\n name: d.name,\n state: 'draft',\n ...(request.organizationId ? { organizationId: request.organizationId } : {}),\n ...(request.actor ? { actor: request.actor } : {}),\n });\n discarded.push({ type: d.type, name: d.name });\n } catch (e: any) {\n failed.push({\n type: d.type,\n name: d.name,\n error: e?.message ?? 'discard failed',\n ...(e?.code ? { code: e.code } : {}),\n });\n }\n }\n\n return {\n success: failed.length === 0 && discarded.length > 0,\n discardedCount: discarded.length,\n failedCount: failed.length,\n discarded,\n failed,\n };\n }\n\n /**\n * Delete an ENTIRE package: every `sys_metadata` row bound to it (active\n * AND draft) and — by default — the physical table of each object it\n * defined. DESTRUCTIVE: removes the app and its data. Use case: \"I don't\n * want this package anymore.\"\n *\n * Set `keepData: true` to remove the metadata but preserve object tables.\n * The `sys_`-table guard in {@link deleteMetaItem} still applies, so\n * platform storage is never dropped. Drafts are removed before active rows\n * so each object's table is torn down once. Per-item failures are collected\n * without aborting the rest.\n */\n async deletePackage(request: {\n packageId: string;\n organizationId?: string;\n actor?: string;\n keepData?: boolean;\n }): Promise<{\n success: boolean;\n deletedCount: number;\n failedCount: number;\n deleted: Array<{ type: string; name: string; state: string }>;\n failed: Array<{ type: string; name: string; error: string; code?: string }>;\n }> {\n const where: Record<string, unknown> = { package_id: request.packageId };\n if (request.organizationId) where.organization_id = request.organizationId;\n const rows = (await this.engine.find('sys_metadata', { where })) as any[];\n\n const dropStorage = request.keepData !== true;\n // Delete drafts before active so an object's table is dropped once (on\n // the active delete), not pre-empted by a draft delete.\n const ordered = [...rows].sort((a, b) => (a.state === 'draft' ? 0 : 1) - (b.state === 'draft' ? 0 : 1));\n\n const deleted: Array<{ type: string; name: string; state: string }> = [];\n const failed: Array<{ type: string; name: string; error: string; code?: string }> = [];\n\n for (const row of ordered) {\n const state: 'active' | 'draft' = row.state === 'draft' ? 'draft' : 'active';\n try {\n await this.deleteMetaItem({\n type: row.type,\n name: row.name,\n state,\n ...(row.organization_id ? { organizationId: row.organization_id } : {}),\n ...(request.actor ? { actor: request.actor } : {}),\n ...(dropStorage ? { dropStorage: true } : {}),\n });\n deleted.push({ type: row.type, name: row.name, state });\n } catch (e: any) {\n failed.push({\n type: row.type,\n name: row.name,\n error: e?.message ?? 'delete failed',\n ...(e?.code ? { code: e.code } : {}),\n });\n }\n }\n\n return {\n success: failed.length === 0 && deleted.length > 0,\n deletedCount: deleted.length,\n failedCount: failed.length,\n deleted,\n failed,\n };\n }\n\n /**\n * ADR-0070 D4 — duplicate a writable base into a NEW package (the Airtable\n * \"duplicate base\" gesture). Clones every ACTIVE item the source owns into\n * `targetPackageId`, RE-NAMESPACING object names — the blueprint prefixes a\n * base's object names with its namespace (e.g. `iojn_repair_ticket`), and\n * `sys_metadata` keys on (type,name,org), so a same-name copy would collide\n * with the source — and rewriting every intra-package reference (lookup\n * `reference`, view `object`, expressions, etc.) to the new names. Per-item\n * best-effort; one failure never aborts the whole clone.\n */\n async duplicatePackage(request: {\n sourcePackageId: string;\n targetPackageId: string;\n targetName?: string;\n targetNamespace?: string;\n organizationId?: string;\n actor?: string;\n }): Promise<{\n success: boolean;\n copiedCount: number;\n failedCount: number;\n targetPackageId: string;\n copied: Array<{ type: string; name: string }>;\n failed: Array<{ type: string; name: string; error: string }>;\n }> {\n const registry: any = (this.engine as any).registry;\n const srcPkg = registry?.getPackage?.(request.sourcePackageId);\n const sourceNs: string =\n (srcPkg?.manifest?.namespace as string) ?? (request.sourcePackageId.split('.').pop() ?? '');\n const targetNs: string =\n request.targetNamespace ?? (request.targetPackageId.split('.').pop() ?? request.targetPackageId);\n\n const where: Record<string, unknown> = { package_id: request.sourcePackageId, state: 'active' };\n if (request.organizationId) where.organization_id = request.organizationId;\n const rows = (await this.engine.find('sys_metadata', { where })) as any[];\n\n // Map only OBJECT names that carry the source namespace prefix; views/etc.\n // are renamed by the same prefix swap and reference-rewritten via the map.\n const renameName = (name: string): string =>\n sourceNs && typeof name === 'string' && name.startsWith(`${sourceNs}_`)\n ? `${targetNs}_${name.slice(sourceNs.length + 1)}`\n : name;\n const renameMap = new Map<string, string>();\n for (const row of rows) {\n if (row?.type === 'object') {\n const nn = renameName(row.name);\n if (nn !== row.name) renameMap.set(row.name, nn);\n }\n }\n // Longest-first, identifier-boundary rewrite so `iojn_task` never corrupts\n // `iojn_task_log`, and `iojn_x` inside `record.iojn_x`/`iojn_x.view` matches.\n const olds = [...renameMap.keys()].sort((a, b) => b.length - a.length);\n const esc = (s: string) => s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const re = olds.length ? new RegExp(`(${olds.map(esc).join('|')})(?![A-Za-z0-9_])`, 'g') : null;\n const deepRewrite = (v: any): any => {\n if (typeof v === 'string') return re ? v.replace(re, (m) => renameMap.get(m) ?? m) : v;\n if (Array.isArray(v)) return v.map(deepRewrite);\n if (v && typeof v === 'object') {\n const o: any = {};\n for (const [k, val] of Object.entries(v)) o[k] = deepRewrite(val);\n return o;\n }\n return v;\n };\n\n if (srcPkg?.manifest && typeof registry?.installPackage === 'function') {\n try {\n registry.installPackage({\n ...srcPkg.manifest,\n id: request.targetPackageId,\n name: request.targetName ?? `${srcPkg.manifest.name ?? request.sourcePackageId} (copy)`,\n namespace: targetNs,\n });\n } catch {\n /* best-effort — the per-item package binding still works without a manifest row */\n }\n }\n\n const copied: Array<{ type: string; name: string }> = [];\n const failed: Array<{ type: string; name: string; error: string }> = [];\n for (const row of rows) {\n const newName = renameName(row.name);\n let item: any;\n try {\n item = typeof row.metadata === 'string' ? JSON.parse(row.metadata) : (row.metadata ?? {});\n } catch {\n failed.push({ type: row.type, name: row.name, error: 'unparseable metadata' });\n continue;\n }\n const rewritten = deepRewrite(item);\n if (rewritten && typeof rewritten === 'object' && !Array.isArray(rewritten)) rewritten.name = newName;\n try {\n await this.saveMetaItem({\n type: row.type,\n name: newName,\n item: rewritten,\n mode: 'publish',\n packageId: request.targetPackageId,\n ...(request.organizationId ? { organizationId: request.organizationId } : {}),\n ...(request.actor ? { actor: request.actor } : {}),\n });\n copied.push({ type: row.type, name: newName });\n } catch (e: any) {\n failed.push({ type: row.type, name: row.name, error: e?.message ?? 'copy failed' });\n }\n }\n return {\n success: failed.length === 0 && copied.length > 0,\n copiedCount: copied.length,\n failedCount: failed.length,\n targetPackageId: request.targetPackageId,\n copied,\n failed,\n };\n }\n\n /**\n * ADR-0070 D5 — adopt orphaned (package-less) metadata into a base. The\n * pre-package-first stopgaps left runtime-authored items with\n * `package_id = null` (or the `sys_metadata` sentinel). This bulk-rebinds\n * every such orphan to `targetPackageId` so the env converges on the\n * package-first model and the \"Local / Custom\" migration scope can be\n * retired. Owned rows (already bound to a real package) are left untouched.\n * Updates the durable column; the in-memory registry picks the new binding\n * up on the next metadata reload.\n */\n async reassignOrphanedMetadata(request: {\n targetPackageId: string;\n organizationId?: string;\n actor?: string;\n }): Promise<{\n success: boolean;\n reassignedCount: number;\n reassigned: Array<{ type: string; name: string }>;\n targetPackageId: string;\n }> {\n const where: Record<string, unknown> = {};\n if (request.organizationId) where.organization_id = request.organizationId;\n const rows = (await this.engine.find('sys_metadata', { where })) as any[];\n const orphans = rows.filter(\n (r) => r?.package_id == null || r.package_id === '' || r.package_id === 'sys_metadata',\n );\n\n const reassigned: Array<{ type: string; name: string }> = [];\n for (const row of orphans) {\n try {\n await this.engine.update(\n 'sys_metadata',\n { package_id: request.targetPackageId },\n { where: { id: row.id } },\n );\n reassigned.push({ type: row.type, name: row.name });\n } catch {\n /* skip a row that fails to update; report only what moved */\n }\n }\n return {\n success: reassigned.length > 0,\n reassignedCount: reassigned.length,\n reassigned,\n targetPackageId: request.targetPackageId,\n };\n }\n\n // ─────────────────────────────────────────────────────────────────────\n // ADR-0067 — package-scoped commit history & rollback\n // ─────────────────────────────────────────────────────────────────────\n\n /**\n * Record one commit row (best-effort) grouping a turn's published\n * artifacts. Returns the commit id, or null if the commit store is\n * unavailable (e.g. unit-test stubs) — recording never blocks a publish.\n */\n private async recordPackageCommit(args: {\n orgId: string | null;\n packageId: string;\n operation: 'apply' | 'revert';\n message?: string;\n actor?: string;\n aiModel?: string;\n parentCommitId?: string;\n items: Array<{ type: string; name: string; existedBefore: boolean; prevVersion: number | null }>;\n eventSeqStart?: number;\n eventSeqEnd?: number;\n }): Promise<{ commitId: string } | null> {\n try {\n const commitId = 'cmt_' + (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'\n ? crypto.randomUUID()\n : `${args.eventSeqEnd ?? 0}-${args.items.length}-${args.packageId}`);\n await this.engine.insert('sys_metadata_commit', {\n id: commitId,\n package_id: args.packageId,\n operation: args.operation,\n ...(args.message ? { message: args.message } : {}),\n ...(args.actor ? { actor: args.actor } : {}),\n ...(args.aiModel ? { ai_model: args.aiModel } : {}),\n ...(args.parentCommitId ? { parent_commit_id: args.parentCommitId } : {}),\n ...(args.eventSeqStart !== undefined ? { event_seq_start: args.eventSeqStart } : {}),\n ...(args.eventSeqEnd !== undefined ? { event_seq_end: args.eventSeqEnd } : {}),\n items: JSON.stringify(args.items),\n item_count: args.items.length,\n organization_id: args.orgId,\n created_at: new Date().toISOString(),\n });\n return { commitId };\n } catch {\n // Commit store unavailable (or insert raced) — the publish itself\n // already succeeded; grouping is a best-effort overlay on top.\n return null;\n }\n }\n\n private parseCommitItems(\n raw: unknown,\n ): Array<{ type: string; name: string; existedBefore: boolean; prevVersion: number | null }> {\n if (Array.isArray(raw)) return raw as Array<{ type: string; name: string; existedBefore: boolean; prevVersion: number | null }>;\n if (typeof raw === 'string') {\n try {\n const p = JSON.parse(raw);\n return Array.isArray(p) ? p : [];\n } catch {\n return [];\n }\n }\n return [];\n }\n\n /**\n * List the commit timeline for a package, newest-first (ADR-0067). Returns\n * [] if the commit store is unavailable.\n */\n async listCommits(request: {\n packageId: string;\n organizationId?: string;\n limit?: number;\n }): Promise<Array<{\n id: string;\n operation: 'apply' | 'revert';\n message?: string;\n actor?: string;\n aiModel?: string;\n parentCommitId?: string;\n itemCount: number;\n items: Array<{ type: string; name: string; existedBefore: boolean; prevVersion: number | null }>;\n createdAt?: string;\n }>> {\n try {\n const where: Record<string, unknown> = { package_id: request.packageId };\n if (request.organizationId) where.organization_id = request.organizationId;\n const rows = (await this.engine.find('sys_metadata_commit', {\n where,\n ...(request.limit ? { limit: request.limit } : {}),\n })) as any[];\n const mapped = rows.map((r) => ({\n id: r.id,\n operation: (r.operation ?? 'apply') as 'apply' | 'revert',\n ...(r.message ? { message: r.message } : {}),\n ...(r.actor ? { actor: r.actor } : {}),\n ...(r.ai_model ? { aiModel: r.ai_model } : {}),\n ...(r.parent_commit_id ? { parentCommitId: r.parent_commit_id } : {}),\n itemCount: typeof r.item_count === 'number' ? r.item_count : 0,\n items: this.parseCommitItems(r.items),\n ...(r.created_at ? { createdAt: r.created_at } : {}),\n }));\n // Newest-first; tolerate drivers that don't order by returning\n // insertion order, then sort by the ISO timestamp.\n mapped.sort((a, b) => String(b.createdAt ?? '').localeCompare(String(a.createdAt ?? '')));\n return mapped;\n } catch {\n return [];\n }\n }\n\n /**\n * Revert a single commit (ADR-0067): undo exactly the artifacts it touched.\n * A created-by-this-commit artifact is soft-removed (metadata row deleted;\n * the data table is NOT dropped — recoverable, per ADR-0067 §5); a modified\n * artifact is restored to its pre-commit `prevVersion`. The revert is itself\n * recorded as a NEW commit (operation='revert'), so history stays\n * append-only and the revert is itself revertible.\n */\n async revertCommit(request: {\n commitId: string;\n organizationId?: string;\n actor?: string;\n }): Promise<{\n success: boolean;\n revertedCount: number;\n failedCount: number;\n reverted: Array<{ type: string; name: string; action: 'removed' | 'restored' }>;\n failed: Array<{ type: string; name: string; error: string; code?: string }>;\n revertCommitId?: string;\n }> {\n await this.ensureOverlayIndex();\n const orgId = request.organizationId ?? null;\n const where: Record<string, unknown> = { id: request.commitId };\n if (request.organizationId) where.organization_id = request.organizationId;\n const row = (await this.engine.findOne('sys_metadata_commit', { where })) as any;\n if (!row) {\n const err: any = new Error(`[commit_not_found] No commit '${request.commitId}'.`);\n err.code = 'commit_not_found';\n err.status = 404;\n throw err;\n }\n const items = this.parseCommitItems(row.items);\n const repo = this.getOverlayRepo(orgId);\n const actor = request.actor ?? 'system';\n const reverted: Array<{ type: string; name: string; action: 'removed' | 'restored' }> = [];\n const failed: Array<{ type: string; name: string; error: string; code?: string }> = [];\n\n // Reverse apply order so artifacts that depend on others (e.g. a view on\n // a new object) are removed before the thing they reference.\n for (const it of [...items].reverse()) {\n const ref = { type: it.type, name: it.name, org: orgId ?? 'env' } as unknown as Parameters<typeof repo.get>[0];\n try {\n const current = await repo.get(ref, { state: 'active' });\n if (!it.existedBefore) {\n // Created by this commit → soft-remove (metadata only; table stays).\n if (current) {\n await repo.delete(ref, {\n parentVersion: current.hash,\n actor,\n source: 'protocol.revertCommit',\n intent: 'override-artifact',\n state: 'active',\n });\n }\n reverted.push({ type: it.type, name: it.name, action: 'removed' });\n } else if (it.prevVersion !== null && it.prevVersion !== undefined) {\n // Edited an existing artifact → restore the pre-commit body.\n await repo.restoreVersion(ref, it.prevVersion, {\n actor,\n source: 'protocol.revertCommit',\n message: `revert commit ${request.commitId}`,\n });\n reverted.push({ type: it.type, name: it.name, action: 'restored' });\n }\n } catch (e: any) {\n failed.push({\n type: it.type,\n name: it.name,\n error: e?.message ?? 'revert failed',\n ...(e?.code ? { code: e.code } : {}),\n });\n }\n }\n\n // Record the revert as its own commit (append-only history).\n const revertCommit = await this.recordPackageCommit({\n orgId,\n packageId: row.package_id,\n operation: 'revert',\n message: `Revert: ${row.message ?? request.commitId}`,\n ...(request.actor ? { actor: request.actor } : {}),\n parentCommitId: request.commitId,\n items: reverted.map((r) => ({\n type: r.type,\n name: r.name,\n existedBefore: r.action === 'restored',\n prevVersion: null,\n })),\n });\n\n return {\n success: failed.length === 0 && reverted.length > 0,\n revertedCount: reverted.length,\n failedCount: failed.length,\n reverted,\n failed,\n ...(revertCommit ? { revertCommitId: revertCommit.commitId } : {}),\n };\n }\n\n /**\n * Roll a package back THROUGH every `apply` commit newer than `commitId`\n * (newest first), leaving the package as it was at that commit. Each step is\n * an individual `revertCommit`, so the whole rollback is itself audited.\n */\n async rollbackToPackageCommit(request: {\n commitId: string;\n organizationId?: string;\n actor?: string;\n }): Promise<{\n success: boolean;\n revertedCommits: string[];\n failed: Array<{ commitId: string; error: string }>;\n }> {\n const where: Record<string, unknown> = { id: request.commitId };\n if (request.organizationId) where.organization_id = request.organizationId;\n const target = (await this.engine.findOne('sys_metadata_commit', { where })) as any;\n if (!target) {\n const err: any = new Error(`[commit_not_found] No commit '${request.commitId}'.`);\n err.code = 'commit_not_found';\n err.status = 404;\n throw err;\n }\n const all = await this.listCommits({\n packageId: target.package_id,\n ...(request.organizationId ? { organizationId: request.organizationId } : {}),\n });\n // listCommits is newest-first; revert every `apply` commit strictly newer\n // than the target (by created_at). Revert commits are skipped (their\n // effect is already captured by re-reverting the apply they undid).\n const targetCreatedAt = String(target.created_at ?? '');\n const toRevert = all.filter(\n (c) => String(c.createdAt ?? '') > targetCreatedAt && c.operation === 'apply',\n );\n const revertedCommits: string[] = [];\n const failed: Array<{ commitId: string; error: string }> = [];\n for (const c of toRevert) {\n try {\n await this.revertCommit({\n commitId: c.id,\n ...(request.organizationId ? { organizationId: request.organizationId } : {}),\n ...(request.actor ? { actor: request.actor } : {}),\n });\n revertedCommits.push(c.id);\n } catch (e: any) {\n failed.push({ commitId: c.id, error: e?.message ?? 'revert failed' });\n }\n }\n return { success: failed.length === 0, revertedCommits, failed };\n }\n\n /**\n * Restore the body recorded at history `toVersion` as the new\n * live row. Writes a history event with `op='revert'`. 404\n * (`[version_not_found]`) when the target version doesn't exist;\n * 409 (`[version_not_restorable]`) when the target is a delete\n * tombstone (no body to bring back).\n */\n async rollbackMetaItem(request: {\n type: string;\n name: string;\n toVersion: number;\n organizationId?: string;\n actor?: string;\n message?: string;\n }): Promise<{\n success: boolean;\n version: string;\n seq: number;\n restoredFromVersion: number;\n message?: string;\n }> {\n if (!Number.isFinite(request.toVersion) || request.toVersion < 1) {\n const err: any = new Error(\n `[invalid_request] rollbackMetaItem requires a positive integer 'toVersion' (got ${request.toVersion}).`,\n );\n err.code = 'invalid_request';\n err.status = 400;\n throw err;\n }\n const singularType = PLURAL_TO_SINGULAR[request.type] ?? request.type;\n if (!ObjectStackProtocolImplementation.isOverlayAllowed(singularType)\n && !ObjectStackProtocolImplementation.isRuntimeCreateAllowed(singularType)) {\n const err: any = new Error(\n `[not_overridable] Metadata type '${request.type}' is not revertable — no overlay/runtime-create permission.`,\n );\n err.code = 'not_overridable';\n err.status = 403;\n throw err;\n }\n // ADR-0010 L3 — lock blocks rollback (writes a new active row).\n const _rollbackLockErr = await this.assertLockAllowsWrite({\n type: request.type,\n name: request.name,\n ...(request.organizationId ? { organizationId: request.organizationId } : {}),\n operation: 'rollback',\n ...(request.actor ? { actor: request.actor } : {}),\n source: 'protocol.rollbackMetaItem',\n });\n if (_rollbackLockErr) throw _rollbackLockErr;\n await this.ensureOverlayIndex();\n const orgId = request.organizationId ?? null;\n const repo = this.getOverlayRepo(orgId);\n const artifactBacked = this.isArtifactBacked(singularType, request.name);\n const intent: 'override-artifact' | 'runtime-only' = artifactBacked\n ? 'override-artifact' : 'runtime-only';\n const ref = {\n type: singularType,\n name: request.name,\n org: orgId ?? 'env',\n } as Parameters<typeof repo.restoreVersion>[0];\n try {\n const result = await repo.restoreVersion(ref, request.toVersion, {\n actor: request.actor ?? 'system',\n source: 'protocol.rollbackMetaItem',\n ...(request.message ? { message: request.message } : {}),\n intent,\n });\n this.applyObjectRegistryMutation({\n type: request.type,\n name: request.name,\n item: result.item.body,\n });\n return {\n success: true,\n version: result.version,\n seq: result.seq,\n restoredFromVersion: request.toVersion,\n message: `Reverted to version ${request.toVersion} — type=${request.type}, name=${request.name} [seq=${result.seq}]`,\n };\n } catch (err: any) {\n if (err instanceof ConflictError) {\n const conflict: any = new Error(\n `[metadata_conflict] ${request.type}/${request.name} advanced during rollback. `\n + `Expected parent ${err.expectedParent ?? 'null'} but current is ${err.actualHead ?? 'null'}.`,\n );\n conflict.code = 'metadata_conflict';\n conflict.status = 409;\n conflict.expectedParent = err.expectedParent;\n conflict.actualHead = err.actualHead;\n throw conflict;\n }\n throw err;\n }\n }\n\n /**\n * Compute a shallow structural diff between two historical\n * versions of a metadata item. Either side may be omitted: when\n * `toVersion` is undefined the current active body is used; when\n * `fromVersion` is undefined the immediately previous history row\n * is used. Returns `{ added, removed, changed }` keyed by JSON\n * pointer-style paths for primitive leaves; nested objects/arrays\n * are reported as a single change record.\n */\n async diffMetaItem(request: {\n type: string;\n name: string;\n fromVersion?: number;\n toVersion?: number;\n organizationId?: string;\n }): Promise<{\n type: string;\n name: string;\n fromVersion: number | null;\n toVersion: number | null;\n added: Array<{ path: string; value: unknown }>;\n removed: Array<{ path: string; value: unknown }>;\n changed: Array<{ path: string; from: unknown; to: unknown }>;\n }> {\n const singularType = PLURAL_TO_SINGULAR[request.type] ?? request.type;\n const orgId = request.organizationId ?? null;\n const events = (await this.historyMetaItem({\n type: singularType,\n name: request.name,\n ...(orgId ? { organizationId: orgId } : {}),\n })).events;\n const versions = events\n .map((ev: any) => (ev as any).version as number | undefined)\n .filter((v): v is number => typeof v === 'number');\n // The `historyMetaItem` MetadataEvent shape doesn't carry the\n // per-(type,name) `version` directly — re-fetch via the repo\n // to read the underlying history rows with their version.\n const repo = this.getOverlayRepo(orgId);\n const fullRef = {\n type: singularType,\n name: request.name,\n org: orgId ?? 'env',\n } as { type: string; name: string; org: string };\n const histRows: Array<{ version: number; body: Record<string, unknown> | null }> = [];\n try {\n const engineAny = this.engine as any;\n const rows = await engineAny.find('sys_metadata_history', {\n where: {\n organization_id: orgId,\n type: singularType,\n name: request.name,\n },\n });\n rows.sort((a: any, b: any) => (a.version ?? 0) - (b.version ?? 0));\n for (const r of rows) {\n const body = r.metadata == null\n ? null\n : (typeof r.metadata === 'string' ? JSON.parse(r.metadata) : r.metadata);\n histRows.push({ version: r.version ?? 0, body });\n }\n } catch {\n // history table unavailable — fall through with empty list\n }\n const byVersion = new Map<number, Record<string, unknown> | null>();\n for (const r of histRows) byVersion.set(r.version, r.body);\n\n let fromBody: Record<string, unknown> | null = null;\n let toBody: Record<string, unknown> | null = null;\n let fromVersion: number | null = null;\n let toVersion: number | null = null;\n\n if (request.toVersion !== undefined) {\n toVersion = request.toVersion;\n toBody = byVersion.get(request.toVersion) ?? null;\n } else {\n const current = await repo.get(fullRef as any, { state: 'active' });\n toBody = current ? (current.body as Record<string, unknown>) : null;\n toVersion = histRows.length ? histRows[histRows.length - 1]!.version : null;\n }\n if (request.fromVersion !== undefined) {\n fromVersion = request.fromVersion;\n fromBody = byVersion.get(request.fromVersion) ?? null;\n } else if (toVersion !== null) {\n // Use the version immediately preceding `toVersion`\n const sorted = histRows.map((r) => r.version).filter((v) => v < toVersion!);\n if (sorted.length) {\n fromVersion = sorted[sorted.length - 1]!;\n fromBody = byVersion.get(fromVersion) ?? null;\n }\n }\n const diff = diffShallow(fromBody ?? {}, toBody ?? {});\n const _used = versions; void _used;\n return {\n type: request.type,\n name: request.name,\n fromVersion,\n toVersion,\n ...diff,\n };\n }\n\n /**\n * Remove a customization overlay row for the given metadata item, so the\n * next read falls through to the artifact-loaded default. Implements the\n * \"Reset to factory default\" semantic from ADR-0005. Whitelist is shared\n * with {@link saveMetaItem}.\n */\n async deleteMetaItem(request: {\n type: string;\n name: string;\n organizationId?: string;\n parentVersion?: string | null;\n actor?: string;\n state?: 'active' | 'draft';\n /**\n * When true, also drop the object's physical table after the metadata\n * is removed (object + active only; never `sys_`). Default false keeps\n * delete non-destructive to data. Used by the \"discard a previewed\n * object\" flow so a publish-to-preview leaves no orphan table.\n */\n dropStorage?: boolean;\n }): Promise<{\n success: boolean;\n message?: string;\n reset?: boolean;\n seq?: number;\n }> {\n // Two-tier authorization for delete (mirrors saveMetaItem).\n // • Artifact-backed item → delete becomes a tombstone overlay,\n // requires `allowOrgOverride`.\n // • DB-only item → hard delete of a user-created row,\n // requires `allowRuntimeCreate` (or `allowOrgOverride`).\n if (this.environmentId !== undefined) {\n const overlayAllowed = ObjectStackProtocolImplementation.isOverlayAllowed(request.type);\n const runtimeCreateAllowed = ObjectStackProtocolImplementation.isRuntimeCreateAllowed(request.type);\n const artifactBacked = this.isArtifactBacked(request.type, request.name);\n if (artifactBacked && !overlayAllowed) {\n const err = new Error(\n `[not_overridable] Metadata item '${request.type}/${request.name}' is provided by a code package `\n + `and the type has not opted into per-org overlay writes. `\n + `See docs/adr/0005-metadata-customization-overlay.md.`\n );\n (err as any).code = 'not_overridable';\n (err as any).status = 403;\n throw err;\n }\n if (!artifactBacked && !overlayAllowed && !runtimeCreateAllowed) {\n const err = new Error(\n `[not_creatable] Metadata type '${request.type}' does not allow runtime creation or deletion.`\n );\n (err as any).code = 'not_creatable';\n (err as any).status = 403;\n throw err;\n }\n\n // ADR-0010 L3 — lock blocks delete.\n const lockErr = await this.assertLockAllowsDelete({\n type: request.type,\n name: request.name,\n ...(request.organizationId ? { organizationId: request.organizationId } : {}),\n ...(request.actor ? { actor: request.actor } : {}),\n source: 'protocol.deleteMetaItem',\n });\n if (lockErr) throw lockErr;\n }\n\n const singularTypeForRepo = PLURAL_TO_SINGULAR[request.type] ?? request.type;\n const overlayAllowedForRepoDel = ObjectStackProtocolImplementation.isOverlayAllowed(singularTypeForRepo);\n const runtimeCreateAllowedForRepoDel = ObjectStackProtocolImplementation.isRuntimeCreateAllowed(singularTypeForRepo);\n const useRepoPath = overlayAllowedForRepoDel || runtimeCreateAllowedForRepoDel;\n\n // ADR-0008 — overlay-allowed types route through SysMetadataRepository\n // so the delete (a) is wrapped in engine.transaction(), (b) appends a\n // tombstone row to sys_metadata_history, and (c) emits a watch event\n // with a monotonic `seq` for HMR. Non-overlay-allowed types (only\n // reachable in control-plane bootstrap mode where environmentId is\n // undefined) take the legacy raw-engine path below — the repository's\n // `assertAllowed()` whitelist would 403 those deletes.\n if (useRepoPath) {\n const orgId = request.organizationId ?? null;\n const repo = this.getOverlayRepo(orgId);\n const ref = {\n type: singularTypeForRepo,\n name: request.name,\n org: orgId ?? 'env',\n } as Parameters<typeof repo.delete>[0];\n\n try {\n const targetState: 'active' | 'draft' = request.state === 'draft' ? 'draft' : 'active';\n // Probe first — \"no overlay exists\" is a success/no-op, not\n // a conflict. The repo would otherwise throw ConflictError.\n const current = await repo.get(ref, { state: targetState });\n if (!current) {\n // Self-heal: even with no overlay row, a stale runtime\n // shadow may linger in the registry (e.g. pollution from\n // before this fix shipped) — drop it so the artifact\n // view really IS the default we claim below.\n if (targetState === 'active') {\n await this.restoreArtifactRegistryView(request.type, request.name);\n }\n return {\n success: true,\n reset: false,\n message: targetState === 'draft'\n ? `No pending draft for ${request.type}/${request.name}.`\n : `No customization overlay found for ${request.type}/${request.name} — already at artifact default.`,\n };\n }\n\n // Last-write-wins parent resolution unless the caller pinned\n // an explicit version (Studio's \"Reset\" button is unpinned;\n // a future \"delete vN\" flow can pass parentVersion).\n const parentVersion: string = request.parentVersion !== undefined\n ? (request.parentVersion ?? current.hash)\n : current.hash;\n\n const result = await repo.delete(ref, {\n parentVersion,\n actor: request.actor ?? 'system',\n source: 'protocol.deleteMetaItem',\n intent: this.isArtifactBacked(singularTypeForRepo, request.name)\n ? 'override-artifact'\n : 'runtime-only',\n state: targetState,\n });\n\n // Heal the registry: drop the overlay's runtime shadow so the\n // packaged artifact is visible again (all kernels), and on\n // control-plane kernels also refresh from MetadataService —\n // see {@link restoreArtifactRegistryView}. Draft discards\n // skip this: drafts never hydrate into the registry, and the\n // still-active overlay (if any) must keep its shadow.\n if (targetState === 'active') {\n await this.restoreArtifactRegistryView(request.type, request.name);\n }\n\n // Storage teardown (opt-in): drop the now-orphaned physical table\n // for a discarded object so a publish-to-preview leaves no residue.\n if (this.shouldDropStorage(request.type, request.name, request.dropStorage, targetState)) {\n await this.dropObjectStorage(singularTypeForRepo, request.name);\n }\n\n // ADR-0010 — success audit (best-effort).\n await this.recordMetadataAudit({\n type: request.type,\n name: request.name,\n organizationId: orgId,\n operation: 'delete',\n outcome: 'allowed',\n code: 'ok',\n ...(request.actor ? { actor: request.actor } : {}),\n source: 'protocol.deleteMetaItem',\n note: targetState,\n });\n\n return {\n success: true,\n reset: true,\n seq: result.seq,\n message: (request.state === 'draft')\n ? `Draft discarded — ${request.type}/${request.name}. [seq=${result.seq}]`\n : `Customization overlay deleted — ${request.type}/${request.name} reset to artifact default. [seq=${result.seq}]`,\n };\n } catch (err: any) {\n if (err instanceof ConflictError) {\n const conflict = new Error(\n `[metadata_conflict] ${request.type}/${request.name} has been modified since you loaded it. `\n + `Expected parent ${err.expectedParent ?? 'null'} but current is ${err.actualHead ?? 'null'}.`,\n );\n (conflict as any).code = 'metadata_conflict';\n (conflict as any).status = 409;\n (conflict as any).expectedParent = err.expectedParent;\n (conflict as any).actualHead = err.actualHead;\n throw conflict;\n }\n const e = new Error(`Failed to delete customization overlay: ${err.message ?? err}`);\n (e as any).status = err?.status ?? 500;\n throw e;\n }\n }\n\n // ── Legacy raw-engine path: only reachable in control-plane bootstrap\n // (environmentId === undefined) for non-overlay-allowed types like\n // `object`, `flow`, `agent`. No history row, no watch event — these\n // types don't participate in the change-log model.\n const scopedWhere: Record<string, unknown> = {\n type: request.type,\n name: request.name,\n organization_id: request.organizationId ?? null,\n };\n\n try {\n const existing = await this.engine.findOne('sys_metadata', { where: scopedWhere });\n if (!existing) {\n return {\n success: true,\n reset: false,\n message: `No customization overlay found for ${request.type}/${request.name} — already at artifact default.`,\n };\n }\n await this.engine.delete('sys_metadata', { where: { id: existing.id } });\n\n // Storage teardown (opt-in) — see the repo-path branch above.\n {\n const targetState: 'active' | 'draft' = request.state === 'draft' ? 'draft' : 'active';\n if (this.shouldDropStorage(request.type, request.name, request.dropStorage, targetState)) {\n await this.dropObjectStorage(PLURAL_TO_SINGULAR[request.type] ?? request.type, request.name);\n }\n }\n\n if (request.state !== 'draft') {\n await this.restoreArtifactRegistryView(request.type, request.name);\n }\n\n return {\n success: true,\n reset: true,\n message: `Customization overlay deleted — ${request.type}/${request.name} reset to artifact default.`,\n };\n } catch (err: any) {\n const e = new Error(`Failed to delete customization overlay: ${err.message}`);\n (e as any).status = 500;\n throw e;\n }\n }\n\n /**\n * Hydrate SchemaRegistry from the database on startup.\n * Loads all active metadata records and registers them in the in-memory registry.\n * Safe to call repeatedly — idempotent (latest DB record wins).\n *\n * Per ADR-0005, project-kernel mode ALSO hydrates from sys_metadata —\n * customization overlay rows must survive restart. Scope filter\n * (`environment_id = this.environmentId ?? null`) keeps tenants isolated.\n */\n async loadMetaFromDb(): Promise<{ loaded: number; errors: number }> {\n let loaded = 0;\n let errors = 0;\n try {\n // ADR-0005 (revised 2026-05): hydrate only env-wide rows\n // (organization_id IS NULL). Per-org overlays are loaded on\n // demand by getMetaItem to avoid cross-org leakage into the\n // process-wide SchemaRegistry.\n const where: Record<string, unknown> = {\n state: 'active',\n organization_id: null,\n };\n const records = await this.engine.find('sys_metadata', { where });\n for (const record of records) {\n try {\n const data = typeof record.metadata === 'string'\n ? JSON.parse(record.metadata)\n : record.metadata;\n // Normalize DB type to singular (DB may store legacy plural forms)\n const normalizedType = PLURAL_TO_SINGULAR[record.type] ?? record.type;\n if (normalizedType === 'object') {\n this.engine.registry.registerObject(data as any, record.packageId || 'sys_metadata');\n } else {\n // Same envelope graft as the getMetaItems hydration:\n // the plain-key entry shadows any packaged artifact,\n // so carry the artifact's `_lock`/`_packageId`/\n // `_provenance` along (ADR-0010 §3.3). When artifacts\n // load after this hydration the merge finds nothing\n // and the row registers unchanged — same as before.\n const artifact = this.lookupArtifactItem(normalizedType, (data as any)?.name);\n this.engine.registry.registerItem(\n normalizedType,\n mergeArtifactProtection(data, artifact) as any,\n 'name' as any,\n );\n }\n loaded++;\n } catch (e) {\n errors++;\n console.warn(`[Protocol] Failed to hydrate ${record.type}/${record.name}: ${e instanceof Error ? e.message : String(e)}`);\n }\n }\n } catch (e: any) {\n // \"no such table\" is expected on first run before migrations execute — not an error.\n if (!/no such table/i.test(e.message ?? '')) {\n console.warn(`[Protocol] DB hydration skipped: ${e.message}`);\n }\n }\n return { loaded, errors };\n }\n\n // ==========================================\n // Metadata References (Phase 3a-references)\n // ==========================================\n\n /**\n * Scan all loaded metadata for references pointing at the given\n * `{type, name}` target. Returns one row per referring artifact with\n * the path that produced the hit, so the admin UI can render an\n * \"Used by\" panel before destructive actions (rename / delete /\n * type-narrowing).\n *\n * Coverage is driven by the hand-curated {@link REFERENCE_PATHS}\n * registry. Types not present in the registry simply return no hits\n * — the engine never throws.\n */\n async findReferencesToMeta(request: {\n type: string;\n name: string;\n organizationId?: string;\n }): Promise<{\n references: Array<{\n type: string;\n name: string;\n label?: string;\n path: string;\n kind: string;\n }>;\n }> {\n const singularTarget = PLURAL_TO_SINGULAR[request.type] ?? request.type;\n const targetName = request.name;\n const matchers = REFERENCE_PATHS[singularTarget];\n if (!matchers || matchers.length === 0) {\n return { references: [] };\n }\n\n const seen = new Set<string>(); // dedup key: `${fromType}|${itemName}|${path}`\n const out: Array<{ type: string; name: string; label?: string; path: string; kind: string }> = [];\n\n // Walk distinct source types in parallel.\n await Promise.all(\n matchers.map(async (matcher) => {\n let items: unknown[] = [];\n try {\n const result = await this.getMetaItems({\n type: matcher.fromType,\n ...(request.organizationId ? { organizationId: request.organizationId } : {}),\n });\n items = (result?.items ?? []) as unknown[];\n } catch {\n return;\n }\n for (const raw of items) {\n if (!raw || typeof raw !== 'object') continue;\n const sourceName = (raw as any).name as string | undefined;\n if (!sourceName) continue;\n // Don't list an item as a reference to itself unless the\n // self-reference is meaningful (e.g. object→field path).\n const isSelfReference = matcher.fromType === singularTarget && sourceName === targetName;\n for (const path of matcher.paths) {\n const values = extractPathValues(raw, path);\n if (!values.includes(targetName)) continue;\n if (isSelfReference && !path.includes('[]') && !path.includes('{}')) continue;\n const key = `${matcher.fromType}|${sourceName}|${path}`;\n if (seen.has(key)) continue;\n seen.add(key);\n const label = (raw as any).label as string | undefined;\n out.push({\n type: matcher.fromType,\n name: sourceName,\n ...(label ? { label } : {}),\n path,\n kind: matcher.kind,\n });\n }\n }\n }),\n );\n\n // Stable sort: by type, then by name.\n out.sort((a, b) => a.type.localeCompare(b.type) || a.name.localeCompare(b.name));\n\n return { references: out };\n }\n\n // ==========================================\n // Feed Operations\n // ==========================================\n\n async listFeed(request: any): Promise<any> {\n const svc = this.requireFeedService();\n const result = await svc.listFeed({\n object: request.object,\n recordId: request.recordId,\n filter: request.type,\n limit: request.limit,\n cursor: request.cursor,\n });\n return { success: true, data: result };\n }\n\n async createFeedItem(request: any): Promise<any> {\n const svc = this.requireFeedService();\n const item = await svc.createFeedItem({\n object: request.object,\n recordId: request.recordId,\n type: request.type,\n actor: { type: 'user', id: 'current_user' },\n body: request.body,\n mentions: request.mentions,\n parentId: request.parentId,\n visibility: request.visibility,\n });\n return { success: true, data: item };\n }\n\n async updateFeedItem(request: any): Promise<any> {\n const svc = this.requireFeedService();\n const item = await svc.updateFeedItem(request.feedId, {\n body: request.body,\n mentions: request.mentions,\n visibility: request.visibility,\n });\n return { success: true, data: item };\n }\n\n async deleteFeedItem(request: any): Promise<any> {\n const svc = this.requireFeedService();\n await svc.deleteFeedItem(request.feedId);\n return { success: true, data: { feedId: request.feedId } };\n }\n\n async addReaction(request: any): Promise<any> {\n const svc = this.requireFeedService();\n const reactions = await svc.addReaction(request.feedId, request.emoji, 'current_user');\n return { success: true, data: { reactions } };\n }\n\n async removeReaction(request: any): Promise<any> {\n const svc = this.requireFeedService();\n const reactions = await svc.removeReaction(request.feedId, request.emoji, 'current_user');\n return { success: true, data: { reactions } };\n }\n\n async pinFeedItem(request: any): Promise<any> {\n const svc = this.requireFeedService();\n const item = await svc.getFeedItem(request.feedId);\n if (!item) throw new Error(`Feed item ${request.feedId} not found`);\n // IFeedService doesn't have dedicated pin/unpin — use updateFeedItem to persist pin state\n await svc.updateFeedItem(request.feedId, { visibility: item.visibility });\n return { success: true, data: { feedId: request.feedId, pinned: true, pinnedAt: new Date().toISOString() } };\n }\n\n async unpinFeedItem(request: any): Promise<any> {\n const svc = this.requireFeedService();\n const item = await svc.getFeedItem(request.feedId);\n if (!item) throw new Error(`Feed item ${request.feedId} not found`);\n await svc.updateFeedItem(request.feedId, { visibility: item.visibility });\n return { success: true, data: { feedId: request.feedId, pinned: false } };\n }\n\n async starFeedItem(request: any): Promise<any> {\n const svc = this.requireFeedService();\n const item = await svc.getFeedItem(request.feedId);\n if (!item) throw new Error(`Feed item ${request.feedId} not found`);\n // IFeedService doesn't have dedicated star/unstar — verify item exists then return state\n await svc.updateFeedItem(request.feedId, { visibility: item.visibility });\n return { success: true, data: { feedId: request.feedId, starred: true, starredAt: new Date().toISOString() } };\n }\n\n async unstarFeedItem(request: any): Promise<any> {\n const svc = this.requireFeedService();\n const item = await svc.getFeedItem(request.feedId);\n if (!item) throw new Error(`Feed item ${request.feedId} not found`);\n await svc.updateFeedItem(request.feedId, { visibility: item.visibility });\n return { success: true, data: { feedId: request.feedId, starred: false } };\n }\n\n async searchFeed(request: any): Promise<any> {\n const svc = this.requireFeedService();\n // Search delegates to listFeed with filter since IFeedService doesn't have a dedicated search\n const result = await svc.listFeed({\n object: request.object,\n recordId: request.recordId,\n filter: request.type,\n limit: request.limit,\n cursor: request.cursor,\n });\n // Filter by query text in body\n const queryLower = (request.query || '').toLowerCase();\n const filtered = result.items.filter((item: any) =>\n item.body?.toLowerCase().includes(queryLower)\n );\n return { success: true, data: { items: filtered, total: filtered.length, hasMore: false } };\n }\n\n async getChangelog(request: any): Promise<any> {\n const svc = this.requireFeedService();\n // Changelog retrieves field_change type feed items\n const result = await svc.listFeed({\n object: request.object,\n recordId: request.recordId,\n filter: 'changes_only',\n limit: request.limit,\n cursor: request.cursor,\n });\n const entries = result.items.map((item: any) => ({\n id: item.id,\n object: item.object,\n recordId: item.recordId,\n actor: item.actor,\n changes: item.changes || [],\n timestamp: item.createdAt,\n source: item.source,\n }));\n return { success: true, data: { entries, total: result.total, nextCursor: result.nextCursor, hasMore: result.hasMore } };\n }\n\n async feedSubscribe(request: any): Promise<any> {\n const svc = this.requireFeedService();\n const subscription = await svc.subscribe({\n object: request.object,\n recordId: request.recordId,\n userId: 'current_user',\n events: request.events,\n channels: request.channels,\n });\n return { success: true, data: subscription };\n }\n\n async feedUnsubscribe(request: any): Promise<any> {\n const svc = this.requireFeedService();\n const unsubscribed = await svc.unsubscribe(request.object, request.recordId, 'current_user');\n return { success: true, data: { object: request.object, recordId: request.recordId, unsubscribed } };\n }\n\n /**\n * Install a package from a manifest — the single canonical write primitive\n * for the package subsystem (ADR-0033 consolidation).\n *\n * It writes BOTH stores that the runtime keeps for packages, so a package\n * surfaces consistently no matter which read path is used:\n * 1. the in-memory `SchemaRegistry` (what the dispatcher's\n * `/api/v1/packages` list/detail and `getMetaItems({type:'package'})`\n * read — i.e. what Studio's package selector shows), and\n * 2. the durable `sys_packages` table via the optional `package` service\n * (so the package survives a restart; that service re-hydrates these\n * rows back into the registry on boot).\n *\n * The DB write is best-effort and non-fatal: when the `package` service is\n * absent (e.g. the `marketplace` capability is off) the package is still\n * registered in-memory and visible for the lifetime of the process.\n */\n async installPackage(request: InstallPackageRequest): Promise<InstallPackageResponse> {\n const manifest = request.manifest;\n const pkg = this.engine.registry.installPackage(manifest as any, request.settings);\n\n // Best-effort durable persistence to `sys_packages`.\n try {\n const services = this.getServicesRegistry?.();\n const pkgSvc = services?.get('package') as\n | { publish?: (data: { manifest: unknown; metadata: unknown }) => Promise<unknown> }\n | undefined;\n if (pkgSvc?.publish && (manifest as any)?.version) {\n await pkgSvc.publish({ manifest, metadata: {} });\n }\n } catch (e) {\n // Non-fatal: registry write already succeeded; log and continue.\n console.warn(\n `[protocol.installPackage] sys_packages persist skipped for '${(manifest as any)?.id}': ${(e as Error)?.message}`,\n );\n }\n\n return { package: pkg as any, message: `Installed package: ${(manifest as any)?.id}` };\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * ADR-0008 PR-10b — `SysMetadataRepository`.\n *\n * Wraps the existing `sys_metadata` table behind the canonical\n * `MetadataRepository` interface. Implements the *single-row update*\n * semantics that ADR-0005 already ships — append-only event-log\n * persistence is M1 work.\n *\n * What this layer DOES (M0 + M1):\n * - get / put / delete / list against `sys_metadata`\n * - tenancy scope = `organization_id` (per-org overlays only;\n * project/branch concepts removed — see ADR-0008 §0 amendment)\n * - hash stamping with `hashSpec` (PR-10a guarantees stability)\n * - watch() implemented via an in-memory event broadcaster fed by\n * every successful put/delete on THIS instance\n * - whitelist enforcement: refuses to persist types whose registry\n * entry has `allowOrgOverride: false` (Prime Directive #8)\n * - **M1**: every successful put/delete appends a durable row to\n * `sys_metadata_history` inside the same engine.transaction() as the\n * parent `sys_metadata` write. No-op puts (identical hash) skip the\n * history write. Failed optimistic-lock checks abort before any\n * write reaches the database.\n * - **M1**: history() yields events from the durable log, ordered by\n * per-(org,type,name) `version` ASC.\n *\n * What this layer does NOT do (and will not, by design):\n * - cross-replica push notifications (LISTEN/NOTIFY, pub/sub, etc.).\n * The watch() contract is scoped to the local repository instance.\n * Multi-replica deployments are not a supported topology for the\n * metadata overlay — see ADR-0008 §11.\n * - hashSpec backfill for legacy rows missing `checksum`\n *\n * Schema mapping (ADR-0008 PR-10d.2):\n * Repository concept sys_metadata column\n * ─────────────────────── ───────────────────\n * body → metadata (JSON string)\n * hash (sha256) → checksum (text(64))\n * monotonic version int → version (number)\n * org isolation → organization_id (lookup)\n * actor → updated_by (lookup, optional)\n *\n * Composition: PR-10c will compose\n * `LayeredRepository([FileSystemRepository, SysMetadataRepository])`\n * and the manager bridge will route reads through that. Until then this\n * file is intentionally NOT wired into any production path — it has its\n * own test surface so we can build confidence before flipping the\n * switch.\n */\n\nimport { hashSpec, ConflictError } from '@objectstack/metadata-core';\nimport { readEnvWithDeprecation } from '@objectstack/types';\nimport type {\n MetadataRepository,\n MetaRef,\n MetadataItem,\n MetadataItemHeader,\n MetadataEvent,\n MetadataWriteIntent,\n PutOptions,\n PutResult,\n DeleteOptions,\n DeleteResult,\n ListFilter,\n WatchFilter,\n HistoryOptions,\n} from '@objectstack/metadata-core';\nimport { DEFAULT_METADATA_TYPE_REGISTRY } from '@objectstack/spec/kernel';\nimport { PLURAL_TO_SINGULAR, SINGULAR_TO_PLURAL } from '@objectstack/spec/shared';\n\n/**\n * Overlay-row lifecycle state.\n *\n * - `'active'` → the published, live overlay. `getMetaItem` (the\n * default read path) and runtime loaders observe this row.\n * - `'draft'` → an unpublished pending change. Lives alongside the\n * active row (one of each per `(org,type,name)`). Promoted to\n * `active` via {@link SysMetadataRepository.promoteDraft}.\n *\n * Other lifecycle values defined on `sys_metadata.state` (`'archived'`,\n * `'deprecated'`) are not yet plumbed through the overlay write path;\n * they remain reserved for future flows (item retirement, freeze).\n */\nexport type OverlayState = 'active' | 'draft';\n\n/**\n * Extended history operation tag. The base `'create' | 'update' |\n * 'delete'` operations are emitted by the canonical put/delete paths.\n * `'publish'` is recorded when a draft is promoted, `'revert'` when a\n * historical version is restored. Both are surfaced as MetadataEvent\n * `.op` values via `history()`.\n */\nexport type ExtendedOperation = 'create' | 'update' | 'publish' | 'revert' | 'delete';\n\n/**\n * Sub-set of the ObjectQL engine shape we depend on. Kept narrow so\n * tests can stub it with a plain mock. Mirrors the real engine's\n * `options.context` pattern so transactions can thread through.\n */\nexport interface SysMetadataEngine {\n find(\n table: string,\n options: { where: Record<string, unknown>; limit?: number; orderBy?: any; context?: any },\n ): Promise<any[]>;\n findOne(\n table: string,\n options: { where: Record<string, unknown>; context?: any },\n ): Promise<any | null>;\n insert(\n table: string,\n data: Record<string, unknown>,\n options?: { context?: any },\n ): Promise<{ id: string }>;\n update(\n table: string,\n data: Record<string, unknown>,\n options: { where: Record<string, unknown>; context?: any },\n ): Promise<{ id: string }>;\n delete(\n table: string,\n options: { where: Record<string, unknown>; context?: any },\n ): Promise<{ deleted: number }>;\n /**\n * Optional. Falls through to direct callback invocation if the\n * underlying driver lacks ACID support (matches the real\n * `ObjectQL.transaction` semantics). Repository code must not rely on\n * rollback for correctness against in-memory drivers.\n */\n transaction?<T>(callback: (trxCtx: any) => Promise<T>, baseContext?: any): Promise<T>;\n}\n\nexport interface SysMetadataRepositoryOptions {\n engine: SysMetadataEngine;\n /**\n * Tenancy scope. `null` writes to env-wide overlay rows; a string\n * scopes to one organization (the supported shared-DB tenant model\n * — see ADR-0005 amendment).\n */\n organizationId?: string | null;\n /** Org label embedded in returned MetaRefs. Defaults to organizationId or `\"system\"`. */\n orgLabel?: string;\n}\n\n/** Derived from registry — single source of truth (Prime Directive #8). */\nconst OVERLAY_ALLOWED_TYPES: ReadonlySet<string> = new Set(\n DEFAULT_METADATA_TYPE_REGISTRY\n .filter((e) => e.allowOrgOverride)\n .map((e) => e.type),\n);\n\n/**\n * Types that opt into runtime creation of brand-new items (two-tier\n * model — ADR-0005 extension). These items live only in `sys_metadata`;\n * there is no artifact backing them and `allowOrgOverride` need not be\n * granted on the type. Used by `assertAllowed()` when called with\n * `intent: 'runtime-only'` — a signal from the protocol layer that it\n * has already verified the absence of an artifact-shadowing collision.\n */\n/**\n * Set of type names that have an *explicit* entry in the static registry.\n * Anything outside this set is a runtime-registered type (e.g. plugin-\n * provided `theme`, `api`, `connector`, …) — the listing endpoint\n * (`getMetaTypes()` in protocol.ts) synthesises a descriptor with\n * `allowRuntimeCreate: true` for those, so the write gate must agree.\n */\nconst STATIC_REGISTRY_TYPES: ReadonlySet<string> = new Set(\n DEFAULT_METADATA_TYPE_REGISTRY.map((e) => e.type),\n);\n\nconst RUNTIME_CREATE_ALLOWED_TYPES: ReadonlySet<string> = new Set(\n DEFAULT_METADATA_TYPE_REGISTRY\n .filter((e) => e.allowRuntimeCreate)\n .map((e) => e.type),\n);\n\n/**\n * Phase 3a-env-writable: parse `OS_METADATA_WRITABLE` (comma-\n * separated singular type names). Memoised; tests can reset via\n * {@link resetEnvWritableMetadataTypes}. Mirrors the same helper in\n * ObjectStackProtocolImplementation — both gates must consult the same\n * elevated set so the env-var escape hatch is applied consistently\n * regardless of which write path a caller takes.\n */\nlet _envWritableMetadataTypes: Set<string> | null = null;\nfunction envWritableMetadataTypes(): ReadonlySet<string> {\n if (_envWritableMetadataTypes !== null) return _envWritableMetadataTypes;\n const raw = readEnvWithDeprecation('OS_METADATA_WRITABLE', []) || '';\n const set = new Set<string>();\n for (const tok of raw.split(',')) {\n const t = tok.trim();\n if (!t) continue;\n const singular = PLURAL_TO_SINGULAR[t] ?? t;\n set.add(singular);\n const plural = SINGULAR_TO_PLURAL[singular];\n if (plural) set.add(plural);\n }\n _envWritableMetadataTypes = set;\n return set;\n}\n\n/** Test hook — clear the memoised env-writable cache. */\nexport function resetEnvWritableMetadataTypes(): void {\n _envWritableMetadataTypes = null;\n}\n\nexport class SysMetadataRepository implements MetadataRepository {\n private readonly engine: SysMetadataEngine;\n private readonly organizationId: string | null;\n private readonly orgLabel: string;\n\n /**\n * Local seq counter for in-memory watch() event broadcasts. Mirrors\n * the durable `event_seq` we write into `sys_metadata_history` on\n * each successful put/delete — assigned AFTER the transaction commits\n * so we never broadcast events that got rolled back.\n */\n private seqCounter = 0;\n private readonly watchers = new Set<(evt: MetadataEvent) => void>();\n private closed = false;\n\n /** Table name for the durable event log. */\n private readonly historyTable = 'sys_metadata_history';\n\n constructor(opts: SysMetadataRepositoryOptions) {\n this.engine = opts.engine;\n this.organizationId = opts.organizationId ?? null;\n this.orgLabel = opts.orgLabel ?? (opts.organizationId ?? 'system');\n }\n\n /**\n * Run `cb` inside `engine.transaction(...)` if the engine supports it,\n * otherwise fall through to a direct call. Matches the real\n * `ObjectQL.transaction` semantics — in-memory drivers (and our test\n * fakes) get no rollback, which is acceptable because production\n * always runs on a SQL driver with real ACID.\n */\n private async withTxn<T>(cb: (ctx: any) => Promise<T>): Promise<T> {\n if (typeof this.engine.transaction === 'function') {\n return this.engine.transaction(cb);\n }\n return cb(undefined);\n }\n\n /**\n * Read the current overlay row. Returns null if no row exists —\n * callers (e.g. LayeredRepository) fall through to lower layers.\n *\n * `opts.state` selects which lifecycle row to read: defaults to the\n * live published row (`'active'`). Pass `'draft'` to read the pending\n * unpublished revision (if any).\n */\n async get(\n ref: MetaRef,\n opts?: { state?: OverlayState; packageId?: string | null },\n ): Promise<MetadataItem | null> {\n this.assertOpen();\n const state = opts?.state ?? 'active';\n // ADR-0048 — when a package scope is supplied, resolve the row owned by\n // that package (used by saveMetaItem to read the correct parent-version\n // lineage before an upsert). Omitted → legacy \"any package\" match.\n const row = await this.engine.findOne('sys_metadata', {\n where: this.whereFor(ref, state, opts && 'packageId' in opts ? (opts.packageId ?? null) : undefined),\n });\n if (!row) return null;\n return this.rowToItem(ref, row);\n }\n\n /**\n * Resolve a historical version by content hash (ADR-0009).\n *\n * Looks up `sys_metadata_history` by `(organization_id, type, name,\n * checksum)`. Returns null if no row matches. `executionPinned` types\n * are guaranteed to find their body here because history GC skips\n * them.\n */\n async getByHash(ref: MetaRef, hash: string): Promise<MetadataItem | null> {\n this.assertOpen();\n const full = this.fullRef(ref);\n const row = await this.engine.findOne(this.historyTable, {\n where: {\n organization_id: this.organizationId,\n type: full.type,\n name: full.name,\n checksum: hash,\n },\n });\n if (!row) return null;\n const rawBody = (row as any).metadata;\n if (rawBody === null || rawBody === undefined) {\n // Tombstone — body is gone, do not resurrect.\n return null;\n }\n const body =\n typeof rawBody === 'string' ? JSON.parse(rawBody) : (rawBody as Record<string, unknown>);\n return {\n ref: { ...full, version: undefined },\n body: body as Record<string, unknown>,\n hash,\n parentHash: (row as any).previous_checksum ?? null,\n authoredBy: (row as any).recorded_by ?? 'unknown',\n authoredAt: (row as any).recorded_at ?? new Date(0).toISOString(),\n message: (row as any).change_note ?? undefined,\n seq: ((row as any).event_seq as number) ?? 0,\n };\n }\n\n async put(\n ref: MetaRef,\n spec: unknown,\n opts: PutOptions & { state?: OverlayState; opType?: ExtendedOperation },\n ): Promise<PutResult> {\n this.assertOpen();\n this.assertAllowed(ref.type, opts.intent);\n\n const state: OverlayState = opts.state ?? 'active';\n const body = (spec ?? {}) as Record<string, unknown>;\n const hash = hashSpec(body);\n\n // Run all reads + writes inside one transaction so the optimistic\n // lock, the parent-row mutation, and the history append are atomic.\n const result = await this.withTxn(async (ctx) => {\n // ADR-0048 — scope the existing-row lookup to the requested package so a\n // save for package B does not find (and overwrite) package A's same-name\n // overlay. A package-less save (packageId null) targets the global row.\n const existing = await this.engine.findOne('sys_metadata', {\n where: this.whereFor(ref, state, opts.packageId ?? null),\n context: ctx,\n });\n const existingHash: string | null = existing?.checksum ?? null;\n if (opts.parentVersion !== existingHash) {\n throw new ConflictError(this.fullRef(ref), opts.parentVersion, existingHash);\n }\n\n // No-op short-circuit: identical body → no write, no history row,\n // no event. We re-yield the existing item so callers see the\n // canonical hash but the seqCounter is unchanged.\n if (existing && existingHash === hash) {\n const item = this.rowToItem(ref, existing);\n return { skipped: true as const, version: hash, seq: item.seq, item };\n }\n\n const now = new Date().toISOString();\n const baseOp: 'create' | 'update' = existing ? 'update' : 'create';\n const op: ExtendedOperation = opts.opType ?? baseOp;\n\n // Per-(org,type,name) lineage counter. Use MAX from history so\n // delete+recreate continues incrementing instead of restarting\n // at 1 (which the prior `sys_metadata.version` semantics did).\n const version = await this.nextItemVersion(ref, ctx);\n // Per-org monotonic event log cursor.\n const eventSeq = await this.nextEventSeq(ctx);\n\n const parentRowData: Record<string, unknown> = {\n type: ref.type,\n name: ref.name,\n organization_id: this.organizationId,\n metadata: JSON.stringify(body),\n checksum: hash,\n state,\n version,\n updated_at: now,\n };\n // Software-package binding (Studio package authoring workspace).\n // Create: stamp with the requested package (or null). Update: preserve\n // an existing non-null binding so an edit made with a different package\n // selected never silently re-binds the row; only fill a null binding.\n if (existing) {\n const existingPkg = (existing as { package_id?: string | null }).package_id ?? null;\n parentRowData.package_id = existingPkg ?? opts.packageId ?? null;\n } else {\n parentRowData.package_id = opts.packageId ?? null;\n }\n if (existing) {\n const existingId = (existing as { id?: string }).id;\n if (existingId === undefined) {\n throw new Error(\n `SysMetadataRepository.put: existing row for ${ref.type}/${ref.name} has no id column`,\n );\n }\n await this.engine.update('sys_metadata', parentRowData, {\n where: { id: existingId },\n context: ctx,\n });\n } else {\n parentRowData.created_at = now;\n await this.engine.insert('sys_metadata', parentRowData, { context: ctx });\n }\n\n // Durable history append — same transaction, so the parent write\n // and the audit row commit together or roll back together.\n await this.engine.insert(\n this.historyTable,\n {\n id: this.uuid(),\n event_seq: eventSeq,\n type: ref.type,\n name: ref.name,\n version,\n operation_type: op,\n metadata: JSON.stringify(body),\n checksum: hash,\n previous_checksum: existingHash,\n change_note: opts.message,\n source: opts.source ?? 'sys-metadata-repo',\n organization_id: this.organizationId,\n recorded_by: opts.actor,\n recorded_at: now,\n },\n { context: ctx },\n );\n\n const item: MetadataItem = {\n ref: this.fullRef(ref),\n body,\n hash,\n parentHash: existingHash,\n authoredBy: opts.actor,\n authoredAt: now,\n message: opts.message,\n seq: eventSeq,\n };\n\n return {\n skipped: false as const,\n version: hash,\n seq: eventSeq,\n item,\n op,\n existingHash,\n now,\n source: opts.source ?? 'sys-metadata-repo',\n message: opts.message,\n actor: opts.actor,\n };\n });\n\n if (result.skipped) {\n return { version: result.version, seq: result.seq, item: result.item };\n }\n\n // Broadcast AFTER commit. seqCounter tracks the durable event_seq\n // so watch() consumers and history() consumers see the same cursor.\n this.seqCounter = result.seq;\n // Drafts are explicitly NOT broadcast — the watch() stream models\n // the live overlay surface. A draft is a private staging buffer\n // until `promoteDraft()` records a `publish` event. Subscribers\n // (cache layers, HMR clients) should not react to drafts.\n if (state === 'active') {\n this.broadcast({\n seq: result.seq,\n op: result.op,\n ref: this.fullRef(ref),\n hash: result.version,\n parentHash: result.existingHash,\n actor: result.actor,\n message: result.message,\n ts: result.now,\n source: result.source,\n });\n }\n\n return { version: result.version, seq: result.seq, item: result.item };\n }\n\n async delete(\n ref: MetaRef,\n opts: DeleteOptions & { state?: OverlayState },\n ): Promise<DeleteResult> {\n this.assertOpen();\n this.assertAllowed(ref.type, opts.intent);\n\n const state: OverlayState = opts.state ?? 'active';\n const result = await this.withTxn(async (ctx) => {\n const existing = await this.engine.findOne('sys_metadata', {\n where: this.whereFor(ref, state),\n context: ctx,\n });\n if (!existing) {\n throw new ConflictError(this.fullRef(ref), opts.parentVersion, null);\n }\n const existingHash: string | null = existing.checksum ?? null;\n if (opts.parentVersion !== existingHash) {\n throw new ConflictError(this.fullRef(ref), opts.parentVersion, existingHash);\n }\n\n const existingId = (existing as { id?: string }).id;\n if (existingId === undefined) {\n throw new Error(\n `SysMetadataRepository.delete: existing row for ${ref.type}/${ref.name} has no id column`,\n );\n }\n\n const now = new Date().toISOString();\n // Draft deletions are a private buffer flush — they don't get a\n // history event (no audit value, and no parent for replay). Only\n // active-row deletes write a tombstone.\n let version = 0;\n let eventSeq = 0;\n if (state === 'active') {\n version = await this.nextItemVersion(ref, ctx);\n eventSeq = await this.nextEventSeq(ctx);\n }\n\n await this.engine.delete('sys_metadata', {\n where: { id: existingId },\n context: ctx,\n });\n\n if (state === 'active') {\n // Tombstone row — metadata/checksum are intentionally null.\n // Identity is preserved via (organization_id, type, name, version);\n // the parent row's id is not retained.\n await this.engine.insert(\n this.historyTable,\n {\n id: this.uuid(),\n event_seq: eventSeq,\n type: ref.type,\n name: ref.name,\n version,\n operation_type: 'delete',\n metadata: null,\n checksum: null,\n previous_checksum: existingHash,\n change_note: opts.message,\n source: opts.source ?? 'sys-metadata-repo',\n organization_id: this.organizationId,\n recorded_by: opts.actor,\n recorded_at: now,\n },\n { context: ctx },\n );\n }\n\n return {\n eventSeq,\n existingHash,\n now,\n source: opts.source ?? 'sys-metadata-repo',\n message: opts.message,\n actor: opts.actor,\n };\n });\n\n if (state === 'active') {\n this.seqCounter = result.eventSeq;\n this.broadcast({\n seq: result.eventSeq,\n op: 'delete',\n ref: this.fullRef(ref),\n hash: null,\n parentHash: result.existingHash,\n actor: result.actor,\n message: result.message,\n ts: result.now,\n source: result.source,\n });\n }\n\n return { seq: result.eventSeq };\n }\n\n /**\n * Promote the pending draft row for `ref` into the live (`active`)\n * overlay. Atomic: reads the draft inside the same transaction, runs\n * the canonical `put` to upsert the active row (which appends a\n * history event with `operation_type='publish'`), then deletes the\n * draft row.\n *\n * Errors if no draft exists (callers should 404). The active row's\n * `parentVersion` is computed from the current active hash so this\n * also surfaces optimistic-lock conflicts when something else has\n * published in between (e.g. another admin reverted to an older\n * version since the draft was authored).\n */\n async promoteDraft(\n ref: MetaRef,\n opts: { actor: string; source?: string; message?: string; intent?: MetadataWriteIntent },\n ): Promise<{ version: string; seq: number; item: MetadataItem }> {\n this.assertOpen();\n // Read the RAW draft row (not just the body) so the promotion can carry\n // the draft's package binding onto the active row. ADR-0048 keys overlay\n // rows by `(org, type, name, package_id)`; promoteDraft historically\n // called put() WITHOUT a packageId, so the freshly-created active row\n // landed unbound (`package_id = NULL`). That silently broke every\n // package-scoped reader — most visibly the ADR-0045 publish visibility\n // flip (`getMetaItems({ type:'app', packageId })` → unhide), which then\n // never matched the just-published app and left AI-built apps `hidden`\n // (invisible in the app switcher / home) forever.\n const draftRow = await this.engine.findOne('sys_metadata', {\n where: this.whereFor(ref, 'draft'),\n });\n if (!draftRow) {\n const err: any = new Error(\n `[no_draft] No pending draft exists for ${ref.type}/${ref.name} — nothing to publish.`,\n );\n err.code = 'no_draft';\n err.status = 404;\n throw err;\n }\n const draftPackageId = (draftRow as { package_id?: string | null }).package_id ?? null;\n const draft = this.rowToItem(ref, draftRow);\n // Read the active row through the SAME package scope we will write, so the\n // optimistic-lock `parentVersion` matches the exact row `put` upserts.\n // (Package-less drafts → packageId null → identical to the prior behaviour.)\n const currentActive = await this.get(ref, { state: 'active', packageId: draftPackageId });\n const result = await this.put(ref, draft.body, {\n parentVersion: currentActive?.hash ?? null,\n actor: opts.actor,\n source: opts.source ?? 'sys-metadata-repo.publish',\n message: opts.message ?? `publish draft (hash ${draft.hash})`,\n intent: opts.intent ?? 'override-artifact',\n state: 'active',\n opType: 'publish',\n packageId: draftPackageId,\n });\n // Drop the draft row — it has been promoted. Tolerate races where\n // a second publisher already drained it.\n try {\n await this.delete(ref, {\n parentVersion: draft.hash,\n actor: opts.actor,\n source: opts.source ?? 'sys-metadata-repo.publish',\n intent: opts.intent ?? 'override-artifact',\n state: 'draft',\n });\n } catch {\n // best-effort: a concurrent publisher may have already drained\n // the draft; the active row's authoritative content is intact.\n }\n return result;\n }\n\n /**\n * Restore the body recorded in history at `targetVersion` (per-org\n * lineage counter) as the new active row. Writes a history event\n * with `operation_type='revert'` so the audit trail captures the\n * intent. Does NOT touch any draft row.\n *\n * Throws `[version_not_found]` (404) if the target version row is\n * missing or is a delete tombstone (no body to restore).\n */\n async restoreVersion(\n ref: MetaRef,\n targetVersion: number,\n opts: { actor: string; source?: string; message?: string; intent?: MetadataWriteIntent },\n ): Promise<{ version: string; seq: number; item: MetadataItem }> {\n this.assertOpen();\n const full = this.fullRef(ref);\n const row = await this.engine.findOne(this.historyTable, {\n where: {\n organization_id: this.organizationId,\n type: full.type,\n name: full.name,\n version: targetVersion,\n },\n });\n if (!row) {\n const err: any = new Error(\n `[version_not_found] No history row at version ${targetVersion} for ${ref.type}/${ref.name}.`,\n );\n err.code = 'version_not_found';\n err.status = 404;\n throw err;\n }\n const raw = (row as any).metadata;\n if (raw === null || raw === undefined) {\n const err: any = new Error(\n `[version_not_restorable] Version ${targetVersion} for ${ref.type}/${ref.name} is a delete tombstone — nothing to restore.`,\n );\n err.code = 'version_not_restorable';\n err.status = 409;\n throw err;\n }\n const body = typeof raw === 'string' ? JSON.parse(raw) : (raw as Record<string, unknown>);\n const currentActive = await this.get(ref, { state: 'active' });\n return this.put(ref, body, {\n parentVersion: currentActive?.hash ?? null,\n actor: opts.actor,\n source: opts.source ?? 'sys-metadata-repo.revert',\n message: opts.message ?? `revert to version ${targetVersion}`,\n intent: opts.intent ?? 'override-artifact',\n state: 'active',\n opType: 'revert',\n });\n }\n\n async *list(filter: ListFilter): AsyncIterable<MetadataItemHeader> {\n this.assertOpen();\n const where: Record<string, unknown> = {\n organization_id: this.organizationId,\n state: 'active',\n };\n if (filter.type) where.type = filter.type;\n const rows = await this.engine.find('sys_metadata', {\n where,\n limit: filter.limit,\n });\n for (const row of rows) {\n if (filter.nameContains && !String(row.name).includes(filter.nameContains)) continue;\n const item = this.rowToItem(\n { ...this.fullRef({ type: row.type, name: row.name } as MetaRef) },\n row,\n );\n // Strip body for the header projection.\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { body, ...header } = item;\n yield header;\n }\n }\n\n /**\n * List pending DRAFT rows (ADR-0033) for this org, optionally narrowed by\n * `type` and/or `packageId`. Unlike {@link list} (which is hard-scoped to\n * `state='active'`), this reads `state='draft'` so the console can surface\n * what an AI authored but a human hasn't published yet. Returns a light\n * header projection (no body) suitable for a \"pending changes\" list.\n */\n async listDrafts(filter?: {\n type?: string;\n packageId?: string;\n }): Promise<\n Array<{\n type: string;\n name: string;\n packageId: string | null;\n updatedAt: string | null;\n updatedBy: string | null;\n }>\n > {\n this.assertOpen();\n const where: Record<string, unknown> = { state: 'draft' };\n // Surface BOTH org-scoped drafts and env-wide (`organization_id IS NULL`)\n // drafts. Env-wide drafts are real pending changes — `getMetaItems`/preview\n // overlays them and `publish-drafts` promotes them — but a strict\n // `organization_id = this.organizationId` equality silently dropped them\n // whenever the active org was non-null. AI-authored metadata is written\n // env-wide, so its drafts vanished from the pending-changes list and the\n // Publish CTA never appeared: the change showed in preview yet could not be\n // published from the UI (the \"orphaned draft\" bug).\n if (this.organizationId != null) {\n where.$or = [\n { organization_id: this.organizationId },\n { organization_id: null },\n ];\n } else {\n where.organization_id = null;\n }\n if (filter?.type) where.type = filter.type;\n if (filter?.packageId) where.package_id = filter.packageId;\n const rows = await this.engine.find('sys_metadata', { where });\n return (rows as any[]).map((row) => ({\n type: row.type,\n name: row.name,\n packageId: row.package_id ?? null,\n updatedAt: row.updated_at ?? row.created_at ?? null,\n updatedBy: row.updated_by ?? row.created_by ?? null,\n }));\n }\n\n /**\n * Yield every history event for `(org, type?, name?)` from the\n * durable log, ordered by per-(type,name) `version` ascending. When\n * `filter.type`/`filter.name` are unset the consumer gets the full\n * org-scoped event stream — still ordered by version within each\n * (type,name) bucket, then by `recorded_at` across buckets (we sort\n * client-side because the test engine doesn't honor `orderBy`).\n */\n async *history(ref: MetaRef, opts?: HistoryOptions): AsyncIterable<MetadataEvent> {\n this.assertOpen();\n const full = this.fullRef(ref);\n const where: Record<string, unknown> = {\n organization_id: this.organizationId,\n type: full.type,\n name: full.name,\n };\n const rows = await this.engine.find(this.historyTable, { where });\n rows.sort((a: any, b: any) => {\n const va = typeof a.event_seq === 'number' ? a.event_seq : 0;\n const vb = typeof b.event_seq === 'number' ? b.event_seq : 0;\n return va - vb;\n });\n let yielded = 0;\n for (const row of rows) {\n if (opts?.sinceSeq !== undefined && (row.event_seq ?? 0) <= opts.sinceSeq) continue;\n if (opts?.limit !== undefined && yielded >= opts.limit) break;\n yielded++;\n yield {\n seq: (row.event_seq as number) ?? 0,\n op: (row.operation_type as MetadataEvent['op']) ?? 'update',\n ref: full,\n hash: (row.checksum as string | null) ?? null,\n parentHash: (row.previous_checksum as string | null) ?? null,\n version: typeof row.version === 'number' ? row.version : undefined,\n actor: (row.recorded_by as string | undefined) ?? 'unknown',\n message: (row.change_note as string | undefined) ?? undefined,\n ts: (row.recorded_at as string) ?? new Date(0).toISOString(),\n source: (row.source as string | undefined) ?? 'sys-metadata-repo',\n };\n }\n }\n\n /**\n * Live event stream. Fires for every successful put/delete on THIS\n * instance — cross-replica fan-out is M1. Manual AsyncIterator (not\n * an async generator) so we can deterministically tear down via\n * `iter.return()`, matching the pattern used by InMemoryRepository.\n */\n watch(filter: WatchFilter, since?: number): AsyncIterable<MetadataEvent> {\n const self = this;\n return {\n [Symbol.asyncIterator]: () => {\n const queue: MetadataEvent[] = [];\n let pendingResolve: ((r: IteratorResult<MetadataEvent>) => void) | null = null;\n let stopped = false;\n\n const dispatch = (evt: MetadataEvent) => {\n if (stopped) return;\n if (!self.matchesFilter(evt, filter)) return;\n if (since !== undefined && evt.seq <= since) return;\n if (pendingResolve) {\n const r = pendingResolve;\n pendingResolve = null;\n r({ value: evt, done: false });\n } else {\n queue.push(evt);\n }\n };\n self.watchers.add(dispatch);\n\n return {\n next(): Promise<IteratorResult<MetadataEvent>> {\n if (stopped) return Promise.resolve({ value: undefined as any, done: true });\n const buffered = queue.shift();\n if (buffered) return Promise.resolve({ value: buffered, done: false });\n return new Promise((resolve) => {\n pendingResolve = resolve;\n });\n },\n return(): Promise<IteratorResult<MetadataEvent>> {\n stopped = true;\n self.watchers.delete(dispatch);\n if (pendingResolve) {\n const r = pendingResolve;\n pendingResolve = null;\n r({ value: undefined as any, done: true });\n }\n return Promise.resolve({ value: undefined as any, done: true });\n },\n };\n },\n };\n }\n\n /** Shut down all watch iterators. */\n close(): void {\n this.closed = true;\n // Drain watchers — each one's `return()` removes itself.\n const snapshot = Array.from(this.watchers);\n for (const w of snapshot) {\n try {\n w({\n seq: -1,\n op: 'delete',\n ref: { org: '', type: 'view', name: '_close' } as MetaRef,\n hash: null,\n parentHash: null,\n actor: 'system',\n ts: new Date().toISOString(),\n source: 'sys-metadata-repo-close',\n });\n } catch { /* noop */ }\n }\n this.watchers.clear();\n }\n\n // ── helpers ─────────────────────────────────────────────────────────\n\n private assertOpen(): void {\n if (this.closed) throw new Error('SysMetadataRepository is closed');\n }\n\n /**\n * Defense-in-depth authorization gate.\n *\n * `intent` defaults to `'override-artifact'` (the historical strict\n * behavior). The protocol layer passes `'runtime-only'` after it has\n * verified — via the schema registry — that no artifact item exists\n * at `(type, name)`. In that case we accept types with\n * `allowRuntimeCreate: true`, even when `allowOrgOverride` is false.\n *\n * The env-var escape hatch (`OS_METADATA_WRITABLE`) still\n * applies to BOTH intents, so operators can opt into artifact\n * overrides at runtime for emergency fixes.\n */\n private assertAllowed(type: string, intent: MetadataWriteIntent = 'override-artifact'): void {\n const singular = PLURAL_TO_SINGULAR[type] ?? type;\n const allowedByRegistry = OVERLAY_ALLOWED_TYPES.has(singular) || OVERLAY_ALLOWED_TYPES.has(type);\n if (allowedByRegistry) return;\n\n // Two-tier extension: runtime-only writes target a brand-new\n // (artifact-free) item, so they only need `allowRuntimeCreate`.\n // Two cases qualify:\n // 1. Type is statically registered with `allowRuntimeCreate: true`.\n // 2. Type has NO static registry entry — it was added at runtime\n // by a plugin (e.g. `theme`, `api`, `connector`). The listing\n // endpoint synthesises `allowRuntimeCreate: true` for these,\n // so the write gate must accept them too. Otherwise the UI\n // would advertise a writable type that 403s on save.\n if (intent === 'runtime-only') {\n if (RUNTIME_CREATE_ALLOWED_TYPES.has(singular) || RUNTIME_CREATE_ALLOWED_TYPES.has(type)) {\n return;\n }\n if (!STATIC_REGISTRY_TYPES.has(singular) && !STATIC_REGISTRY_TYPES.has(type)) {\n return;\n }\n }\n\n // Phase 3a-env-writable: env-var escape hatch.\n const env = envWritableMetadataTypes();\n if (env.has(singular) || env.has(type)) return;\n\n const allowed = [\n ...OVERLAY_ALLOWED_TYPES,\n ...envWritableMetadataTypes(),\n ];\n const code = intent === 'runtime-only' ? 'not_creatable' : 'not_overridable';\n const detail = intent === 'runtime-only'\n ? `'${type}' has neither allowOrgOverride nor allowRuntimeCreate in the registry. `\n : `'${type}' is not allowOrgOverride in the registry. `;\n const err: any = new Error(\n `[${code}] ${detail}` +\n `Overlay-allowed: ${Array.from(new Set(allowed)).join(', ') || '(none)'}. ` +\n `Set OS_METADATA_WRITABLE to enable additional types at runtime.`,\n );\n err.code = code;\n err.status = 403;\n throw err;\n }\n\n private whereFor(\n ref: Pick<MetaRef, 'type' | 'name'>,\n state: OverlayState = 'active',\n packageId?: string | null,\n ): Record<string, unknown> {\n const where: Record<string, unknown> = {\n type: ref.type,\n name: ref.name,\n organization_id: this.organizationId,\n state,\n };\n // ADR-0048 — when the caller scopes by package, the overlay row is keyed by\n // `(org, type, name, package_id)` so two installed packages shipping the\n // same name each get their OWN customization row (a package-less / global\n // overlay uses `package_id IS NULL`). When `packageId` is omitted (legacy\n // callers — delete/promote/restore), the package dimension is left out so\n // the query keeps its historical \"match any package\" behaviour.\n if (packageId !== undefined) where.package_id = packageId; // string → eq; null → IS NULL\n return where;\n }\n\n private fullRef(ref: Pick<MetaRef, 'type' | 'name'>): MetaRef {\n return {\n org: this.orgLabel,\n type: ref.type,\n name: ref.name,\n };\n }\n\n private rowToItem(ref: Pick<MetaRef, 'type' | 'name'>, row: any): MetadataItem {\n const body: Record<string, unknown> =\n typeof row.metadata === 'string' ? JSON.parse(row.metadata) : (row.metadata ?? {});\n const hash: string = row.checksum ?? hashSpec(body);\n return {\n ref: this.fullRef(ref),\n body,\n hash,\n parentHash: null,\n authoredBy: row.updated_by ?? row.created_by ?? 'unknown',\n authoredAt: row.updated_at ?? row.created_at ?? new Date().toISOString(),\n message: undefined,\n seq: this.seqCounter,\n };\n }\n\n private broadcast(evt: MetadataEvent): void {\n for (const w of Array.from(this.watchers)) {\n try { w(evt); } catch { /* listener errors don't break the repo */ }\n }\n }\n\n private matchesFilter(evt: MetadataEvent, filter: WatchFilter): boolean {\n if (filter.type && evt.ref.type !== filter.type) return false;\n if (filter.name && evt.ref.name !== filter.name) return false;\n if (filter.org && evt.ref.org !== filter.org) return false;\n return true;\n }\n\n /**\n * Per-org monotonic event sequence. Reads `MAX(event_seq) + 1` from\n * `sys_metadata_history` scoped by `organization_id`. MUST be called\n * inside a transaction (the only caller is the put/delete txn body) —\n * concurrent writers in the same org race otherwise.\n */\n private async nextEventSeq(ctx: any): Promise<number> {\n try {\n const rows = await this.engine.find(this.historyTable, {\n where: { organization_id: this.organizationId },\n context: ctx,\n });\n let max = 0;\n for (const row of rows as Array<{ event_seq?: number | null }>) {\n const v = typeof row.event_seq === 'number' ? row.event_seq : 0;\n if (v > max) max = v;\n }\n return max + 1;\n } catch {\n // Table not provisioned yet (fresh DB) — start at 1.\n return 1;\n }\n }\n\n /**\n * Per-(org,type,name) lineage counter. Reads from history (not from\n * `sys_metadata.version`) so delete + recreate continues incrementing\n * instead of restarting at 1.\n */\n private async nextItemVersion(\n ref: Pick<MetaRef, 'type' | 'name'>,\n ctx: any,\n ): Promise<number> {\n try {\n const rows = await this.engine.find(this.historyTable, {\n where: {\n organization_id: this.organizationId,\n type: ref.type,\n name: ref.name,\n },\n context: ctx,\n });\n let max = 0;\n for (const row of rows as Array<{ version?: number | null }>) {\n const v = typeof row.version === 'number' ? row.version : 0;\n if (v > max) max = v;\n }\n return max + 1;\n } catch {\n return 1;\n }\n }\n\n /** Lightweight UUID-ish id for history rows; sufficient for an audit log. */\n private uuid(): string {\n if (typeof globalThis.crypto?.randomUUID === 'function') {\n return globalThis.crypto.randomUUID();\n }\n return `evt_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Load-time metadata diagnostics.\n *\n * Decorates metadata documents read from `getMetaItems()` /\n * `getMetaItem()` with a `_diagnostics` envelope so Studio (and any\n * other consumer) can render validity badges, inline field errors, and\n * governance dashboards without having to re-implement spec validation\n * on the client.\n *\n * Single source of truth: the same {@link getMetadataTypeSchema} that\n * the save path (`protocol.saveMetaItem` →\n * `resolveOverlaySchema().safeParse()`) and the JSON-Schema emitter\n * (`getMetaTypes() → entries[].schema`) already consult. Adding a new\n * metadata type's Zod schema in one place automatically wires it up\n * for read-time diagnostics, write-time validation, **and** Studio's\n * form renderer.\n *\n * Wire shape (`_diagnostics`) intentionally mirrors the existing\n * {@link MetadataValidationResult} type from\n * `@objectstack/spec/kernel` so consumers can share one type alias\n * across the validate / write / read surfaces.\n */\n\nimport type { z } from 'zod';\nimport { getMetadataTypeSchema } from '@objectstack/spec/kernel';\nimport type { MetadataValidationResult } from '@objectstack/spec/kernel';\nimport { PLURAL_TO_SINGULAR } from '@objectstack/spec/shared';\n\n/**\n * Re-export the canonical validation-result type so callers in this\n * package don't need to dual-import from `@objectstack/spec/kernel`.\n */\nexport type MetadataDiagnostics = MetadataValidationResult;\n\n/**\n * Compute spec diagnostics for a single metadata document.\n *\n * Returns `undefined` when the type has no registered Zod schema\n * (`function` / `service` / `router`, or any plugin type that has not\n * called `registerMetadataTypeSchema()`). Callers MUST treat that as\n * \"no opinion\" — not as \"valid\" — and either skip decoration entirely\n * or surface a `validatable: false` flag if their UI cares.\n */\nexport function computeMetadataDiagnostics(\n type: string,\n item: unknown,\n): MetadataDiagnostics | undefined {\n const singular = PLURAL_TO_SINGULAR[type] ?? type;\n const schema = getMetadataTypeSchema(singular);\n if (!schema) return undefined;\n\n if (item === null || item === undefined || typeof item !== 'object') {\n return {\n valid: false,\n errors: [{\n path: '',\n message: 'Metadata document must be a non-null object',\n code: 'invalid_type',\n }],\n };\n }\n\n // Strip our own decoration before re-validating so it never becomes\n // a false-positive \"unrecognized_keys\" failure on schemas that grow\n // a `.strict()` mode in the future.\n const candidate = '_diagnostics' in (item as Record<string, unknown>)\n ? stripDiagnostics(item as Record<string, unknown>)\n : item;\n\n const parsed = (schema as z.ZodTypeAny).safeParse(candidate);\n if (parsed.success) {\n return { valid: true };\n }\n\n const errors = parsed.error.issues.map((issue) => ({\n path: issue.path.map(String).join('.'),\n message: issue.message,\n code: issue.code as string,\n }));\n\n return { valid: false, errors };\n}\n\nfunction stripDiagnostics(item: Record<string, unknown>): Record<string, unknown> {\n const { _diagnostics: _drop, ...rest } = item;\n void _drop;\n return rest;\n}\n\n/**\n * Attach `_diagnostics` to a single metadata item. Returns the item\n * unchanged when no diagnostics could be computed (unknown type) or\n * when the input is not an object.\n *\n * The returned reference is always a shallow copy when decoration\n * occurs — callers must not assume identity equality with the input.\n */\nexport function decorateMetadataItem<T>(type: string, item: T): T {\n if (!item || typeof item !== 'object') return item;\n const diagnostics = computeMetadataDiagnostics(type, item);\n if (!diagnostics) return item;\n return { ...(item as Record<string, unknown>), _diagnostics: diagnostics } as T;\n}\n\n/**\n * Decorate an array of metadata items. Non-array inputs and non-object\n * elements are returned unchanged, preserving the upstream defensive\n * \"items may be a wrapped or naked array\" contract documented in\n * `rest-server.ts`.\n */\nexport function decorateMetadataItems<T>(type: string, items: T[]): T[] {\n if (!Array.isArray(items)) return items;\n return items.map((item) => decorateMetadataItem(type, item));\n}\n\n// ---------------------------------------------------------------------------\n// ADR-0047 — reference-integrity diagnostics for list views\n// ---------------------------------------------------------------------------\n\n/** Minimal object-definition shape the reference checker needs. */\ninterface ObjectDefLike {\n fields?: Record<string, { type?: string }> | Array<{ name: string; type?: string }>;\n}\n\nfunction fieldMap(objectDef: ObjectDefLike): Map<string, { type?: string }> {\n const map = new Map<string, { type?: string }>();\n const fields = objectDef?.fields;\n if (Array.isArray(fields)) {\n for (const f of fields) if (f?.name) map.set(f.name, f);\n } else if (fields && typeof fields === 'object') {\n for (const [name, f] of Object.entries(fields)) map.set(name, f ?? {});\n }\n return map;\n}\n\n/**\n * Cross-document reference checks Zod cannot express: every field a list\n * view's user-facing filter surface points at must exist on the source\n * object, and binding-dependent visualizations must have resolvable\n * bindings (kanban → select-like `groupByField`).\n *\n * Pure function — callers (read decoration, the ADR-0033 AI apply loop)\n * supply the already-resolved object definition. Returns `{ valid: true }`\n * when every reference resolves; errors use the same wire shape as\n * {@link computeMetadataDiagnostics} so consumers can merge the two.\n *\n * Spec-shape validation stays in `computeMetadataDiagnostics`; this only\n * covers what a schema alone cannot see.\n */\nexport function computeViewReferenceDiagnostics(\n view: Record<string, unknown>,\n objectDef: ObjectDefLike,\n): MetadataDiagnostics {\n const fields = fieldMap(objectDef);\n const errors: NonNullable<MetadataDiagnostics['errors']> = [];\n const requireField = (name: unknown, path: string) => {\n if (typeof name !== 'string' || !name) return;\n if (!fields.has(name)) {\n errors.push({\n path,\n message: `Field \"${name}\" does not exist on the source object`,\n code: 'reference_not_found',\n });\n }\n };\n\n const userFilters = view?.userFilters as\n | { fields?: Array<{ field?: string }>; tabs?: Array<{ filter?: Array<{ field?: string }> }> }\n | undefined;\n userFilters?.fields?.forEach((f, i) => requireField(f?.field, `userFilters.fields.${i}.field`));\n userFilters?.tabs?.forEach((t, i) =>\n t?.filter?.forEach((r, j) => requireField(r?.field, `userFilters.tabs.${i}.filter.${j}.field`)));\n\n (view?.tabs as Array<{ filter?: Array<{ field?: string }> }> | undefined)?.forEach((t, i) =>\n t?.filter?.forEach((r, j) => requireField(r?.field, `tabs.${i}.filter.${j}.field`)));\n\n (view?.filterableFields as string[] | undefined)?.forEach((f, i) =>\n requireField(f, `filterableFields.${i}`));\n\n const kanban = view?.kanban as { groupByField?: string } | undefined;\n if (kanban?.groupByField) {\n requireField(kanban.groupByField, 'kanban.groupByField');\n const def = fields.get(kanban.groupByField);\n if (def && def.type && !['select', 'multi-select', 'boolean', 'lookup', 'master_detail', 'user'].includes(def.type)) {\n errors.push({\n path: 'kanban.groupByField',\n message: `Field \"${kanban.groupByField}\" (type \"${def.type}\") cannot group a kanban — use a select-like field`,\n code: 'invalid_binding',\n });\n }\n }\n\n return errors.length ? { valid: false, errors } : { valid: true };\n}\n"]}
|