@ai-matrx/alchemy 0.19.1 → 0.20.4

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/CHANGELOG.md CHANGED
@@ -1,5 +1,43 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.20.4
4
+ ### Publish 0.20.3's sync fix (the release was held by a stale test)
5
+
6
+ - 0.20.3 (never published): `checks/sync` gives an item-type value the value contract columns, so a surface with item types syncs.
7
+ - `table-copy.test.tsx` asserts design-system 0.72.4's contract: the row window labels each field by its column's own name (`Name`), never the raw key (`name`). It asserted the retired raw-key label, which failed the 0.20.3 publish. No other code change.
8
+
9
+ **Consumer action:** none.
10
+
11
+ ## 0.20.2
12
+ ### `documentToPdf` is `captureDocumentPdf` (first published form)
13
+
14
+ - Renamed so it can never be mistaken for print's document-tree engine (`print/document` `documentToPdf`, which `check-operate-sizes.mjs` refuses in the capture entry by that exact name — it held 0.20.1). Same behaviour.
15
+
16
+ **Consumer action:** call `captureDocumentPdf` from `@ai-matrx/alchemy/operate/capture`.
17
+
18
+ ## 0.20.1
19
+ ### Publish 0.20.0's document capture (the release was held by a stale test)
20
+
21
+ - `table-default-selection.test.tsx` now asserts design-system 0.72.1's contract: the default copy selection is kept across source pages of the same table (it asserted the retired "cleared on page change" behaviour, which failed every alchemy publish since 0.19.2). No code change.
22
+
23
+ **Consumer action:** none (adopt `captureDocumentPdf`).
24
+
25
+ ## 0.20.0
26
+ ### `/operate/capture` `documentToPdf`; the rasteriser ruling corrected (AP-2 fifth check)
27
+
28
+ - `captureDocumentPdf(element | elements, options)` (named `documentToPdf` in the unpublished 0.20.0/0.20.1) answers a DOCUMENT as a PDF Blob through print's one engine: one tall element sliced into paper pages (`captureToPDFBlob`), or a list of designed report pages, one each (`captureElementsToPDFBlob`). Save it with kit `downloadFile`. Test: `src/operate/capture.test.ts`.
29
+ - FEATURE.md ruling A6 corrected: PDF assembly is print's `pagesToPdf`; canvases come from `renderElement` for a picture of a surface and html2canvas for a document. Packages that depend on Alchemy and all app code reach `@ai-matrx/print/pdf` only through `/operate/capture`.
30
+
31
+ **Consumer action:** replace a direct `@ai-matrx/print/pdf` `captureToPDF` / `captureElementsToPDF` / `captureBlockElement` call with `captureDocumentPdf` + `downloadFile`.
32
+
33
+ ## 0.19.2
34
+ ### `/operate/read` reverses the formula guard; the rasteriser ruling is recorded (AP-2 A1/A6)
35
+
36
+ - `parseDelimited` (re-exported from kit, needs kit 0.32.0) strips the guard's `'` from cells our writer guarded (`unguardFormulas: false` keeps raw text). Workbook copy text stays guarded.
37
+ - FEATURE.md records the rasteriser ruling: one DOM→PDF engine (print); html2canvas owns document capture, html-to-image owns a picture of a visual surface.
38
+
39
+ **Consumer action:** none.
40
+
3
41
  ## 0.19.1
4
42
  ### Split-Copy hosts can drop the two tiles they already show
5
43
 
package/dist/checks.cjs CHANGED
@@ -1,4 +1,4 @@
1
- "use strict";var __defProp=Object.defineProperty;var __getOwnPropDesc=Object.getOwnPropertyDescriptor;var __getOwnPropNames=Object.getOwnPropertyNames;var __hasOwnProp=Object.prototype.hasOwnProperty;var __export=(target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:!0})},__copyProps=(to,from,except,desc)=>{if(from&&typeof from=="object"||typeof from=="function")for(let key of __getOwnPropNames(from))!__hasOwnProp.call(to,key)&&key!==except&&__defProp(to,key,{get:()=>from[key],enumerable:!(desc=__getOwnPropDesc(from,key))||desc.enumerable});return to};var __toCommonJS=mod=>__copyProps(__defProp({},"__esModule",{value:!0}),mod);var checks_exports={};__export(checks_exports,{DECLARED_BY:()=>DECLARED_BY,GOVERNANCE_COLUMNS:()=>GOVERNANCE_COLUMNS,INSERT_ONLY_COLUMNS:()=>INSERT_ONLY_COLUMNS,SURFACE_ACTION_KEY:()=>SURFACE_ACTION_KEY,SURFACE_ACTION_TABLE:()=>SURFACE_ACTION_TABLE,conflictUpdateColumns:()=>conflictUpdateColumns,findDeadUrlPatterns:()=>findDeadUrlPatterns,findStaleRows:()=>findStaleRows,isDatabaseDeclared:()=>isDatabaseDeclared,mirrorRowKey:()=>mirrorRowKey,planColumns:()=>planColumns,planSurfaceSync:()=>planSurfaceSync,renderSurfaceSyncSql:()=>renderSurfaceSyncSql,renderSurfaceSyncStatements:()=>renderSurfaceSyncStatements,routeSegments:()=>routeSegments,sqlLiteral:()=>sqlLiteral,urlPatternMatchesRoute:()=>urlPatternMatchesRoute});module.exports=__toCommonJS(checks_exports);function surfaceClientOf(surfaceName){return surfaceName.split("/")[0]??""}var DEFAULT_SENSITIVITY=Object.freeze({exportable:!0,classification:"ordinary",includedByDefault:!0});function resolveSensitivity(value){return{exportable:value.sensitivity?.exportable??DEFAULT_SENSITIVITY.exportable,classification:value.sensitivity?.classification??DEFAULT_SENSITIVITY.classification,includedByDefault:value.sensitivity?.includedByDefault??DEFAULT_SENSITIVITY.includedByDefault}}var GOVERNANCE_COLUMNS=["organization_id","published_to_web"],DECLARED_BY={code:"code",database:"database"},INSERT_ONLY_COLUMNS=[...GOVERNANCE_COLUMNS,"declared_by"],SURFACE_ACTION_TABLE="ui.ui_surface_action",SURFACE_ACTION_KEY=["surface_name","item_type","name"];function isDatabaseDeclared(row){return row.declared_by===DECLARED_BY.database}function childKey(schema){return schema?.itemType?["surface_name","item_type","name"]:["surface_name","name"]}function planSurfaceSync(declarations,options){let{organizationId,syncedFrom,schema}=options,provenance={synced_by:options.syncedBy??null,synced_from:syncedFrom},governance={organization_id:organizationId,published_to_web:!0,declared_by:DECLARED_BY.code},surfaces=[],values=[],targets=[],itemTypes=[],extensionTables=new Map;for(let d of declarations){let urlPattern=d.urlPattern?.trim()||options.resolveUrlPattern?.(d)||null,intro=d.intro?.trim()||null,situation=d.situation?.trim()||null,parent=d.inheritsFrom??null,owned={client_name:surfaceClientOf(d.surfaceName),execution_mode:d.executionMode,description:d.description,label:d.label,value_groups:d.groups??[],readiness:d.readiness,readiness_note:d.readinessNote?.trim()||null,...schema?.contentHash?{content_hash:d.contentHash}:{}},claimed={...d.overlayId?{overlay_id:d.overlayId}:{},...urlPattern?{url_pattern:urlPattern}:{},...intro?{intro}:{},...situation?{situation}:{},...parent?{parent_surface_name:parent}:{}};surfaces.push({name:d.surfaceName,insert:{organization_id:organizationId,name:d.surfaceName,declared_by:DECLARED_BY.code,...owned,overlay_id:d.overlayId??null,url_pattern:urlPattern,intro,situation,parent_surface_name:parent},update:{...owned,...claimed}});for(let v of d.values)values.push({...governance,surface_name:d.surfaceName,...schema?.itemType?{item_type:""}:{},name:v.name,label:v.label,description:v.description,value_type:v.valueType,always_available:v.alwaysAvailable,typical_char_count:v.typicalCharCount,sort_order:v.sortOrder??1e3,auto_context:v.autoContext??!0,group_key:v.groupKey??v.group??"general",...schema?.valueContract?{exportable:resolveSensitivity(v).exportable,classification:resolveSensitivity(v).classification,included_by_default:resolveSensitivity(v).includedByDefault,kind_key:v.valueKind??null,live_slot:v.liveSlot??null,role:v.role??"data"}:{},...provenance});for(let t of d.writeTargets??[])targets.push({...governance,surface_name:d.surfaceName,...schema?.itemType?{item_type:t.itemType??""}:{},name:t.name,label:t.label,description:t.description,value_type:t.valueType,mode:t.mode,updates_value:t.updatesValue??null,group_key:t.group??"general",sort_order:t.sortOrder??1e3,apply_policy:t.applyPolicy??"manual",kind_key:t.valueKind??null,...schema?.valueContract?{patchable:t.patchable??!1,approval_comparison:t.approvalComparison??null,stream_ops:t.streamOps?[...t.streamOps]:null,destination:t.destination??!1}:{},...provenance});if(schema?.itemType)for(let[position,item]of(d.itemTypes??[]).entries()){itemTypes.push({...governance,surface_name:d.surfaceName,name:item.name,label:item.label,description:item.description,identity:[...item.identity],levels:item.levels?[...item.levels]:null,entity_type:item.entityType??null,sort_order:position,...provenance});for(let v of item.values)values.push({...governance,surface_name:d.surfaceName,item_type:item.name,name:v.name,label:v.label,description:v.description,value_type:v.valueType,always_available:v.alwaysAvailable,typical_char_count:v.typicalCharCount,sort_order:v.sortOrder??1e3,auto_context:v.autoContext??!0,group_key:v.group??"general",...provenance})}for(let extension of options.extensions??[])for(let entry of extension.toRows?.(d)??[]){let plan=extensionTables.get(entry.table)??{table:entry.table,conflict:entry.conflict,insertOnly:INSERT_ONLY_COLUMNS,rows:[]};plan.rows.push({...governance,...entry.row,...provenance}),extensionTables.set(entry.table,plan)}}let keys={"ui.ui_surface_value":childKey(schema),"ui.ui_surface_write_target":childKey(schema),...schema?.itemType?{"ui.ui_surface_item_type":["surface_name","name"]}:{},...extensionTables.has(SURFACE_ACTION_TABLE)?{[SURFACE_ACTION_TABLE]:SURFACE_ACTION_KEY}:{}};for(let extension of options.extensions??[])extension.mirror&&(keys[extension.mirror.table]=extension.mirror.conflict);for(let plan of extensionTables.values())keys[plan.table]??=plan.conflict;let tables=[{table:"ui.ui_surface_value",conflict:childKey(schema),insertOnly:INSERT_ONLY_COLUMNS,rows:values},{table:"ui.ui_surface_write_target",conflict:childKey(schema),insertOnly:INSERT_ONLY_COLUMNS,rows:targets},...schema?.itemType?[{table:"ui.ui_surface_item_type",conflict:["surface_name","name"],insertOnly:INSERT_ONLY_COLUMNS,rows:itemTypes}]:[],...extensionTables.values()];return{surfaces,tables:tables.filter(table=>table.rows.length>0),keys}}function mirrorRowKey(row,columns){return columns.map(column=>String(row[column]??"")).join("\0")}function findStaleRows(plan,table,existing){let key=plan.keys[table];if(!key)throw new Error(`Sync plan declares no key for "${table}"; give the extension that owns it a \`mirror\` entry.`);let managed=new Set(plan.surfaces.map(surface=>surface.name)),declared=new Set((plan.tables.find(entry=>entry.table===table)?.rows??[]).map(row=>mirrorRowKey(row,key)));return existing.filter(row=>!isDatabaseDeclared(row)&&managed.has(String(row.surface_name))&&!declared.has(mirrorRowKey(row,key)))}function planColumns(table){let columns=[];for(let row of table.rows)for(let key of Object.keys(row))columns.includes(key)||columns.push(key);return columns}function conflictUpdateColumns(table){return planColumns(table).filter(c=>!table.conflict.includes(c)&&!table.insertOnly.includes(c))}function sqlLiteral(value){return value==null?"NULL":typeof value=="boolean"||typeof value=="number"?String(value):typeof value=="string"?`'${value.replace(/'/g,"''")}'`:Array.isArray(value)&&value.every(entry=>typeof entry=="string")?`ARRAY[${value.map(entry=>sqlLiteral(entry)).join(", ")}]::text[]`:`'${JSON.stringify(value).replace(/'/g,"''")}'::jsonb`}function renderSurfaceSyncStatements(plan){let statements=[];if(plan.surfaces.length>0){let insertColumns=Object.keys(plan.surfaces[0].insert);statements.push(`INSERT INTO ui.ui_surface (${insertColumns.join(", ")}) VALUES
1
+ "use strict";var __defProp=Object.defineProperty;var __getOwnPropDesc=Object.getOwnPropertyDescriptor;var __getOwnPropNames=Object.getOwnPropertyNames;var __hasOwnProp=Object.prototype.hasOwnProperty;var __export=(target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:!0})},__copyProps=(to,from,except,desc)=>{if(from&&typeof from=="object"||typeof from=="function")for(let key of __getOwnPropNames(from))!__hasOwnProp.call(to,key)&&key!==except&&__defProp(to,key,{get:()=>from[key],enumerable:!(desc=__getOwnPropDesc(from,key))||desc.enumerable});return to};var __toCommonJS=mod=>__copyProps(__defProp({},"__esModule",{value:!0}),mod);var checks_exports={};__export(checks_exports,{DECLARED_BY:()=>DECLARED_BY,GOVERNANCE_COLUMNS:()=>GOVERNANCE_COLUMNS,INSERT_ONLY_COLUMNS:()=>INSERT_ONLY_COLUMNS,SURFACE_ACTION_KEY:()=>SURFACE_ACTION_KEY,SURFACE_ACTION_TABLE:()=>SURFACE_ACTION_TABLE,conflictUpdateColumns:()=>conflictUpdateColumns,findDeadUrlPatterns:()=>findDeadUrlPatterns,findStaleRows:()=>findStaleRows,isDatabaseDeclared:()=>isDatabaseDeclared,mirrorRowKey:()=>mirrorRowKey,planColumns:()=>planColumns,planSurfaceSync:()=>planSurfaceSync,renderSurfaceSyncSql:()=>renderSurfaceSyncSql,renderSurfaceSyncStatements:()=>renderSurfaceSyncStatements,routeSegments:()=>routeSegments,sqlLiteral:()=>sqlLiteral,urlPatternMatchesRoute:()=>urlPatternMatchesRoute});module.exports=__toCommonJS(checks_exports);function surfaceClientOf(surfaceName){return surfaceName.split("/")[0]??""}var DEFAULT_SENSITIVITY=Object.freeze({exportable:!0,classification:"ordinary",includedByDefault:!0});function resolveSensitivity(value){return{exportable:value.sensitivity?.exportable??DEFAULT_SENSITIVITY.exportable,classification:value.sensitivity?.classification??DEFAULT_SENSITIVITY.classification,includedByDefault:value.sensitivity?.includedByDefault??DEFAULT_SENSITIVITY.includedByDefault}}var GOVERNANCE_COLUMNS=["organization_id","published_to_web"],DECLARED_BY={code:"code",database:"database"},INSERT_ONLY_COLUMNS=[...GOVERNANCE_COLUMNS,"declared_by"],SURFACE_ACTION_TABLE="ui.ui_surface_action",SURFACE_ACTION_KEY=["surface_name","item_type","name"];function isDatabaseDeclared(row){return row.declared_by===DECLARED_BY.database}function childKey(schema){return schema?.itemType?["surface_name","item_type","name"]:["surface_name","name"]}function planSurfaceSync(declarations,options){let{organizationId,syncedFrom,schema}=options,provenance={synced_by:options.syncedBy??null,synced_from:syncedFrom},governance={organization_id:organizationId,published_to_web:!0,declared_by:DECLARED_BY.code},surfaces=[],values=[],targets=[],itemTypes=[],extensionTables=new Map;for(let d of declarations){let urlPattern=d.urlPattern?.trim()||options.resolveUrlPattern?.(d)||null,intro=d.intro?.trim()||null,situation=d.situation?.trim()||null,parent=d.inheritsFrom??null,owned={client_name:surfaceClientOf(d.surfaceName),execution_mode:d.executionMode,description:d.description,label:d.label,value_groups:d.groups??[],readiness:d.readiness,readiness_note:d.readinessNote?.trim()||null,...schema?.contentHash?{content_hash:d.contentHash}:{}},claimed={...d.overlayId?{overlay_id:d.overlayId}:{},...urlPattern?{url_pattern:urlPattern}:{},...intro?{intro}:{},...situation?{situation}:{},...parent?{parent_surface_name:parent}:{}};surfaces.push({name:d.surfaceName,insert:{organization_id:organizationId,name:d.surfaceName,declared_by:DECLARED_BY.code,...owned,overlay_id:d.overlayId??null,url_pattern:urlPattern,intro,situation,parent_surface_name:parent},update:{...owned,...claimed}});for(let v of d.values)values.push({...governance,surface_name:d.surfaceName,...schema?.itemType?{item_type:""}:{},name:v.name,label:v.label,description:v.description,value_type:v.valueType,always_available:v.alwaysAvailable,typical_char_count:v.typicalCharCount,sort_order:v.sortOrder??1e3,auto_context:v.autoContext??!0,group_key:v.groupKey??v.group??"general",...schema?.valueContract?{exportable:resolveSensitivity(v).exportable,classification:resolveSensitivity(v).classification,included_by_default:resolveSensitivity(v).includedByDefault,kind_key:v.valueKind??null,live_slot:v.liveSlot??null,role:v.role??"data"}:{},...provenance});for(let t of d.writeTargets??[])targets.push({...governance,surface_name:d.surfaceName,...schema?.itemType?{item_type:t.itemType??""}:{},name:t.name,label:t.label,description:t.description,value_type:t.valueType,mode:t.mode,updates_value:t.updatesValue??null,group_key:t.group??"general",sort_order:t.sortOrder??1e3,apply_policy:t.applyPolicy??"manual",kind_key:t.valueKind??null,...schema?.valueContract?{patchable:t.patchable??!1,approval_comparison:t.approvalComparison??null,stream_ops:t.streamOps?[...t.streamOps]:null,destination:t.destination??!1}:{},...provenance});if(schema?.itemType)for(let[position,item]of(d.itemTypes??[]).entries()){itemTypes.push({...governance,surface_name:d.surfaceName,name:item.name,label:item.label,description:item.description,identity:[...item.identity],levels:item.levels?[...item.levels]:null,entity_type:item.entityType??null,sort_order:position,...provenance});for(let v of item.values)values.push({...governance,surface_name:d.surfaceName,item_type:item.name,name:v.name,label:v.label,description:v.description,value_type:v.valueType,always_available:v.alwaysAvailable,typical_char_count:v.typicalCharCount,sort_order:v.sortOrder??1e3,auto_context:v.autoContext??!0,group_key:v.group??"general",...schema?.valueContract?{exportable:resolveSensitivity(v).exportable,classification:resolveSensitivity(v).classification,included_by_default:resolveSensitivity(v).includedByDefault,kind_key:v.valueKind??null,live_slot:v.liveSlot??null,role:v.role??"data"}:{},...provenance})}for(let extension of options.extensions??[])for(let entry of extension.toRows?.(d)??[]){let plan=extensionTables.get(entry.table)??{table:entry.table,conflict:entry.conflict,insertOnly:INSERT_ONLY_COLUMNS,rows:[]};plan.rows.push({...governance,...entry.row,...provenance}),extensionTables.set(entry.table,plan)}}let keys={"ui.ui_surface_value":childKey(schema),"ui.ui_surface_write_target":childKey(schema),...schema?.itemType?{"ui.ui_surface_item_type":["surface_name","name"]}:{},...extensionTables.has(SURFACE_ACTION_TABLE)?{[SURFACE_ACTION_TABLE]:SURFACE_ACTION_KEY}:{}};for(let extension of options.extensions??[])extension.mirror&&(keys[extension.mirror.table]=extension.mirror.conflict);for(let plan of extensionTables.values())keys[plan.table]??=plan.conflict;let tables=[{table:"ui.ui_surface_value",conflict:childKey(schema),insertOnly:INSERT_ONLY_COLUMNS,rows:values},{table:"ui.ui_surface_write_target",conflict:childKey(schema),insertOnly:INSERT_ONLY_COLUMNS,rows:targets},...schema?.itemType?[{table:"ui.ui_surface_item_type",conflict:["surface_name","name"],insertOnly:INSERT_ONLY_COLUMNS,rows:itemTypes}]:[],...extensionTables.values()];return{surfaces,tables:tables.filter(table=>table.rows.length>0),keys}}function mirrorRowKey(row,columns){return columns.map(column=>String(row[column]??"")).join("\0")}function findStaleRows(plan,table,existing){let key=plan.keys[table];if(!key)throw new Error(`Sync plan declares no key for "${table}"; give the extension that owns it a \`mirror\` entry.`);let managed=new Set(plan.surfaces.map(surface=>surface.name)),declared=new Set((plan.tables.find(entry=>entry.table===table)?.rows??[]).map(row=>mirrorRowKey(row,key)));return existing.filter(row=>!isDatabaseDeclared(row)&&managed.has(String(row.surface_name))&&!declared.has(mirrorRowKey(row,key)))}function planColumns(table){let columns=[];for(let row of table.rows)for(let key of Object.keys(row))columns.includes(key)||columns.push(key);return columns}function conflictUpdateColumns(table){return planColumns(table).filter(c=>!table.conflict.includes(c)&&!table.insertOnly.includes(c))}function sqlLiteral(value){return value==null?"NULL":typeof value=="boolean"||typeof value=="number"?String(value):typeof value=="string"?`'${value.replace(/'/g,"''")}'`:Array.isArray(value)&&value.every(entry=>typeof entry=="string")?`ARRAY[${value.map(entry=>sqlLiteral(entry)).join(", ")}]::text[]`:`'${JSON.stringify(value).replace(/'/g,"''")}'::jsonb`}function renderSurfaceSyncStatements(plan){let statements=[];if(plan.surfaces.length>0){let insertColumns=Object.keys(plan.surfaces[0].insert);statements.push(`INSERT INTO ui.ui_surface (${insertColumns.join(", ")}) VALUES
2
2
  ${plan.surfaces.map(s=>`(${insertColumns.map(c=>sqlLiteral(s.insert[c])).join(", ")})`).join(`,
3
3
  `)}
4
4
  ON CONFLICT (name) DO NOTHING`);for(let s of plan.surfaces)statements.push(`UPDATE ui.ui_surface SET ${Object.entries(s.update).map(([c,v])=>`${c} = ${sqlLiteral(v)}`).join(", ")}, updated_at = now() WHERE name = ${sqlLiteral(s.name)}`)}for(let table of plan.tables){let columns=planColumns(table),sets=conflictUpdateColumns(table).map(c=>`${c} = EXCLUDED.${c}`);statements.push(`INSERT INTO ${table.table} (${columns.join(", ")}) VALUES
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/checks.ts","../src/declare/keys.ts","../src/declare/sensitivity.ts","../src/checks/sync.ts","../src/checks/routes.ts"],"sourcesContent":["/**\n * `@ai-matrx/alchemy/checks` — the ONE surface sync (CONTRACT §2.7): the row plan\n * every sync path executes, and its SQL rendering. Pure; no database client.\n */\nexport {\n DECLARED_BY,\n GOVERNANCE_COLUMNS,\n INSERT_ONLY_COLUMNS,\n SURFACE_ACTION_KEY,\n SURFACE_ACTION_TABLE,\n isDatabaseDeclared,\n type DeclaredBy,\n conflictUpdateColumns,\n findStaleRows,\n mirrorRowKey,\n planColumns,\n planSurfaceSync,\n renderSurfaceSyncSql,\n renderSurfaceSyncStatements,\n sqlLiteral,\n type SurfaceRowPlan,\n type SurfaceSyncPlan,\n type SurfaceSyncPlanOptions,\n type SyncSchema,\n type SyncTablePlan,\n} from \"./checks/sync\";\nexport { findDeadUrlPatterns, routeSegments, urlPatternMatchesRoute } from \"./checks/routes\";\n","import type { QualifiedValueKey } from \"./types\";\n\n/** Every value, write-target, item-type and group name (the DB CHECK rule). */\nexport const DECLARATION_NAME_RE = /^[a-z][a-z0-9_]*$/;\n\n/** \"<client>/<local>\" — the surface naming convention. */\nexport const SURFACE_NAME_RE = /^[a-z][a-z0-9-]*\\/[a-z0-9-/]+$/;\n\n/** A bare screen value (`status`) or an item value (`table_row.status`). */\nexport const QUALIFIED_VALUE_KEY_RE = /^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)?$/;\n\nexport interface ParsedValueKey {\n /** \"\" for a screen-level value. */\n itemType: string;\n name: string;\n}\n\n/** Split a qualified key; null when it is not a valid bare or qualified key. */\nexport function parseQualifiedValueKey(key: string): ParsedValueKey | null {\n if (!QUALIFIED_VALUE_KEY_RE.test(key)) return null;\n const dot = key.indexOf(\".\");\n if (dot === -1) return { itemType: \"\", name: key };\n return { itemType: key.slice(0, dot), name: key.slice(dot + 1) };\n}\n\nexport function qualifyValueKey(itemType: string, name: string): QualifiedValueKey {\n return itemType ? `${itemType}.${name}` : name;\n}\n\n/** The client that owns a surface: its name prefix. */\nexport function surfaceClientOf(surfaceName: string): string {\n return surfaceName.split(\"/\")[0] ?? \"\";\n}\n","/**\n * Sensitivity defaults and resolution (CONTRACT §2.1). Its own module so the\n * lightweight entries (`/actions`, the T0 React host) can project without\n * pulling the declaration registry.\n */\nimport type { DeclaredValue, Sensitivity } from \"./types\";\n\nexport const DEFAULT_SENSITIVITY: Sensitivity = Object.freeze({\n exportable: true,\n classification: \"ordinary\",\n includedByDefault: true,\n});\n\nexport function resolveSensitivity(value: Pick<DeclaredValue, \"sensitivity\">): Sensitivity {\n return {\n exportable: value.sensitivity?.exportable ?? DEFAULT_SENSITIVITY.exportable,\n classification: value.sensitivity?.classification ?? DEFAULT_SENSITIVITY.classification,\n includedByDefault: value.sensitivity?.includedByDefault ?? DEFAULT_SENSITIVITY.includedByDefault,\n };\n}\n","/**\n * THE ONE SYNC (CONTRACT §2.7 \"One sync path\", chair ruling B1).\n *\n * `planSurfaceSync` maps resolved declarations onto the `ui` mirror rows; every\n * sync path (the SQL emitter and the admin sync service) executes this plan and\n * builds no rows of its own. Governance columns (`organization_id`,\n * `published_to_web`) are INSERT-ONLY (chair ruling N5): a conflict never rewrites them.\n * Mirror rows are published to the web on insert, written explicitly (access ladder\n * T-13: never left to a column default); the database stamps when and by whom.\n *\n * Schema generations: the columns added by the ALC-14 migration (item_type,\n * sensitivity, live slots, content_hash) are emitted only when the caller says\n * that migration is live (`schema`), so no emitter ever writes a column — or\n * upserts against a key — the database does not have yet.\n */\nimport { resolveSensitivity, type Resolved } from \"../declare/registry\";\nimport { surfaceClientOf } from \"../declare/keys\";\nimport type { DeclarationExtension, Json, SurfaceDeclaration } from \"../declare/types\";\n\nexport const GOVERNANCE_COLUMNS = [\"organization_id\", \"published_to_web\"] as const;\n\n/**\n * WHO DECLARED A ROW (Applets CONTRACTS §6, PLAN ruling 7). Every `ui.ui_surface*`\n * row is `declared_by` \"code\" or \"database\". This sync writes only code rows: it\n * stamps \"code\" on insert and never rewrites the column on conflict (a database\n * trigger refuses a code takeover of a database row anyway); stale and orphan\n * checks never return a database row.\n */\nexport const DECLARED_BY = { code: \"code\", database: \"database\" } as const;\nexport type DeclaredBy = (typeof DECLARED_BY)[keyof typeof DECLARED_BY];\n\n/** Set on insert, never on conflict: governance plus `declared_by`. */\nexport const INSERT_ONLY_COLUMNS = [...GOVERNANCE_COLUMNS, \"declared_by\"] as const;\n\n/** The Actions mirror. Always keyed with item_type (the table was born re-keyed). */\nexport const SURFACE_ACTION_TABLE = \"ui.ui_surface_action\";\nexport const SURFACE_ACTION_KEY = [\"surface_name\", \"item_type\", \"name\"] as const;\n\n/** True for a row an organization authored in the database — code sync never touches it. */\nexport function isDatabaseDeclared(row: Readonly<Record<string, unknown>>): boolean {\n return row.declared_by === DECLARED_BY.database;\n}\n\nexport interface SyncSchema {\n /** `ui_surface_value/write_target.item_type` exists and the conflict key includes it (re-key step 1). */\n itemType?: boolean;\n /** Sensitivity, kind, live slot and role columns exist on `ui_surface_value`. */\n valueContract?: boolean;\n /** `ui_surface.content_hash` exists. */\n contentHash?: boolean;\n}\n\nexport interface SurfaceSyncPlanOptions<D extends SurfaceDeclaration> {\n organizationId: string;\n syncedFrom: string;\n syncedBy?: string | null;\n /** The address the host derives for a declaration without `urlPattern`. */\n resolveUrlPattern?: (declaration: Resolved<D>) => string | undefined;\n extensions?: readonly DeclarationExtension<D>[];\n schema?: SyncSchema;\n}\n\nexport interface SurfaceRowPlan {\n name: string;\n /** Written only when the row does not exist yet. */\n insert: Record<string, Json>;\n /** Written on every sync (code owns these columns). */\n update: Record<string, Json>;\n}\n\nexport interface SyncTablePlan {\n table: string;\n /** The ON CONFLICT target (a unique index or primary key on exactly these columns). */\n conflict: readonly string[];\n /** Columns set on insert and never on conflict. */\n insertOnly: readonly string[];\n rows: Record<string, Json>[];\n}\n\nexport interface SurfaceSyncPlan {\n surfaces: SurfaceRowPlan[];\n tables: SyncTablePlan[];\n /**\n * The key (ON CONFLICT target) of every mirror table this plan owns,\n * including tables it emits no rows for. Stale detection and deletes use\n * exactly these columns (`findStaleRows`).\n */\n keys: Record<string, readonly string[]>;\n}\n\nfunction childKey(schema: SyncSchema | undefined): readonly string[] {\n return schema?.itemType ? [\"surface_name\", \"item_type\", \"name\"] : [\"surface_name\", \"name\"];\n}\n\nexport function planSurfaceSync<D extends SurfaceDeclaration>(\n declarations: readonly Resolved<D>[],\n options: SurfaceSyncPlanOptions<D>,\n): SurfaceSyncPlan {\n const { organizationId, syncedFrom, schema } = options;\n const syncedBy = options.syncedBy ?? null;\n const provenance = { synced_by: syncedBy, synced_from: syncedFrom };\n const governance = { organization_id: organizationId, published_to_web: true, declared_by: DECLARED_BY.code };\n const surfaces: SurfaceRowPlan[] = [];\n const values: Record<string, Json>[] = [];\n const targets: Record<string, Json>[] = [];\n const itemTypes: Record<string, Json>[] = [];\n const extensionTables = new Map<string, SyncTablePlan>();\n\n for (const d of declarations) {\n const urlPattern = d.urlPattern?.trim() || options.resolveUrlPattern?.(d) || null;\n const intro = d.intro?.trim() || null;\n const situation = d.situation?.trim() || null;\n const parent = d.inheritsFrom ?? null;\n const client = surfaceClientOf(d.surfaceName);\n const owned: Record<string, Json> = {\n client_name: client,\n execution_mode: d.executionMode,\n description: d.description,\n label: d.label,\n value_groups: (d.groups ?? []) as unknown as Json,\n readiness: d.readiness,\n readiness_note: d.readinessNote?.trim() || null,\n ...(schema?.contentHash ? { content_hash: d.contentHash } : {}),\n };\n // A declaration that does not claim these never clears a DB-authored value.\n const claimed: Record<string, Json> = {\n ...(d.overlayId ? { overlay_id: d.overlayId } : {}),\n ...(urlPattern ? { url_pattern: urlPattern } : {}),\n ...(intro ? { intro } : {}),\n ...(situation ? { situation } : {}),\n ...(parent ? { parent_surface_name: parent } : {}),\n };\n surfaces.push({\n name: d.surfaceName,\n insert: {\n // Insert-only ownership: `ui.ui_surface.organization_id` is NOT NULL since AP-2 (code rows → the\n // system organization); a new surface without it was refused by the database (AP-6, 2026-10-06).\n organization_id: organizationId,\n name: d.surfaceName,\n declared_by: DECLARED_BY.code,\n ...owned,\n overlay_id: d.overlayId ?? null,\n url_pattern: urlPattern,\n intro,\n situation,\n parent_surface_name: parent,\n },\n update: { ...owned, ...claimed },\n });\n\n for (const v of d.values) {\n values.push({\n ...governance,\n surface_name: d.surfaceName,\n ...(schema?.itemType ? { item_type: \"\" } : {}),\n name: v.name,\n label: v.label,\n description: v.description,\n value_type: v.valueType,\n always_available: v.alwaysAvailable,\n typical_char_count: v.typicalCharCount,\n sort_order: v.sortOrder ?? 1000,\n auto_context: v.autoContext ?? true,\n group_key: v.groupKey ?? v.group ?? \"general\",\n ...(schema?.valueContract\n ? {\n exportable: resolveSensitivity(v).exportable,\n classification: resolveSensitivity(v).classification,\n included_by_default: resolveSensitivity(v).includedByDefault,\n kind_key: v.valueKind ?? null,\n live_slot: v.liveSlot ?? null,\n role: v.role ?? \"data\",\n }\n : {}),\n ...provenance,\n });\n }\n\n for (const t of d.writeTargets ?? []) {\n targets.push({\n ...governance,\n surface_name: d.surfaceName,\n ...(schema?.itemType ? { item_type: t.itemType ?? \"\" } : {}),\n name: t.name,\n label: t.label,\n description: t.description,\n value_type: t.valueType,\n mode: t.mode,\n updates_value: t.updatesValue ?? null,\n group_key: t.group ?? \"general\",\n sort_order: t.sortOrder ?? 1000,\n apply_policy: t.applyPolicy ?? \"manual\",\n kind_key: t.valueKind ?? null,\n ...(schema?.valueContract\n ? {\n patchable: t.patchable ?? false,\n approval_comparison: t.approvalComparison ?? null,\n stream_ops: t.streamOps ? [...t.streamOps] : null,\n destination: t.destination ?? false,\n }\n : {}),\n ...provenance,\n });\n }\n\n if (schema?.itemType) {\n for (const [position, item] of (d.itemTypes ?? []).entries()) {\n itemTypes.push({\n ...governance,\n surface_name: d.surfaceName,\n name: item.name,\n label: item.label,\n description: item.description,\n identity: [...item.identity],\n levels: item.levels ? [...item.levels] : null,\n entity_type: item.entityType ?? null,\n sort_order: position,\n ...provenance,\n });\n for (const v of item.values) {\n values.push({\n ...governance,\n surface_name: d.surfaceName,\n item_type: item.name,\n name: v.name,\n label: v.label,\n description: v.description,\n value_type: v.valueType,\n always_available: v.alwaysAvailable,\n typical_char_count: v.typicalCharCount,\n sort_order: v.sortOrder ?? 1000,\n auto_context: v.autoContext ?? true,\n group_key: v.group ?? \"general\",\n ...provenance,\n });\n }\n }\n }\n\n for (const extension of options.extensions ?? []) {\n for (const entry of extension.toRows?.(d as unknown as D) ?? []) {\n const plan = extensionTables.get(entry.table) ?? {\n table: entry.table,\n conflict: entry.conflict,\n insertOnly: INSERT_ONLY_COLUMNS,\n rows: [],\n };\n plan.rows.push({ ...governance, ...entry.row, ...provenance });\n extensionTables.set(entry.table, plan);\n }\n }\n }\n\n const keys: Record<string, readonly string[]> = {\n \"ui.ui_surface_value\": childKey(schema),\n \"ui.ui_surface_write_target\": childKey(schema),\n ...(schema?.itemType ? { \"ui.ui_surface_item_type\": [\"surface_name\", \"name\"] } : {}),\n // ui.ui_surface_action is claimed ONLY when a code declaration emits Action rows: until code\n // declares Actions, every live Action row is database-owned, and claiming the table would read\n // a future code Action as stale. Unclaimed, a stale check on it throws by name instead.\n ...(extensionTables.has(SURFACE_ACTION_TABLE) ? { [SURFACE_ACTION_TABLE]: SURFACE_ACTION_KEY } : {}),\n };\n for (const extension of options.extensions ?? []) {\n if (extension.mirror) keys[extension.mirror.table] = extension.mirror.conflict;\n }\n for (const plan of extensionTables.values()) keys[plan.table] ??= plan.conflict;\n\n const tables: SyncTablePlan[] = [\n { table: \"ui.ui_surface_value\", conflict: childKey(schema), insertOnly: INSERT_ONLY_COLUMNS, rows: values },\n { table: \"ui.ui_surface_write_target\", conflict: childKey(schema), insertOnly: INSERT_ONLY_COLUMNS, rows: targets },\n ...(schema?.itemType\n ? [{ table: \"ui.ui_surface_item_type\", conflict: [\"surface_name\", \"name\"], insertOnly: INSERT_ONLY_COLUMNS, rows: itemTypes }]\n : []),\n ...extensionTables.values(),\n ];\n return { surfaces, tables: tables.filter((table) => table.rows.length > 0), keys };\n}\n\n/** One row's key under `columns` (a missing column reads as ''). */\nexport function mirrorRowKey(row: Readonly<Record<string, unknown>>, columns: readonly string[]): string {\n return columns.map((column) => String(row[column] ?? \"\")).join(\"\\u0000\");\n}\n\n/**\n * Rows of `table` that belong to a surface this plan manages but that the\n * plan no longer declares, judged by the table's full key (`plan.keys`) — so\n * a screen value and an item value that share a name are distinct rows. The\n * caller deletes each returned row filtered by every column of that key.\n * A row whose `declared_by` is \"database\" is never returned (pass the column\n * in `existing`, and delete with `declared_by = 'code'` as well).\n */\nexport function findStaleRows<R extends Readonly<Record<string, unknown>>>(\n plan: SurfaceSyncPlan,\n table: string,\n existing: readonly R[],\n): R[] {\n const key = plan.keys[table];\n if (!key) {\n throw new Error(\n `Sync plan declares no key for \"${table}\"; give the extension that owns it a \\`mirror\\` entry.`,\n );\n }\n const managed = new Set(plan.surfaces.map((surface) => surface.name));\n const declared = new Set(\n (plan.tables.find((entry) => entry.table === table)?.rows ?? []).map((row) => mirrorRowKey(row, key)),\n );\n return existing.filter(\n (row) => !isDatabaseDeclared(row) && managed.has(String(row.surface_name)) && !declared.has(mirrorRowKey(row, key)),\n );\n}\n\n/** Every column a table's rows carry, in first-seen order. */\nexport function planColumns(table: SyncTablePlan): string[] {\n const columns: string[] = [];\n for (const row of table.rows) for (const key of Object.keys(row)) if (!columns.includes(key)) columns.push(key);\n return columns;\n}\n\n/** Columns a conflict rewrites: everything except the key and the insert-only set. */\nexport function conflictUpdateColumns(table: SyncTablePlan): string[] {\n return planColumns(table).filter((c) => !table.conflict.includes(c) && !table.insertOnly.includes(c));\n}\n\nexport function sqlLiteral(value: Json | undefined): string {\n if (value === null || value === undefined) return \"NULL\";\n if (typeof value === \"boolean\" || typeof value === \"number\") return String(value);\n if (typeof value === \"string\") return `'${value.replace(/'/g, \"''\")}'`;\n if (Array.isArray(value) && value.every((entry) => typeof entry === \"string\")) {\n return `ARRAY[${value.map((entry) => sqlLiteral(entry)).join(\", \")}]::text[]`;\n }\n return `'${JSON.stringify(value).replace(/'/g, \"''\")}'::jsonb`;\n}\n\n/**\n * The plan as SQL statements, one per entry: surface insert, one UPDATE per\n * surface, one upsert per table. Callers may run them, or EXPLAIN each one in a\n * read-only transaction to prove the plan against a live schema with no write.\n */\nexport function renderSurfaceSyncStatements(plan: SurfaceSyncPlan): string[] {\n const statements: string[] = [];\n if (plan.surfaces.length > 0) {\n const insertColumns = Object.keys(plan.surfaces[0]!.insert);\n statements.push(\n `INSERT INTO ui.ui_surface (${insertColumns.join(\", \")}) VALUES\\n${plan.surfaces\n .map((s) => `(${insertColumns.map((c) => sqlLiteral(s.insert[c])).join(\", \")})`)\n .join(\",\\n\")}\\nON CONFLICT (name) DO NOTHING`,\n );\n for (const s of plan.surfaces) {\n statements.push(\n `UPDATE ui.ui_surface SET ${Object.entries(s.update)\n .map(([c, v]) => `${c} = ${sqlLiteral(v)}`)\n .join(\", \")}, updated_at = now() WHERE name = ${sqlLiteral(s.name)}`,\n );\n }\n }\n for (const table of plan.tables) {\n const columns = planColumns(table);\n const sets = conflictUpdateColumns(table).map((c) => `${c} = EXCLUDED.${c}`);\n statements.push(\n `INSERT INTO ${table.table} (${columns.join(\", \")}) VALUES\\n${table.rows\n .map((row) => `(${columns.map((c) => sqlLiteral(row[c])).join(\", \")})`)\n .join(\",\\n\")}\\nON CONFLICT (${table.conflict.join(\", \")}) DO UPDATE SET ${[...sets, \"updated_at = now()\"].join(\", \")}`,\n );\n }\n return statements;\n}\n\n/** The plan as one SQL script (governance columns insert-only). */\nexport function renderSurfaceSyncSql(plan: SurfaceSyncPlan): string {\n return renderSurfaceSyncStatements(plan)\n .map((statement) => `${statement};`)\n .join(\"\\n\\n\");\n}\n","/**\n * The declaration half of the route check (CONTRACT §2.6 `/checks`): does each\n * declared `urlPattern` address a live route? The host supplies its route list\n * (it owns the filesystem); the matching rule lives here, once. Ported from\n * matrx-frontend `scripts/check-surface-routes.ts` (ALC-14).\n */\n\n/** Route or pattern → segments; `[x]` / `:x` → \"*\", `[...x]` / `[[...x]]` → \"**\". */\nexport function routeSegments(path: string): string[] {\n return (path.split(\"?\")[0] ?? \"\")\n .split(\"#\")[0]!\n .split(\"/\")\n .filter(Boolean)\n .map((segment) => {\n if (/^\\[\\[?\\.\\.\\..+\\]\\]?$/.test(segment)) return \"**\";\n if (segment.startsWith(\"[\") || segment.startsWith(\":\")) return \"*\";\n return segment;\n });\n}\n\n/**\n * Does a declared `urlPattern` address a live route? A literal pattern segment\n * must be a literal route segment — a dynamic route segment does NOT satisfy it,\n * or `/marketing/[brandId]/…` would \"cover\" a retired `/marketing/brands/…`.\n * A trailing `*` makes the pattern a prefix.\n */\nexport function urlPatternMatchesRoute(pattern: string, route: readonly string[]): boolean {\n const isPrefix = pattern.trimEnd().endsWith(\"*\") && !pattern.endsWith(\"]*\");\n const segments = routeSegments(pattern.replace(/\\*+\\s*$/, \"\"));\n for (let i = 0; i < segments.length; i += 1) {\n const routeSegment = route[i];\n if (routeSegment === \"**\") return true;\n if (routeSegment === undefined) return false;\n const declared = segments[i];\n if (declared === \"**\") return true;\n if (declared === \"*\" ? routeSegment !== \"*\" : routeSegment !== declared) return false;\n }\n return isPrefix || route.length === segments.length;\n}\n\n/** Every declaration whose `urlPattern` no live route can produce. */\nexport function findDeadUrlPatterns(\n declarations: readonly { surfaceName: string; urlPattern?: string | undefined }[],\n liveRoutes: readonly string[],\n): { surfaceName: string; urlPattern: string }[] {\n const routes = liveRoutes.map(routeSegments);\n const dead: { surfaceName: string; urlPattern: string }[] = [];\n for (const declaration of declarations) {\n const pattern = declaration.urlPattern;\n if (!pattern) continue;\n if (!routes.some((route) => urlPatternMatchesRoute(pattern, route))) {\n dead.push({ surfaceName: declaration.surfaceName, urlPattern: pattern });\n }\n }\n return dead;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/checks.ts","../src/declare/keys.ts","../src/declare/sensitivity.ts","../src/checks/sync.ts","../src/checks/routes.ts"],"sourcesContent":["/**\n * `@ai-matrx/alchemy/checks` — the ONE surface sync (CONTRACT §2.7): the row plan\n * every sync path executes, and its SQL rendering. Pure; no database client.\n */\nexport {\n DECLARED_BY,\n GOVERNANCE_COLUMNS,\n INSERT_ONLY_COLUMNS,\n SURFACE_ACTION_KEY,\n SURFACE_ACTION_TABLE,\n isDatabaseDeclared,\n type DeclaredBy,\n conflictUpdateColumns,\n findStaleRows,\n mirrorRowKey,\n planColumns,\n planSurfaceSync,\n renderSurfaceSyncSql,\n renderSurfaceSyncStatements,\n sqlLiteral,\n type SurfaceRowPlan,\n type SurfaceSyncPlan,\n type SurfaceSyncPlanOptions,\n type SyncSchema,\n type SyncTablePlan,\n} from \"./checks/sync\";\nexport { findDeadUrlPatterns, routeSegments, urlPatternMatchesRoute } from \"./checks/routes\";\n","import type { QualifiedValueKey } from \"./types\";\n\n/** Every value, write-target, item-type and group name (the DB CHECK rule). */\nexport const DECLARATION_NAME_RE = /^[a-z][a-z0-9_]*$/;\n\n/** \"<client>/<local>\" — the surface naming convention. */\nexport const SURFACE_NAME_RE = /^[a-z][a-z0-9-]*\\/[a-z0-9-/]+$/;\n\n/** A bare screen value (`status`) or an item value (`table_row.status`). */\nexport const QUALIFIED_VALUE_KEY_RE = /^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)?$/;\n\nexport interface ParsedValueKey {\n /** \"\" for a screen-level value. */\n itemType: string;\n name: string;\n}\n\n/** Split a qualified key; null when it is not a valid bare or qualified key. */\nexport function parseQualifiedValueKey(key: string): ParsedValueKey | null {\n if (!QUALIFIED_VALUE_KEY_RE.test(key)) return null;\n const dot = key.indexOf(\".\");\n if (dot === -1) return { itemType: \"\", name: key };\n return { itemType: key.slice(0, dot), name: key.slice(dot + 1) };\n}\n\nexport function qualifyValueKey(itemType: string, name: string): QualifiedValueKey {\n return itemType ? `${itemType}.${name}` : name;\n}\n\n/** The client that owns a surface: its name prefix. */\nexport function surfaceClientOf(surfaceName: string): string {\n return surfaceName.split(\"/\")[0] ?? \"\";\n}\n","/**\n * Sensitivity defaults and resolution (CONTRACT §2.1). Its own module so the\n * lightweight entries (`/actions`, the T0 React host) can project without\n * pulling the declaration registry.\n */\nimport type { DeclaredValue, Sensitivity } from \"./types\";\n\nexport const DEFAULT_SENSITIVITY: Sensitivity = Object.freeze({\n exportable: true,\n classification: \"ordinary\",\n includedByDefault: true,\n});\n\nexport function resolveSensitivity(value: Pick<DeclaredValue, \"sensitivity\">): Sensitivity {\n return {\n exportable: value.sensitivity?.exportable ?? DEFAULT_SENSITIVITY.exportable,\n classification: value.sensitivity?.classification ?? DEFAULT_SENSITIVITY.classification,\n includedByDefault: value.sensitivity?.includedByDefault ?? DEFAULT_SENSITIVITY.includedByDefault,\n };\n}\n","/**\n * THE ONE SYNC (CONTRACT §2.7 \"One sync path\", chair ruling B1).\n *\n * `planSurfaceSync` maps resolved declarations onto the `ui` mirror rows; every\n * sync path (the SQL emitter and the admin sync service) executes this plan and\n * builds no rows of its own. Governance columns (`organization_id`,\n * `published_to_web`) are INSERT-ONLY (chair ruling N5): a conflict never rewrites them.\n * Mirror rows are published to the web on insert, written explicitly (access ladder\n * T-13: never left to a column default); the database stamps when and by whom.\n *\n * Schema generations: the columns added by the ALC-14 migration (item_type,\n * sensitivity, live slots, content_hash) are emitted only when the caller says\n * that migration is live (`schema`), so no emitter ever writes a column — or\n * upserts against a key — the database does not have yet.\n */\nimport { resolveSensitivity, type Resolved } from \"../declare/registry\";\nimport { surfaceClientOf } from \"../declare/keys\";\nimport type { DeclarationExtension, Json, SurfaceDeclaration } from \"../declare/types\";\n\nexport const GOVERNANCE_COLUMNS = [\"organization_id\", \"published_to_web\"] as const;\n\n/**\n * WHO DECLARED A ROW (Applets CONTRACTS §6, PLAN ruling 7). Every `ui.ui_surface*`\n * row is `declared_by` \"code\" or \"database\". This sync writes only code rows: it\n * stamps \"code\" on insert and never rewrites the column on conflict (a database\n * trigger refuses a code takeover of a database row anyway); stale and orphan\n * checks never return a database row.\n */\nexport const DECLARED_BY = { code: \"code\", database: \"database\" } as const;\nexport type DeclaredBy = (typeof DECLARED_BY)[keyof typeof DECLARED_BY];\n\n/** Set on insert, never on conflict: governance plus `declared_by`. */\nexport const INSERT_ONLY_COLUMNS = [...GOVERNANCE_COLUMNS, \"declared_by\"] as const;\n\n/** The Actions mirror. Always keyed with item_type (the table was born re-keyed). */\nexport const SURFACE_ACTION_TABLE = \"ui.ui_surface_action\";\nexport const SURFACE_ACTION_KEY = [\"surface_name\", \"item_type\", \"name\"] as const;\n\n/** True for a row an organization authored in the database — code sync never touches it. */\nexport function isDatabaseDeclared(row: Readonly<Record<string, unknown>>): boolean {\n return row.declared_by === DECLARED_BY.database;\n}\n\nexport interface SyncSchema {\n /** `ui_surface_value/write_target.item_type` exists and the conflict key includes it (re-key step 1). */\n itemType?: boolean;\n /** Sensitivity, kind, live slot and role columns exist on `ui_surface_value`. */\n valueContract?: boolean;\n /** `ui_surface.content_hash` exists. */\n contentHash?: boolean;\n}\n\nexport interface SurfaceSyncPlanOptions<D extends SurfaceDeclaration> {\n organizationId: string;\n syncedFrom: string;\n syncedBy?: string | null;\n /** The address the host derives for a declaration without `urlPattern`. */\n resolveUrlPattern?: (declaration: Resolved<D>) => string | undefined;\n extensions?: readonly DeclarationExtension<D>[];\n schema?: SyncSchema;\n}\n\nexport interface SurfaceRowPlan {\n name: string;\n /** Written only when the row does not exist yet. */\n insert: Record<string, Json>;\n /** Written on every sync (code owns these columns). */\n update: Record<string, Json>;\n}\n\nexport interface SyncTablePlan {\n table: string;\n /** The ON CONFLICT target (a unique index or primary key on exactly these columns). */\n conflict: readonly string[];\n /** Columns set on insert and never on conflict. */\n insertOnly: readonly string[];\n rows: Record<string, Json>[];\n}\n\nexport interface SurfaceSyncPlan {\n surfaces: SurfaceRowPlan[];\n tables: SyncTablePlan[];\n /**\n * The key (ON CONFLICT target) of every mirror table this plan owns,\n * including tables it emits no rows for. Stale detection and deletes use\n * exactly these columns (`findStaleRows`).\n */\n keys: Record<string, readonly string[]>;\n}\n\nfunction childKey(schema: SyncSchema | undefined): readonly string[] {\n return schema?.itemType ? [\"surface_name\", \"item_type\", \"name\"] : [\"surface_name\", \"name\"];\n}\n\nexport function planSurfaceSync<D extends SurfaceDeclaration>(\n declarations: readonly Resolved<D>[],\n options: SurfaceSyncPlanOptions<D>,\n): SurfaceSyncPlan {\n const { organizationId, syncedFrom, schema } = options;\n const syncedBy = options.syncedBy ?? null;\n const provenance = { synced_by: syncedBy, synced_from: syncedFrom };\n const governance = { organization_id: organizationId, published_to_web: true, declared_by: DECLARED_BY.code };\n const surfaces: SurfaceRowPlan[] = [];\n const values: Record<string, Json>[] = [];\n const targets: Record<string, Json>[] = [];\n const itemTypes: Record<string, Json>[] = [];\n const extensionTables = new Map<string, SyncTablePlan>();\n\n for (const d of declarations) {\n const urlPattern = d.urlPattern?.trim() || options.resolveUrlPattern?.(d) || null;\n const intro = d.intro?.trim() || null;\n const situation = d.situation?.trim() || null;\n const parent = d.inheritsFrom ?? null;\n const client = surfaceClientOf(d.surfaceName);\n const owned: Record<string, Json> = {\n client_name: client,\n execution_mode: d.executionMode,\n description: d.description,\n label: d.label,\n value_groups: (d.groups ?? []) as unknown as Json,\n readiness: d.readiness,\n readiness_note: d.readinessNote?.trim() || null,\n ...(schema?.contentHash ? { content_hash: d.contentHash } : {}),\n };\n // A declaration that does not claim these never clears a DB-authored value.\n const claimed: Record<string, Json> = {\n ...(d.overlayId ? { overlay_id: d.overlayId } : {}),\n ...(urlPattern ? { url_pattern: urlPattern } : {}),\n ...(intro ? { intro } : {}),\n ...(situation ? { situation } : {}),\n ...(parent ? { parent_surface_name: parent } : {}),\n };\n surfaces.push({\n name: d.surfaceName,\n insert: {\n // Insert-only ownership: `ui.ui_surface.organization_id` is NOT NULL since AP-2 (code rows → the\n // system organization); a new surface without it was refused by the database (AP-6, 2026-10-06).\n organization_id: organizationId,\n name: d.surfaceName,\n declared_by: DECLARED_BY.code,\n ...owned,\n overlay_id: d.overlayId ?? null,\n url_pattern: urlPattern,\n intro,\n situation,\n parent_surface_name: parent,\n },\n update: { ...owned, ...claimed },\n });\n\n for (const v of d.values) {\n values.push({\n ...governance,\n surface_name: d.surfaceName,\n ...(schema?.itemType ? { item_type: \"\" } : {}),\n name: v.name,\n label: v.label,\n description: v.description,\n value_type: v.valueType,\n always_available: v.alwaysAvailable,\n typical_char_count: v.typicalCharCount,\n sort_order: v.sortOrder ?? 1000,\n auto_context: v.autoContext ?? true,\n group_key: v.groupKey ?? v.group ?? \"general\",\n ...(schema?.valueContract\n ? {\n exportable: resolveSensitivity(v).exportable,\n classification: resolveSensitivity(v).classification,\n included_by_default: resolveSensitivity(v).includedByDefault,\n kind_key: v.valueKind ?? null,\n live_slot: v.liveSlot ?? null,\n role: v.role ?? \"data\",\n }\n : {}),\n ...provenance,\n });\n }\n\n for (const t of d.writeTargets ?? []) {\n targets.push({\n ...governance,\n surface_name: d.surfaceName,\n ...(schema?.itemType ? { item_type: t.itemType ?? \"\" } : {}),\n name: t.name,\n label: t.label,\n description: t.description,\n value_type: t.valueType,\n mode: t.mode,\n updates_value: t.updatesValue ?? null,\n group_key: t.group ?? \"general\",\n sort_order: t.sortOrder ?? 1000,\n apply_policy: t.applyPolicy ?? \"manual\",\n kind_key: t.valueKind ?? null,\n ...(schema?.valueContract\n ? {\n patchable: t.patchable ?? false,\n approval_comparison: t.approvalComparison ?? null,\n stream_ops: t.streamOps ? [...t.streamOps] : null,\n destination: t.destination ?? false,\n }\n : {}),\n ...provenance,\n });\n }\n\n if (schema?.itemType) {\n for (const [position, item] of (d.itemTypes ?? []).entries()) {\n itemTypes.push({\n ...governance,\n surface_name: d.surfaceName,\n name: item.name,\n label: item.label,\n description: item.description,\n identity: [...item.identity],\n levels: item.levels ? [...item.levels] : null,\n entity_type: item.entityType ?? null,\n sort_order: position,\n ...provenance,\n });\n for (const v of item.values) {\n values.push({\n ...governance,\n surface_name: d.surfaceName,\n item_type: item.name,\n name: v.name,\n label: v.label,\n description: v.description,\n value_type: v.valueType,\n always_available: v.alwaysAvailable,\n typical_char_count: v.typicalCharCount,\n sort_order: v.sortOrder ?? 1000,\n auto_context: v.autoContext ?? true,\n group_key: v.group ?? \"general\",\n // Every row of one INSERT carries the same columns: a screen value with the value\n // contract makes the statement name these, so an item value that left them out was\n // sent as NULL into NOT NULL columns and the whole sync refused (2026-10-07).\n ...(schema?.valueContract\n ? {\n exportable: resolveSensitivity(v).exportable,\n classification: resolveSensitivity(v).classification,\n included_by_default: resolveSensitivity(v).includedByDefault,\n kind_key: v.valueKind ?? null,\n live_slot: v.liveSlot ?? null,\n role: v.role ?? \"data\",\n }\n : {}),\n ...provenance,\n });\n }\n }\n }\n\n for (const extension of options.extensions ?? []) {\n for (const entry of extension.toRows?.(d as unknown as D) ?? []) {\n const plan = extensionTables.get(entry.table) ?? {\n table: entry.table,\n conflict: entry.conflict,\n insertOnly: INSERT_ONLY_COLUMNS,\n rows: [],\n };\n plan.rows.push({ ...governance, ...entry.row, ...provenance });\n extensionTables.set(entry.table, plan);\n }\n }\n }\n\n const keys: Record<string, readonly string[]> = {\n \"ui.ui_surface_value\": childKey(schema),\n \"ui.ui_surface_write_target\": childKey(schema),\n ...(schema?.itemType ? { \"ui.ui_surface_item_type\": [\"surface_name\", \"name\"] } : {}),\n // ui.ui_surface_action is claimed ONLY when a code declaration emits Action rows: until code\n // declares Actions, every live Action row is database-owned, and claiming the table would read\n // a future code Action as stale. Unclaimed, a stale check on it throws by name instead.\n ...(extensionTables.has(SURFACE_ACTION_TABLE) ? { [SURFACE_ACTION_TABLE]: SURFACE_ACTION_KEY } : {}),\n };\n for (const extension of options.extensions ?? []) {\n if (extension.mirror) keys[extension.mirror.table] = extension.mirror.conflict;\n }\n for (const plan of extensionTables.values()) keys[plan.table] ??= plan.conflict;\n\n const tables: SyncTablePlan[] = [\n { table: \"ui.ui_surface_value\", conflict: childKey(schema), insertOnly: INSERT_ONLY_COLUMNS, rows: values },\n { table: \"ui.ui_surface_write_target\", conflict: childKey(schema), insertOnly: INSERT_ONLY_COLUMNS, rows: targets },\n ...(schema?.itemType\n ? [{ table: \"ui.ui_surface_item_type\", conflict: [\"surface_name\", \"name\"], insertOnly: INSERT_ONLY_COLUMNS, rows: itemTypes }]\n : []),\n ...extensionTables.values(),\n ];\n return { surfaces, tables: tables.filter((table) => table.rows.length > 0), keys };\n}\n\n/** One row's key under `columns` (a missing column reads as ''). */\nexport function mirrorRowKey(row: Readonly<Record<string, unknown>>, columns: readonly string[]): string {\n return columns.map((column) => String(row[column] ?? \"\")).join(\"\\u0000\");\n}\n\n/**\n * Rows of `table` that belong to a surface this plan manages but that the\n * plan no longer declares, judged by the table's full key (`plan.keys`) — so\n * a screen value and an item value that share a name are distinct rows. The\n * caller deletes each returned row filtered by every column of that key.\n * A row whose `declared_by` is \"database\" is never returned (pass the column\n * in `existing`, and delete with `declared_by = 'code'` as well).\n */\nexport function findStaleRows<R extends Readonly<Record<string, unknown>>>(\n plan: SurfaceSyncPlan,\n table: string,\n existing: readonly R[],\n): R[] {\n const key = plan.keys[table];\n if (!key) {\n throw new Error(\n `Sync plan declares no key for \"${table}\"; give the extension that owns it a \\`mirror\\` entry.`,\n );\n }\n const managed = new Set(plan.surfaces.map((surface) => surface.name));\n const declared = new Set(\n (plan.tables.find((entry) => entry.table === table)?.rows ?? []).map((row) => mirrorRowKey(row, key)),\n );\n return existing.filter(\n (row) => !isDatabaseDeclared(row) && managed.has(String(row.surface_name)) && !declared.has(mirrorRowKey(row, key)),\n );\n}\n\n/** Every column a table's rows carry, in first-seen order. */\nexport function planColumns(table: SyncTablePlan): string[] {\n const columns: string[] = [];\n for (const row of table.rows) for (const key of Object.keys(row)) if (!columns.includes(key)) columns.push(key);\n return columns;\n}\n\n/** Columns a conflict rewrites: everything except the key and the insert-only set. */\nexport function conflictUpdateColumns(table: SyncTablePlan): string[] {\n return planColumns(table).filter((c) => !table.conflict.includes(c) && !table.insertOnly.includes(c));\n}\n\nexport function sqlLiteral(value: Json | undefined): string {\n if (value === null || value === undefined) return \"NULL\";\n if (typeof value === \"boolean\" || typeof value === \"number\") return String(value);\n if (typeof value === \"string\") return `'${value.replace(/'/g, \"''\")}'`;\n if (Array.isArray(value) && value.every((entry) => typeof entry === \"string\")) {\n return `ARRAY[${value.map((entry) => sqlLiteral(entry)).join(\", \")}]::text[]`;\n }\n return `'${JSON.stringify(value).replace(/'/g, \"''\")}'::jsonb`;\n}\n\n/**\n * The plan as SQL statements, one per entry: surface insert, one UPDATE per\n * surface, one upsert per table. Callers may run them, or EXPLAIN each one in a\n * read-only transaction to prove the plan against a live schema with no write.\n */\nexport function renderSurfaceSyncStatements(plan: SurfaceSyncPlan): string[] {\n const statements: string[] = [];\n if (plan.surfaces.length > 0) {\n const insertColumns = Object.keys(plan.surfaces[0]!.insert);\n statements.push(\n `INSERT INTO ui.ui_surface (${insertColumns.join(\", \")}) VALUES\\n${plan.surfaces\n .map((s) => `(${insertColumns.map((c) => sqlLiteral(s.insert[c])).join(\", \")})`)\n .join(\",\\n\")}\\nON CONFLICT (name) DO NOTHING`,\n );\n for (const s of plan.surfaces) {\n statements.push(\n `UPDATE ui.ui_surface SET ${Object.entries(s.update)\n .map(([c, v]) => `${c} = ${sqlLiteral(v)}`)\n .join(\", \")}, updated_at = now() WHERE name = ${sqlLiteral(s.name)}`,\n );\n }\n }\n for (const table of plan.tables) {\n const columns = planColumns(table);\n const sets = conflictUpdateColumns(table).map((c) => `${c} = EXCLUDED.${c}`);\n statements.push(\n `INSERT INTO ${table.table} (${columns.join(\", \")}) VALUES\\n${table.rows\n .map((row) => `(${columns.map((c) => sqlLiteral(row[c])).join(\", \")})`)\n .join(\",\\n\")}\\nON CONFLICT (${table.conflict.join(\", \")}) DO UPDATE SET ${[...sets, \"updated_at = now()\"].join(\", \")}`,\n );\n }\n return statements;\n}\n\n/** The plan as one SQL script (governance columns insert-only). */\nexport function renderSurfaceSyncSql(plan: SurfaceSyncPlan): string {\n return renderSurfaceSyncStatements(plan)\n .map((statement) => `${statement};`)\n .join(\"\\n\\n\");\n}\n","/**\n * The declaration half of the route check (CONTRACT §2.6 `/checks`): does each\n * declared `urlPattern` address a live route? The host supplies its route list\n * (it owns the filesystem); the matching rule lives here, once. Ported from\n * matrx-frontend `scripts/check-surface-routes.ts` (ALC-14).\n */\n\n/** Route or pattern → segments; `[x]` / `:x` → \"*\", `[...x]` / `[[...x]]` → \"**\". */\nexport function routeSegments(path: string): string[] {\n return (path.split(\"?\")[0] ?? \"\")\n .split(\"#\")[0]!\n .split(\"/\")\n .filter(Boolean)\n .map((segment) => {\n if (/^\\[\\[?\\.\\.\\..+\\]\\]?$/.test(segment)) return \"**\";\n if (segment.startsWith(\"[\") || segment.startsWith(\":\")) return \"*\";\n return segment;\n });\n}\n\n/**\n * Does a declared `urlPattern` address a live route? A literal pattern segment\n * must be a literal route segment — a dynamic route segment does NOT satisfy it,\n * or `/marketing/[brandId]/…` would \"cover\" a retired `/marketing/brands/…`.\n * A trailing `*` makes the pattern a prefix.\n */\nexport function urlPatternMatchesRoute(pattern: string, route: readonly string[]): boolean {\n const isPrefix = pattern.trimEnd().endsWith(\"*\") && !pattern.endsWith(\"]*\");\n const segments = routeSegments(pattern.replace(/\\*+\\s*$/, \"\"));\n for (let i = 0; i < segments.length; i += 1) {\n const routeSegment = route[i];\n if (routeSegment === \"**\") return true;\n if (routeSegment === undefined) return false;\n const declared = segments[i];\n if (declared === \"**\") return true;\n if (declared === \"*\" ? routeSegment !== \"*\" : routeSegment !== declared) return false;\n }\n return isPrefix || route.length === segments.length;\n}\n\n/** Every declaration whose `urlPattern` no live route can produce. */\nexport function findDeadUrlPatterns(\n declarations: readonly { surfaceName: string; urlPattern?: string | undefined }[],\n liveRoutes: readonly string[],\n): { surfaceName: string; urlPattern: string }[] {\n const routes = liveRoutes.map(routeSegments);\n const dead: { surfaceName: string; urlPattern: string }[] = [];\n for (const declaration of declarations) {\n const pattern = declaration.urlPattern;\n if (!pattern) continue;\n if (!routes.some((route) => urlPatternMatchesRoute(pattern, route))) {\n dead.push({ surfaceName: declaration.surfaceName, urlPattern: pattern });\n }\n }\n return dead;\n}\n"],"mappings":"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","names":[]}
package/dist/checks.js CHANGED
@@ -1,4 +1,4 @@
1
- function surfaceClientOf(surfaceName){return surfaceName.split("/")[0]??""}var DEFAULT_SENSITIVITY=Object.freeze({exportable:!0,classification:"ordinary",includedByDefault:!0});function resolveSensitivity(value){return{exportable:value.sensitivity?.exportable??DEFAULT_SENSITIVITY.exportable,classification:value.sensitivity?.classification??DEFAULT_SENSITIVITY.classification,includedByDefault:value.sensitivity?.includedByDefault??DEFAULT_SENSITIVITY.includedByDefault}}var GOVERNANCE_COLUMNS=["organization_id","published_to_web"],DECLARED_BY={code:"code",database:"database"},INSERT_ONLY_COLUMNS=[...GOVERNANCE_COLUMNS,"declared_by"],SURFACE_ACTION_TABLE="ui.ui_surface_action",SURFACE_ACTION_KEY=["surface_name","item_type","name"];function isDatabaseDeclared(row){return row.declared_by===DECLARED_BY.database}function childKey(schema){return schema?.itemType?["surface_name","item_type","name"]:["surface_name","name"]}function planSurfaceSync(declarations,options){let{organizationId,syncedFrom,schema}=options,provenance={synced_by:options.syncedBy??null,synced_from:syncedFrom},governance={organization_id:organizationId,published_to_web:!0,declared_by:DECLARED_BY.code},surfaces=[],values=[],targets=[],itemTypes=[],extensionTables=new Map;for(let d of declarations){let urlPattern=d.urlPattern?.trim()||options.resolveUrlPattern?.(d)||null,intro=d.intro?.trim()||null,situation=d.situation?.trim()||null,parent=d.inheritsFrom??null,owned={client_name:surfaceClientOf(d.surfaceName),execution_mode:d.executionMode,description:d.description,label:d.label,value_groups:d.groups??[],readiness:d.readiness,readiness_note:d.readinessNote?.trim()||null,...schema?.contentHash?{content_hash:d.contentHash}:{}},claimed={...d.overlayId?{overlay_id:d.overlayId}:{},...urlPattern?{url_pattern:urlPattern}:{},...intro?{intro}:{},...situation?{situation}:{},...parent?{parent_surface_name:parent}:{}};surfaces.push({name:d.surfaceName,insert:{organization_id:organizationId,name:d.surfaceName,declared_by:DECLARED_BY.code,...owned,overlay_id:d.overlayId??null,url_pattern:urlPattern,intro,situation,parent_surface_name:parent},update:{...owned,...claimed}});for(let v of d.values)values.push({...governance,surface_name:d.surfaceName,...schema?.itemType?{item_type:""}:{},name:v.name,label:v.label,description:v.description,value_type:v.valueType,always_available:v.alwaysAvailable,typical_char_count:v.typicalCharCount,sort_order:v.sortOrder??1e3,auto_context:v.autoContext??!0,group_key:v.groupKey??v.group??"general",...schema?.valueContract?{exportable:resolveSensitivity(v).exportable,classification:resolveSensitivity(v).classification,included_by_default:resolveSensitivity(v).includedByDefault,kind_key:v.valueKind??null,live_slot:v.liveSlot??null,role:v.role??"data"}:{},...provenance});for(let t of d.writeTargets??[])targets.push({...governance,surface_name:d.surfaceName,...schema?.itemType?{item_type:t.itemType??""}:{},name:t.name,label:t.label,description:t.description,value_type:t.valueType,mode:t.mode,updates_value:t.updatesValue??null,group_key:t.group??"general",sort_order:t.sortOrder??1e3,apply_policy:t.applyPolicy??"manual",kind_key:t.valueKind??null,...schema?.valueContract?{patchable:t.patchable??!1,approval_comparison:t.approvalComparison??null,stream_ops:t.streamOps?[...t.streamOps]:null,destination:t.destination??!1}:{},...provenance});if(schema?.itemType)for(let[position,item]of(d.itemTypes??[]).entries()){itemTypes.push({...governance,surface_name:d.surfaceName,name:item.name,label:item.label,description:item.description,identity:[...item.identity],levels:item.levels?[...item.levels]:null,entity_type:item.entityType??null,sort_order:position,...provenance});for(let v of item.values)values.push({...governance,surface_name:d.surfaceName,item_type:item.name,name:v.name,label:v.label,description:v.description,value_type:v.valueType,always_available:v.alwaysAvailable,typical_char_count:v.typicalCharCount,sort_order:v.sortOrder??1e3,auto_context:v.autoContext??!0,group_key:v.group??"general",...provenance})}for(let extension of options.extensions??[])for(let entry of extension.toRows?.(d)??[]){let plan=extensionTables.get(entry.table)??{table:entry.table,conflict:entry.conflict,insertOnly:INSERT_ONLY_COLUMNS,rows:[]};plan.rows.push({...governance,...entry.row,...provenance}),extensionTables.set(entry.table,plan)}}let keys={"ui.ui_surface_value":childKey(schema),"ui.ui_surface_write_target":childKey(schema),...schema?.itemType?{"ui.ui_surface_item_type":["surface_name","name"]}:{},...extensionTables.has(SURFACE_ACTION_TABLE)?{[SURFACE_ACTION_TABLE]:SURFACE_ACTION_KEY}:{}};for(let extension of options.extensions??[])extension.mirror&&(keys[extension.mirror.table]=extension.mirror.conflict);for(let plan of extensionTables.values())keys[plan.table]??=plan.conflict;let tables=[{table:"ui.ui_surface_value",conflict:childKey(schema),insertOnly:INSERT_ONLY_COLUMNS,rows:values},{table:"ui.ui_surface_write_target",conflict:childKey(schema),insertOnly:INSERT_ONLY_COLUMNS,rows:targets},...schema?.itemType?[{table:"ui.ui_surface_item_type",conflict:["surface_name","name"],insertOnly:INSERT_ONLY_COLUMNS,rows:itemTypes}]:[],...extensionTables.values()];return{surfaces,tables:tables.filter(table=>table.rows.length>0),keys}}function mirrorRowKey(row,columns){return columns.map(column=>String(row[column]??"")).join("\0")}function findStaleRows(plan,table,existing){let key=plan.keys[table];if(!key)throw new Error(`Sync plan declares no key for "${table}"; give the extension that owns it a \`mirror\` entry.`);let managed=new Set(plan.surfaces.map(surface=>surface.name)),declared=new Set((plan.tables.find(entry=>entry.table===table)?.rows??[]).map(row=>mirrorRowKey(row,key)));return existing.filter(row=>!isDatabaseDeclared(row)&&managed.has(String(row.surface_name))&&!declared.has(mirrorRowKey(row,key)))}function planColumns(table){let columns=[];for(let row of table.rows)for(let key of Object.keys(row))columns.includes(key)||columns.push(key);return columns}function conflictUpdateColumns(table){return planColumns(table).filter(c=>!table.conflict.includes(c)&&!table.insertOnly.includes(c))}function sqlLiteral(value){return value==null?"NULL":typeof value=="boolean"||typeof value=="number"?String(value):typeof value=="string"?`'${value.replace(/'/g,"''")}'`:Array.isArray(value)&&value.every(entry=>typeof entry=="string")?`ARRAY[${value.map(entry=>sqlLiteral(entry)).join(", ")}]::text[]`:`'${JSON.stringify(value).replace(/'/g,"''")}'::jsonb`}function renderSurfaceSyncStatements(plan){let statements=[];if(plan.surfaces.length>0){let insertColumns=Object.keys(plan.surfaces[0].insert);statements.push(`INSERT INTO ui.ui_surface (${insertColumns.join(", ")}) VALUES
1
+ function surfaceClientOf(surfaceName){return surfaceName.split("/")[0]??""}var DEFAULT_SENSITIVITY=Object.freeze({exportable:!0,classification:"ordinary",includedByDefault:!0});function resolveSensitivity(value){return{exportable:value.sensitivity?.exportable??DEFAULT_SENSITIVITY.exportable,classification:value.sensitivity?.classification??DEFAULT_SENSITIVITY.classification,includedByDefault:value.sensitivity?.includedByDefault??DEFAULT_SENSITIVITY.includedByDefault}}var GOVERNANCE_COLUMNS=["organization_id","published_to_web"],DECLARED_BY={code:"code",database:"database"},INSERT_ONLY_COLUMNS=[...GOVERNANCE_COLUMNS,"declared_by"],SURFACE_ACTION_TABLE="ui.ui_surface_action",SURFACE_ACTION_KEY=["surface_name","item_type","name"];function isDatabaseDeclared(row){return row.declared_by===DECLARED_BY.database}function childKey(schema){return schema?.itemType?["surface_name","item_type","name"]:["surface_name","name"]}function planSurfaceSync(declarations,options){let{organizationId,syncedFrom,schema}=options,provenance={synced_by:options.syncedBy??null,synced_from:syncedFrom},governance={organization_id:organizationId,published_to_web:!0,declared_by:DECLARED_BY.code},surfaces=[],values=[],targets=[],itemTypes=[],extensionTables=new Map;for(let d of declarations){let urlPattern=d.urlPattern?.trim()||options.resolveUrlPattern?.(d)||null,intro=d.intro?.trim()||null,situation=d.situation?.trim()||null,parent=d.inheritsFrom??null,owned={client_name:surfaceClientOf(d.surfaceName),execution_mode:d.executionMode,description:d.description,label:d.label,value_groups:d.groups??[],readiness:d.readiness,readiness_note:d.readinessNote?.trim()||null,...schema?.contentHash?{content_hash:d.contentHash}:{}},claimed={...d.overlayId?{overlay_id:d.overlayId}:{},...urlPattern?{url_pattern:urlPattern}:{},...intro?{intro}:{},...situation?{situation}:{},...parent?{parent_surface_name:parent}:{}};surfaces.push({name:d.surfaceName,insert:{organization_id:organizationId,name:d.surfaceName,declared_by:DECLARED_BY.code,...owned,overlay_id:d.overlayId??null,url_pattern:urlPattern,intro,situation,parent_surface_name:parent},update:{...owned,...claimed}});for(let v of d.values)values.push({...governance,surface_name:d.surfaceName,...schema?.itemType?{item_type:""}:{},name:v.name,label:v.label,description:v.description,value_type:v.valueType,always_available:v.alwaysAvailable,typical_char_count:v.typicalCharCount,sort_order:v.sortOrder??1e3,auto_context:v.autoContext??!0,group_key:v.groupKey??v.group??"general",...schema?.valueContract?{exportable:resolveSensitivity(v).exportable,classification:resolveSensitivity(v).classification,included_by_default:resolveSensitivity(v).includedByDefault,kind_key:v.valueKind??null,live_slot:v.liveSlot??null,role:v.role??"data"}:{},...provenance});for(let t of d.writeTargets??[])targets.push({...governance,surface_name:d.surfaceName,...schema?.itemType?{item_type:t.itemType??""}:{},name:t.name,label:t.label,description:t.description,value_type:t.valueType,mode:t.mode,updates_value:t.updatesValue??null,group_key:t.group??"general",sort_order:t.sortOrder??1e3,apply_policy:t.applyPolicy??"manual",kind_key:t.valueKind??null,...schema?.valueContract?{patchable:t.patchable??!1,approval_comparison:t.approvalComparison??null,stream_ops:t.streamOps?[...t.streamOps]:null,destination:t.destination??!1}:{},...provenance});if(schema?.itemType)for(let[position,item]of(d.itemTypes??[]).entries()){itemTypes.push({...governance,surface_name:d.surfaceName,name:item.name,label:item.label,description:item.description,identity:[...item.identity],levels:item.levels?[...item.levels]:null,entity_type:item.entityType??null,sort_order:position,...provenance});for(let v of item.values)values.push({...governance,surface_name:d.surfaceName,item_type:item.name,name:v.name,label:v.label,description:v.description,value_type:v.valueType,always_available:v.alwaysAvailable,typical_char_count:v.typicalCharCount,sort_order:v.sortOrder??1e3,auto_context:v.autoContext??!0,group_key:v.group??"general",...schema?.valueContract?{exportable:resolveSensitivity(v).exportable,classification:resolveSensitivity(v).classification,included_by_default:resolveSensitivity(v).includedByDefault,kind_key:v.valueKind??null,live_slot:v.liveSlot??null,role:v.role??"data"}:{},...provenance})}for(let extension of options.extensions??[])for(let entry of extension.toRows?.(d)??[]){let plan=extensionTables.get(entry.table)??{table:entry.table,conflict:entry.conflict,insertOnly:INSERT_ONLY_COLUMNS,rows:[]};plan.rows.push({...governance,...entry.row,...provenance}),extensionTables.set(entry.table,plan)}}let keys={"ui.ui_surface_value":childKey(schema),"ui.ui_surface_write_target":childKey(schema),...schema?.itemType?{"ui.ui_surface_item_type":["surface_name","name"]}:{},...extensionTables.has(SURFACE_ACTION_TABLE)?{[SURFACE_ACTION_TABLE]:SURFACE_ACTION_KEY}:{}};for(let extension of options.extensions??[])extension.mirror&&(keys[extension.mirror.table]=extension.mirror.conflict);for(let plan of extensionTables.values())keys[plan.table]??=plan.conflict;let tables=[{table:"ui.ui_surface_value",conflict:childKey(schema),insertOnly:INSERT_ONLY_COLUMNS,rows:values},{table:"ui.ui_surface_write_target",conflict:childKey(schema),insertOnly:INSERT_ONLY_COLUMNS,rows:targets},...schema?.itemType?[{table:"ui.ui_surface_item_type",conflict:["surface_name","name"],insertOnly:INSERT_ONLY_COLUMNS,rows:itemTypes}]:[],...extensionTables.values()];return{surfaces,tables:tables.filter(table=>table.rows.length>0),keys}}function mirrorRowKey(row,columns){return columns.map(column=>String(row[column]??"")).join("\0")}function findStaleRows(plan,table,existing){let key=plan.keys[table];if(!key)throw new Error(`Sync plan declares no key for "${table}"; give the extension that owns it a \`mirror\` entry.`);let managed=new Set(plan.surfaces.map(surface=>surface.name)),declared=new Set((plan.tables.find(entry=>entry.table===table)?.rows??[]).map(row=>mirrorRowKey(row,key)));return existing.filter(row=>!isDatabaseDeclared(row)&&managed.has(String(row.surface_name))&&!declared.has(mirrorRowKey(row,key)))}function planColumns(table){let columns=[];for(let row of table.rows)for(let key of Object.keys(row))columns.includes(key)||columns.push(key);return columns}function conflictUpdateColumns(table){return planColumns(table).filter(c=>!table.conflict.includes(c)&&!table.insertOnly.includes(c))}function sqlLiteral(value){return value==null?"NULL":typeof value=="boolean"||typeof value=="number"?String(value):typeof value=="string"?`'${value.replace(/'/g,"''")}'`:Array.isArray(value)&&value.every(entry=>typeof entry=="string")?`ARRAY[${value.map(entry=>sqlLiteral(entry)).join(", ")}]::text[]`:`'${JSON.stringify(value).replace(/'/g,"''")}'::jsonb`}function renderSurfaceSyncStatements(plan){let statements=[];if(plan.surfaces.length>0){let insertColumns=Object.keys(plan.surfaces[0].insert);statements.push(`INSERT INTO ui.ui_surface (${insertColumns.join(", ")}) VALUES
2
2
  ${plan.surfaces.map(s=>`(${insertColumns.map(c=>sqlLiteral(s.insert[c])).join(", ")})`).join(`,
3
3
  `)}
4
4
  ON CONFLICT (name) DO NOTHING`);for(let s of plan.surfaces)statements.push(`UPDATE ui.ui_surface SET ${Object.entries(s.update).map(([c,v])=>`${c} = ${sqlLiteral(v)}`).join(", ")}, updated_at = now() WHERE name = ${sqlLiteral(s.name)}`)}for(let table of plan.tables){let columns=planColumns(table),sets=conflictUpdateColumns(table).map(c=>`${c} = EXCLUDED.${c}`);statements.push(`INSERT INTO ${table.table} (${columns.join(", ")}) VALUES
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/declare/keys.ts","../src/declare/sensitivity.ts","../src/checks/sync.ts","../src/checks/routes.ts"],"sourcesContent":["import type { QualifiedValueKey } from \"./types\";\n\n/** Every value, write-target, item-type and group name (the DB CHECK rule). */\nexport const DECLARATION_NAME_RE = /^[a-z][a-z0-9_]*$/;\n\n/** \"<client>/<local>\" — the surface naming convention. */\nexport const SURFACE_NAME_RE = /^[a-z][a-z0-9-]*\\/[a-z0-9-/]+$/;\n\n/** A bare screen value (`status`) or an item value (`table_row.status`). */\nexport const QUALIFIED_VALUE_KEY_RE = /^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)?$/;\n\nexport interface ParsedValueKey {\n /** \"\" for a screen-level value. */\n itemType: string;\n name: string;\n}\n\n/** Split a qualified key; null when it is not a valid bare or qualified key. */\nexport function parseQualifiedValueKey(key: string): ParsedValueKey | null {\n if (!QUALIFIED_VALUE_KEY_RE.test(key)) return null;\n const dot = key.indexOf(\".\");\n if (dot === -1) return { itemType: \"\", name: key };\n return { itemType: key.slice(0, dot), name: key.slice(dot + 1) };\n}\n\nexport function qualifyValueKey(itemType: string, name: string): QualifiedValueKey {\n return itemType ? `${itemType}.${name}` : name;\n}\n\n/** The client that owns a surface: its name prefix. */\nexport function surfaceClientOf(surfaceName: string): string {\n return surfaceName.split(\"/\")[0] ?? \"\";\n}\n","/**\n * Sensitivity defaults and resolution (CONTRACT §2.1). Its own module so the\n * lightweight entries (`/actions`, the T0 React host) can project without\n * pulling the declaration registry.\n */\nimport type { DeclaredValue, Sensitivity } from \"./types\";\n\nexport const DEFAULT_SENSITIVITY: Sensitivity = Object.freeze({\n exportable: true,\n classification: \"ordinary\",\n includedByDefault: true,\n});\n\nexport function resolveSensitivity(value: Pick<DeclaredValue, \"sensitivity\">): Sensitivity {\n return {\n exportable: value.sensitivity?.exportable ?? DEFAULT_SENSITIVITY.exportable,\n classification: value.sensitivity?.classification ?? DEFAULT_SENSITIVITY.classification,\n includedByDefault: value.sensitivity?.includedByDefault ?? DEFAULT_SENSITIVITY.includedByDefault,\n };\n}\n","/**\n * THE ONE SYNC (CONTRACT §2.7 \"One sync path\", chair ruling B1).\n *\n * `planSurfaceSync` maps resolved declarations onto the `ui` mirror rows; every\n * sync path (the SQL emitter and the admin sync service) executes this plan and\n * builds no rows of its own. Governance columns (`organization_id`,\n * `published_to_web`) are INSERT-ONLY (chair ruling N5): a conflict never rewrites them.\n * Mirror rows are published to the web on insert, written explicitly (access ladder\n * T-13: never left to a column default); the database stamps when and by whom.\n *\n * Schema generations: the columns added by the ALC-14 migration (item_type,\n * sensitivity, live slots, content_hash) are emitted only when the caller says\n * that migration is live (`schema`), so no emitter ever writes a column — or\n * upserts against a key — the database does not have yet.\n */\nimport { resolveSensitivity, type Resolved } from \"../declare/registry\";\nimport { surfaceClientOf } from \"../declare/keys\";\nimport type { DeclarationExtension, Json, SurfaceDeclaration } from \"../declare/types\";\n\nexport const GOVERNANCE_COLUMNS = [\"organization_id\", \"published_to_web\"] as const;\n\n/**\n * WHO DECLARED A ROW (Applets CONTRACTS §6, PLAN ruling 7). Every `ui.ui_surface*`\n * row is `declared_by` \"code\" or \"database\". This sync writes only code rows: it\n * stamps \"code\" on insert and never rewrites the column on conflict (a database\n * trigger refuses a code takeover of a database row anyway); stale and orphan\n * checks never return a database row.\n */\nexport const DECLARED_BY = { code: \"code\", database: \"database\" } as const;\nexport type DeclaredBy = (typeof DECLARED_BY)[keyof typeof DECLARED_BY];\n\n/** Set on insert, never on conflict: governance plus `declared_by`. */\nexport const INSERT_ONLY_COLUMNS = [...GOVERNANCE_COLUMNS, \"declared_by\"] as const;\n\n/** The Actions mirror. Always keyed with item_type (the table was born re-keyed). */\nexport const SURFACE_ACTION_TABLE = \"ui.ui_surface_action\";\nexport const SURFACE_ACTION_KEY = [\"surface_name\", \"item_type\", \"name\"] as const;\n\n/** True for a row an organization authored in the database — code sync never touches it. */\nexport function isDatabaseDeclared(row: Readonly<Record<string, unknown>>): boolean {\n return row.declared_by === DECLARED_BY.database;\n}\n\nexport interface SyncSchema {\n /** `ui_surface_value/write_target.item_type` exists and the conflict key includes it (re-key step 1). */\n itemType?: boolean;\n /** Sensitivity, kind, live slot and role columns exist on `ui_surface_value`. */\n valueContract?: boolean;\n /** `ui_surface.content_hash` exists. */\n contentHash?: boolean;\n}\n\nexport interface SurfaceSyncPlanOptions<D extends SurfaceDeclaration> {\n organizationId: string;\n syncedFrom: string;\n syncedBy?: string | null;\n /** The address the host derives for a declaration without `urlPattern`. */\n resolveUrlPattern?: (declaration: Resolved<D>) => string | undefined;\n extensions?: readonly DeclarationExtension<D>[];\n schema?: SyncSchema;\n}\n\nexport interface SurfaceRowPlan {\n name: string;\n /** Written only when the row does not exist yet. */\n insert: Record<string, Json>;\n /** Written on every sync (code owns these columns). */\n update: Record<string, Json>;\n}\n\nexport interface SyncTablePlan {\n table: string;\n /** The ON CONFLICT target (a unique index or primary key on exactly these columns). */\n conflict: readonly string[];\n /** Columns set on insert and never on conflict. */\n insertOnly: readonly string[];\n rows: Record<string, Json>[];\n}\n\nexport interface SurfaceSyncPlan {\n surfaces: SurfaceRowPlan[];\n tables: SyncTablePlan[];\n /**\n * The key (ON CONFLICT target) of every mirror table this plan owns,\n * including tables it emits no rows for. Stale detection and deletes use\n * exactly these columns (`findStaleRows`).\n */\n keys: Record<string, readonly string[]>;\n}\n\nfunction childKey(schema: SyncSchema | undefined): readonly string[] {\n return schema?.itemType ? [\"surface_name\", \"item_type\", \"name\"] : [\"surface_name\", \"name\"];\n}\n\nexport function planSurfaceSync<D extends SurfaceDeclaration>(\n declarations: readonly Resolved<D>[],\n options: SurfaceSyncPlanOptions<D>,\n): SurfaceSyncPlan {\n const { organizationId, syncedFrom, schema } = options;\n const syncedBy = options.syncedBy ?? null;\n const provenance = { synced_by: syncedBy, synced_from: syncedFrom };\n const governance = { organization_id: organizationId, published_to_web: true, declared_by: DECLARED_BY.code };\n const surfaces: SurfaceRowPlan[] = [];\n const values: Record<string, Json>[] = [];\n const targets: Record<string, Json>[] = [];\n const itemTypes: Record<string, Json>[] = [];\n const extensionTables = new Map<string, SyncTablePlan>();\n\n for (const d of declarations) {\n const urlPattern = d.urlPattern?.trim() || options.resolveUrlPattern?.(d) || null;\n const intro = d.intro?.trim() || null;\n const situation = d.situation?.trim() || null;\n const parent = d.inheritsFrom ?? null;\n const client = surfaceClientOf(d.surfaceName);\n const owned: Record<string, Json> = {\n client_name: client,\n execution_mode: d.executionMode,\n description: d.description,\n label: d.label,\n value_groups: (d.groups ?? []) as unknown as Json,\n readiness: d.readiness,\n readiness_note: d.readinessNote?.trim() || null,\n ...(schema?.contentHash ? { content_hash: d.contentHash } : {}),\n };\n // A declaration that does not claim these never clears a DB-authored value.\n const claimed: Record<string, Json> = {\n ...(d.overlayId ? { overlay_id: d.overlayId } : {}),\n ...(urlPattern ? { url_pattern: urlPattern } : {}),\n ...(intro ? { intro } : {}),\n ...(situation ? { situation } : {}),\n ...(parent ? { parent_surface_name: parent } : {}),\n };\n surfaces.push({\n name: d.surfaceName,\n insert: {\n // Insert-only ownership: `ui.ui_surface.organization_id` is NOT NULL since AP-2 (code rows → the\n // system organization); a new surface without it was refused by the database (AP-6, 2026-10-06).\n organization_id: organizationId,\n name: d.surfaceName,\n declared_by: DECLARED_BY.code,\n ...owned,\n overlay_id: d.overlayId ?? null,\n url_pattern: urlPattern,\n intro,\n situation,\n parent_surface_name: parent,\n },\n update: { ...owned, ...claimed },\n });\n\n for (const v of d.values) {\n values.push({\n ...governance,\n surface_name: d.surfaceName,\n ...(schema?.itemType ? { item_type: \"\" } : {}),\n name: v.name,\n label: v.label,\n description: v.description,\n value_type: v.valueType,\n always_available: v.alwaysAvailable,\n typical_char_count: v.typicalCharCount,\n sort_order: v.sortOrder ?? 1000,\n auto_context: v.autoContext ?? true,\n group_key: v.groupKey ?? v.group ?? \"general\",\n ...(schema?.valueContract\n ? {\n exportable: resolveSensitivity(v).exportable,\n classification: resolveSensitivity(v).classification,\n included_by_default: resolveSensitivity(v).includedByDefault,\n kind_key: v.valueKind ?? null,\n live_slot: v.liveSlot ?? null,\n role: v.role ?? \"data\",\n }\n : {}),\n ...provenance,\n });\n }\n\n for (const t of d.writeTargets ?? []) {\n targets.push({\n ...governance,\n surface_name: d.surfaceName,\n ...(schema?.itemType ? { item_type: t.itemType ?? \"\" } : {}),\n name: t.name,\n label: t.label,\n description: t.description,\n value_type: t.valueType,\n mode: t.mode,\n updates_value: t.updatesValue ?? null,\n group_key: t.group ?? \"general\",\n sort_order: t.sortOrder ?? 1000,\n apply_policy: t.applyPolicy ?? \"manual\",\n kind_key: t.valueKind ?? null,\n ...(schema?.valueContract\n ? {\n patchable: t.patchable ?? false,\n approval_comparison: t.approvalComparison ?? null,\n stream_ops: t.streamOps ? [...t.streamOps] : null,\n destination: t.destination ?? false,\n }\n : {}),\n ...provenance,\n });\n }\n\n if (schema?.itemType) {\n for (const [position, item] of (d.itemTypes ?? []).entries()) {\n itemTypes.push({\n ...governance,\n surface_name: d.surfaceName,\n name: item.name,\n label: item.label,\n description: item.description,\n identity: [...item.identity],\n levels: item.levels ? [...item.levels] : null,\n entity_type: item.entityType ?? null,\n sort_order: position,\n ...provenance,\n });\n for (const v of item.values) {\n values.push({\n ...governance,\n surface_name: d.surfaceName,\n item_type: item.name,\n name: v.name,\n label: v.label,\n description: v.description,\n value_type: v.valueType,\n always_available: v.alwaysAvailable,\n typical_char_count: v.typicalCharCount,\n sort_order: v.sortOrder ?? 1000,\n auto_context: v.autoContext ?? true,\n group_key: v.group ?? \"general\",\n ...provenance,\n });\n }\n }\n }\n\n for (const extension of options.extensions ?? []) {\n for (const entry of extension.toRows?.(d as unknown as D) ?? []) {\n const plan = extensionTables.get(entry.table) ?? {\n table: entry.table,\n conflict: entry.conflict,\n insertOnly: INSERT_ONLY_COLUMNS,\n rows: [],\n };\n plan.rows.push({ ...governance, ...entry.row, ...provenance });\n extensionTables.set(entry.table, plan);\n }\n }\n }\n\n const keys: Record<string, readonly string[]> = {\n \"ui.ui_surface_value\": childKey(schema),\n \"ui.ui_surface_write_target\": childKey(schema),\n ...(schema?.itemType ? { \"ui.ui_surface_item_type\": [\"surface_name\", \"name\"] } : {}),\n // ui.ui_surface_action is claimed ONLY when a code declaration emits Action rows: until code\n // declares Actions, every live Action row is database-owned, and claiming the table would read\n // a future code Action as stale. Unclaimed, a stale check on it throws by name instead.\n ...(extensionTables.has(SURFACE_ACTION_TABLE) ? { [SURFACE_ACTION_TABLE]: SURFACE_ACTION_KEY } : {}),\n };\n for (const extension of options.extensions ?? []) {\n if (extension.mirror) keys[extension.mirror.table] = extension.mirror.conflict;\n }\n for (const plan of extensionTables.values()) keys[plan.table] ??= plan.conflict;\n\n const tables: SyncTablePlan[] = [\n { table: \"ui.ui_surface_value\", conflict: childKey(schema), insertOnly: INSERT_ONLY_COLUMNS, rows: values },\n { table: \"ui.ui_surface_write_target\", conflict: childKey(schema), insertOnly: INSERT_ONLY_COLUMNS, rows: targets },\n ...(schema?.itemType\n ? [{ table: \"ui.ui_surface_item_type\", conflict: [\"surface_name\", \"name\"], insertOnly: INSERT_ONLY_COLUMNS, rows: itemTypes }]\n : []),\n ...extensionTables.values(),\n ];\n return { surfaces, tables: tables.filter((table) => table.rows.length > 0), keys };\n}\n\n/** One row's key under `columns` (a missing column reads as ''). */\nexport function mirrorRowKey(row: Readonly<Record<string, unknown>>, columns: readonly string[]): string {\n return columns.map((column) => String(row[column] ?? \"\")).join(\"\\u0000\");\n}\n\n/**\n * Rows of `table` that belong to a surface this plan manages but that the\n * plan no longer declares, judged by the table's full key (`plan.keys`) — so\n * a screen value and an item value that share a name are distinct rows. The\n * caller deletes each returned row filtered by every column of that key.\n * A row whose `declared_by` is \"database\" is never returned (pass the column\n * in `existing`, and delete with `declared_by = 'code'` as well).\n */\nexport function findStaleRows<R extends Readonly<Record<string, unknown>>>(\n plan: SurfaceSyncPlan,\n table: string,\n existing: readonly R[],\n): R[] {\n const key = plan.keys[table];\n if (!key) {\n throw new Error(\n `Sync plan declares no key for \"${table}\"; give the extension that owns it a \\`mirror\\` entry.`,\n );\n }\n const managed = new Set(plan.surfaces.map((surface) => surface.name));\n const declared = new Set(\n (plan.tables.find((entry) => entry.table === table)?.rows ?? []).map((row) => mirrorRowKey(row, key)),\n );\n return existing.filter(\n (row) => !isDatabaseDeclared(row) && managed.has(String(row.surface_name)) && !declared.has(mirrorRowKey(row, key)),\n );\n}\n\n/** Every column a table's rows carry, in first-seen order. */\nexport function planColumns(table: SyncTablePlan): string[] {\n const columns: string[] = [];\n for (const row of table.rows) for (const key of Object.keys(row)) if (!columns.includes(key)) columns.push(key);\n return columns;\n}\n\n/** Columns a conflict rewrites: everything except the key and the insert-only set. */\nexport function conflictUpdateColumns(table: SyncTablePlan): string[] {\n return planColumns(table).filter((c) => !table.conflict.includes(c) && !table.insertOnly.includes(c));\n}\n\nexport function sqlLiteral(value: Json | undefined): string {\n if (value === null || value === undefined) return \"NULL\";\n if (typeof value === \"boolean\" || typeof value === \"number\") return String(value);\n if (typeof value === \"string\") return `'${value.replace(/'/g, \"''\")}'`;\n if (Array.isArray(value) && value.every((entry) => typeof entry === \"string\")) {\n return `ARRAY[${value.map((entry) => sqlLiteral(entry)).join(\", \")}]::text[]`;\n }\n return `'${JSON.stringify(value).replace(/'/g, \"''\")}'::jsonb`;\n}\n\n/**\n * The plan as SQL statements, one per entry: surface insert, one UPDATE per\n * surface, one upsert per table. Callers may run them, or EXPLAIN each one in a\n * read-only transaction to prove the plan against a live schema with no write.\n */\nexport function renderSurfaceSyncStatements(plan: SurfaceSyncPlan): string[] {\n const statements: string[] = [];\n if (plan.surfaces.length > 0) {\n const insertColumns = Object.keys(plan.surfaces[0]!.insert);\n statements.push(\n `INSERT INTO ui.ui_surface (${insertColumns.join(\", \")}) VALUES\\n${plan.surfaces\n .map((s) => `(${insertColumns.map((c) => sqlLiteral(s.insert[c])).join(\", \")})`)\n .join(\",\\n\")}\\nON CONFLICT (name) DO NOTHING`,\n );\n for (const s of plan.surfaces) {\n statements.push(\n `UPDATE ui.ui_surface SET ${Object.entries(s.update)\n .map(([c, v]) => `${c} = ${sqlLiteral(v)}`)\n .join(\", \")}, updated_at = now() WHERE name = ${sqlLiteral(s.name)}`,\n );\n }\n }\n for (const table of plan.tables) {\n const columns = planColumns(table);\n const sets = conflictUpdateColumns(table).map((c) => `${c} = EXCLUDED.${c}`);\n statements.push(\n `INSERT INTO ${table.table} (${columns.join(\", \")}) VALUES\\n${table.rows\n .map((row) => `(${columns.map((c) => sqlLiteral(row[c])).join(\", \")})`)\n .join(\",\\n\")}\\nON CONFLICT (${table.conflict.join(\", \")}) DO UPDATE SET ${[...sets, \"updated_at = now()\"].join(\", \")}`,\n );\n }\n return statements;\n}\n\n/** The plan as one SQL script (governance columns insert-only). */\nexport function renderSurfaceSyncSql(plan: SurfaceSyncPlan): string {\n return renderSurfaceSyncStatements(plan)\n .map((statement) => `${statement};`)\n .join(\"\\n\\n\");\n}\n","/**\n * The declaration half of the route check (CONTRACT §2.6 `/checks`): does each\n * declared `urlPattern` address a live route? The host supplies its route list\n * (it owns the filesystem); the matching rule lives here, once. Ported from\n * matrx-frontend `scripts/check-surface-routes.ts` (ALC-14).\n */\n\n/** Route or pattern → segments; `[x]` / `:x` → \"*\", `[...x]` / `[[...x]]` → \"**\". */\nexport function routeSegments(path: string): string[] {\n return (path.split(\"?\")[0] ?? \"\")\n .split(\"#\")[0]!\n .split(\"/\")\n .filter(Boolean)\n .map((segment) => {\n if (/^\\[\\[?\\.\\.\\..+\\]\\]?$/.test(segment)) return \"**\";\n if (segment.startsWith(\"[\") || segment.startsWith(\":\")) return \"*\";\n return segment;\n });\n}\n\n/**\n * Does a declared `urlPattern` address a live route? A literal pattern segment\n * must be a literal route segment — a dynamic route segment does NOT satisfy it,\n * or `/marketing/[brandId]/…` would \"cover\" a retired `/marketing/brands/…`.\n * A trailing `*` makes the pattern a prefix.\n */\nexport function urlPatternMatchesRoute(pattern: string, route: readonly string[]): boolean {\n const isPrefix = pattern.trimEnd().endsWith(\"*\") && !pattern.endsWith(\"]*\");\n const segments = routeSegments(pattern.replace(/\\*+\\s*$/, \"\"));\n for (let i = 0; i < segments.length; i += 1) {\n const routeSegment = route[i];\n if (routeSegment === \"**\") return true;\n if (routeSegment === undefined) return false;\n const declared = segments[i];\n if (declared === \"**\") return true;\n if (declared === \"*\" ? routeSegment !== \"*\" : routeSegment !== declared) return false;\n }\n return isPrefix || route.length === segments.length;\n}\n\n/** Every declaration whose `urlPattern` no live route can produce. */\nexport function findDeadUrlPatterns(\n declarations: readonly { surfaceName: string; urlPattern?: string | undefined }[],\n liveRoutes: readonly string[],\n): { surfaceName: string; urlPattern: string }[] {\n const routes = liveRoutes.map(routeSegments);\n const dead: { surfaceName: string; urlPattern: string }[] = [];\n for (const declaration of declarations) {\n const pattern = declaration.urlPattern;\n if (!pattern) continue;\n if (!routes.some((route) => urlPatternMatchesRoute(pattern, route))) {\n dead.push({ surfaceName: declaration.surfaceName, urlPattern: pattern });\n }\n }\n return dead;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/declare/keys.ts","../src/declare/sensitivity.ts","../src/checks/sync.ts","../src/checks/routes.ts"],"sourcesContent":["import type { QualifiedValueKey } from \"./types\";\n\n/** Every value, write-target, item-type and group name (the DB CHECK rule). */\nexport const DECLARATION_NAME_RE = /^[a-z][a-z0-9_]*$/;\n\n/** \"<client>/<local>\" — the surface naming convention. */\nexport const SURFACE_NAME_RE = /^[a-z][a-z0-9-]*\\/[a-z0-9-/]+$/;\n\n/** A bare screen value (`status`) or an item value (`table_row.status`). */\nexport const QUALIFIED_VALUE_KEY_RE = /^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)?$/;\n\nexport interface ParsedValueKey {\n /** \"\" for a screen-level value. */\n itemType: string;\n name: string;\n}\n\n/** Split a qualified key; null when it is not a valid bare or qualified key. */\nexport function parseQualifiedValueKey(key: string): ParsedValueKey | null {\n if (!QUALIFIED_VALUE_KEY_RE.test(key)) return null;\n const dot = key.indexOf(\".\");\n if (dot === -1) return { itemType: \"\", name: key };\n return { itemType: key.slice(0, dot), name: key.slice(dot + 1) };\n}\n\nexport function qualifyValueKey(itemType: string, name: string): QualifiedValueKey {\n return itemType ? `${itemType}.${name}` : name;\n}\n\n/** The client that owns a surface: its name prefix. */\nexport function surfaceClientOf(surfaceName: string): string {\n return surfaceName.split(\"/\")[0] ?? \"\";\n}\n","/**\n * Sensitivity defaults and resolution (CONTRACT §2.1). Its own module so the\n * lightweight entries (`/actions`, the T0 React host) can project without\n * pulling the declaration registry.\n */\nimport type { DeclaredValue, Sensitivity } from \"./types\";\n\nexport const DEFAULT_SENSITIVITY: Sensitivity = Object.freeze({\n exportable: true,\n classification: \"ordinary\",\n includedByDefault: true,\n});\n\nexport function resolveSensitivity(value: Pick<DeclaredValue, \"sensitivity\">): Sensitivity {\n return {\n exportable: value.sensitivity?.exportable ?? DEFAULT_SENSITIVITY.exportable,\n classification: value.sensitivity?.classification ?? DEFAULT_SENSITIVITY.classification,\n includedByDefault: value.sensitivity?.includedByDefault ?? DEFAULT_SENSITIVITY.includedByDefault,\n };\n}\n","/**\n * THE ONE SYNC (CONTRACT §2.7 \"One sync path\", chair ruling B1).\n *\n * `planSurfaceSync` maps resolved declarations onto the `ui` mirror rows; every\n * sync path (the SQL emitter and the admin sync service) executes this plan and\n * builds no rows of its own. Governance columns (`organization_id`,\n * `published_to_web`) are INSERT-ONLY (chair ruling N5): a conflict never rewrites them.\n * Mirror rows are published to the web on insert, written explicitly (access ladder\n * T-13: never left to a column default); the database stamps when and by whom.\n *\n * Schema generations: the columns added by the ALC-14 migration (item_type,\n * sensitivity, live slots, content_hash) are emitted only when the caller says\n * that migration is live (`schema`), so no emitter ever writes a column — or\n * upserts against a key — the database does not have yet.\n */\nimport { resolveSensitivity, type Resolved } from \"../declare/registry\";\nimport { surfaceClientOf } from \"../declare/keys\";\nimport type { DeclarationExtension, Json, SurfaceDeclaration } from \"../declare/types\";\n\nexport const GOVERNANCE_COLUMNS = [\"organization_id\", \"published_to_web\"] as const;\n\n/**\n * WHO DECLARED A ROW (Applets CONTRACTS §6, PLAN ruling 7). Every `ui.ui_surface*`\n * row is `declared_by` \"code\" or \"database\". This sync writes only code rows: it\n * stamps \"code\" on insert and never rewrites the column on conflict (a database\n * trigger refuses a code takeover of a database row anyway); stale and orphan\n * checks never return a database row.\n */\nexport const DECLARED_BY = { code: \"code\", database: \"database\" } as const;\nexport type DeclaredBy = (typeof DECLARED_BY)[keyof typeof DECLARED_BY];\n\n/** Set on insert, never on conflict: governance plus `declared_by`. */\nexport const INSERT_ONLY_COLUMNS = [...GOVERNANCE_COLUMNS, \"declared_by\"] as const;\n\n/** The Actions mirror. Always keyed with item_type (the table was born re-keyed). */\nexport const SURFACE_ACTION_TABLE = \"ui.ui_surface_action\";\nexport const SURFACE_ACTION_KEY = [\"surface_name\", \"item_type\", \"name\"] as const;\n\n/** True for a row an organization authored in the database — code sync never touches it. */\nexport function isDatabaseDeclared(row: Readonly<Record<string, unknown>>): boolean {\n return row.declared_by === DECLARED_BY.database;\n}\n\nexport interface SyncSchema {\n /** `ui_surface_value/write_target.item_type` exists and the conflict key includes it (re-key step 1). */\n itemType?: boolean;\n /** Sensitivity, kind, live slot and role columns exist on `ui_surface_value`. */\n valueContract?: boolean;\n /** `ui_surface.content_hash` exists. */\n contentHash?: boolean;\n}\n\nexport interface SurfaceSyncPlanOptions<D extends SurfaceDeclaration> {\n organizationId: string;\n syncedFrom: string;\n syncedBy?: string | null;\n /** The address the host derives for a declaration without `urlPattern`. */\n resolveUrlPattern?: (declaration: Resolved<D>) => string | undefined;\n extensions?: readonly DeclarationExtension<D>[];\n schema?: SyncSchema;\n}\n\nexport interface SurfaceRowPlan {\n name: string;\n /** Written only when the row does not exist yet. */\n insert: Record<string, Json>;\n /** Written on every sync (code owns these columns). */\n update: Record<string, Json>;\n}\n\nexport interface SyncTablePlan {\n table: string;\n /** The ON CONFLICT target (a unique index or primary key on exactly these columns). */\n conflict: readonly string[];\n /** Columns set on insert and never on conflict. */\n insertOnly: readonly string[];\n rows: Record<string, Json>[];\n}\n\nexport interface SurfaceSyncPlan {\n surfaces: SurfaceRowPlan[];\n tables: SyncTablePlan[];\n /**\n * The key (ON CONFLICT target) of every mirror table this plan owns,\n * including tables it emits no rows for. Stale detection and deletes use\n * exactly these columns (`findStaleRows`).\n */\n keys: Record<string, readonly string[]>;\n}\n\nfunction childKey(schema: SyncSchema | undefined): readonly string[] {\n return schema?.itemType ? [\"surface_name\", \"item_type\", \"name\"] : [\"surface_name\", \"name\"];\n}\n\nexport function planSurfaceSync<D extends SurfaceDeclaration>(\n declarations: readonly Resolved<D>[],\n options: SurfaceSyncPlanOptions<D>,\n): SurfaceSyncPlan {\n const { organizationId, syncedFrom, schema } = options;\n const syncedBy = options.syncedBy ?? null;\n const provenance = { synced_by: syncedBy, synced_from: syncedFrom };\n const governance = { organization_id: organizationId, published_to_web: true, declared_by: DECLARED_BY.code };\n const surfaces: SurfaceRowPlan[] = [];\n const values: Record<string, Json>[] = [];\n const targets: Record<string, Json>[] = [];\n const itemTypes: Record<string, Json>[] = [];\n const extensionTables = new Map<string, SyncTablePlan>();\n\n for (const d of declarations) {\n const urlPattern = d.urlPattern?.trim() || options.resolveUrlPattern?.(d) || null;\n const intro = d.intro?.trim() || null;\n const situation = d.situation?.trim() || null;\n const parent = d.inheritsFrom ?? null;\n const client = surfaceClientOf(d.surfaceName);\n const owned: Record<string, Json> = {\n client_name: client,\n execution_mode: d.executionMode,\n description: d.description,\n label: d.label,\n value_groups: (d.groups ?? []) as unknown as Json,\n readiness: d.readiness,\n readiness_note: d.readinessNote?.trim() || null,\n ...(schema?.contentHash ? { content_hash: d.contentHash } : {}),\n };\n // A declaration that does not claim these never clears a DB-authored value.\n const claimed: Record<string, Json> = {\n ...(d.overlayId ? { overlay_id: d.overlayId } : {}),\n ...(urlPattern ? { url_pattern: urlPattern } : {}),\n ...(intro ? { intro } : {}),\n ...(situation ? { situation } : {}),\n ...(parent ? { parent_surface_name: parent } : {}),\n };\n surfaces.push({\n name: d.surfaceName,\n insert: {\n // Insert-only ownership: `ui.ui_surface.organization_id` is NOT NULL since AP-2 (code rows → the\n // system organization); a new surface without it was refused by the database (AP-6, 2026-10-06).\n organization_id: organizationId,\n name: d.surfaceName,\n declared_by: DECLARED_BY.code,\n ...owned,\n overlay_id: d.overlayId ?? null,\n url_pattern: urlPattern,\n intro,\n situation,\n parent_surface_name: parent,\n },\n update: { ...owned, ...claimed },\n });\n\n for (const v of d.values) {\n values.push({\n ...governance,\n surface_name: d.surfaceName,\n ...(schema?.itemType ? { item_type: \"\" } : {}),\n name: v.name,\n label: v.label,\n description: v.description,\n value_type: v.valueType,\n always_available: v.alwaysAvailable,\n typical_char_count: v.typicalCharCount,\n sort_order: v.sortOrder ?? 1000,\n auto_context: v.autoContext ?? true,\n group_key: v.groupKey ?? v.group ?? \"general\",\n ...(schema?.valueContract\n ? {\n exportable: resolveSensitivity(v).exportable,\n classification: resolveSensitivity(v).classification,\n included_by_default: resolveSensitivity(v).includedByDefault,\n kind_key: v.valueKind ?? null,\n live_slot: v.liveSlot ?? null,\n role: v.role ?? \"data\",\n }\n : {}),\n ...provenance,\n });\n }\n\n for (const t of d.writeTargets ?? []) {\n targets.push({\n ...governance,\n surface_name: d.surfaceName,\n ...(schema?.itemType ? { item_type: t.itemType ?? \"\" } : {}),\n name: t.name,\n label: t.label,\n description: t.description,\n value_type: t.valueType,\n mode: t.mode,\n updates_value: t.updatesValue ?? null,\n group_key: t.group ?? \"general\",\n sort_order: t.sortOrder ?? 1000,\n apply_policy: t.applyPolicy ?? \"manual\",\n kind_key: t.valueKind ?? null,\n ...(schema?.valueContract\n ? {\n patchable: t.patchable ?? false,\n approval_comparison: t.approvalComparison ?? null,\n stream_ops: t.streamOps ? [...t.streamOps] : null,\n destination: t.destination ?? false,\n }\n : {}),\n ...provenance,\n });\n }\n\n if (schema?.itemType) {\n for (const [position, item] of (d.itemTypes ?? []).entries()) {\n itemTypes.push({\n ...governance,\n surface_name: d.surfaceName,\n name: item.name,\n label: item.label,\n description: item.description,\n identity: [...item.identity],\n levels: item.levels ? [...item.levels] : null,\n entity_type: item.entityType ?? null,\n sort_order: position,\n ...provenance,\n });\n for (const v of item.values) {\n values.push({\n ...governance,\n surface_name: d.surfaceName,\n item_type: item.name,\n name: v.name,\n label: v.label,\n description: v.description,\n value_type: v.valueType,\n always_available: v.alwaysAvailable,\n typical_char_count: v.typicalCharCount,\n sort_order: v.sortOrder ?? 1000,\n auto_context: v.autoContext ?? true,\n group_key: v.group ?? \"general\",\n // Every row of one INSERT carries the same columns: a screen value with the value\n // contract makes the statement name these, so an item value that left them out was\n // sent as NULL into NOT NULL columns and the whole sync refused (2026-10-07).\n ...(schema?.valueContract\n ? {\n exportable: resolveSensitivity(v).exportable,\n classification: resolveSensitivity(v).classification,\n included_by_default: resolveSensitivity(v).includedByDefault,\n kind_key: v.valueKind ?? null,\n live_slot: v.liveSlot ?? null,\n role: v.role ?? \"data\",\n }\n : {}),\n ...provenance,\n });\n }\n }\n }\n\n for (const extension of options.extensions ?? []) {\n for (const entry of extension.toRows?.(d as unknown as D) ?? []) {\n const plan = extensionTables.get(entry.table) ?? {\n table: entry.table,\n conflict: entry.conflict,\n insertOnly: INSERT_ONLY_COLUMNS,\n rows: [],\n };\n plan.rows.push({ ...governance, ...entry.row, ...provenance });\n extensionTables.set(entry.table, plan);\n }\n }\n }\n\n const keys: Record<string, readonly string[]> = {\n \"ui.ui_surface_value\": childKey(schema),\n \"ui.ui_surface_write_target\": childKey(schema),\n ...(schema?.itemType ? { \"ui.ui_surface_item_type\": [\"surface_name\", \"name\"] } : {}),\n // ui.ui_surface_action is claimed ONLY when a code declaration emits Action rows: until code\n // declares Actions, every live Action row is database-owned, and claiming the table would read\n // a future code Action as stale. Unclaimed, a stale check on it throws by name instead.\n ...(extensionTables.has(SURFACE_ACTION_TABLE) ? { [SURFACE_ACTION_TABLE]: SURFACE_ACTION_KEY } : {}),\n };\n for (const extension of options.extensions ?? []) {\n if (extension.mirror) keys[extension.mirror.table] = extension.mirror.conflict;\n }\n for (const plan of extensionTables.values()) keys[plan.table] ??= plan.conflict;\n\n const tables: SyncTablePlan[] = [\n { table: \"ui.ui_surface_value\", conflict: childKey(schema), insertOnly: INSERT_ONLY_COLUMNS, rows: values },\n { table: \"ui.ui_surface_write_target\", conflict: childKey(schema), insertOnly: INSERT_ONLY_COLUMNS, rows: targets },\n ...(schema?.itemType\n ? [{ table: \"ui.ui_surface_item_type\", conflict: [\"surface_name\", \"name\"], insertOnly: INSERT_ONLY_COLUMNS, rows: itemTypes }]\n : []),\n ...extensionTables.values(),\n ];\n return { surfaces, tables: tables.filter((table) => table.rows.length > 0), keys };\n}\n\n/** One row's key under `columns` (a missing column reads as ''). */\nexport function mirrorRowKey(row: Readonly<Record<string, unknown>>, columns: readonly string[]): string {\n return columns.map((column) => String(row[column] ?? \"\")).join(\"\\u0000\");\n}\n\n/**\n * Rows of `table` that belong to a surface this plan manages but that the\n * plan no longer declares, judged by the table's full key (`plan.keys`) — so\n * a screen value and an item value that share a name are distinct rows. The\n * caller deletes each returned row filtered by every column of that key.\n * A row whose `declared_by` is \"database\" is never returned (pass the column\n * in `existing`, and delete with `declared_by = 'code'` as well).\n */\nexport function findStaleRows<R extends Readonly<Record<string, unknown>>>(\n plan: SurfaceSyncPlan,\n table: string,\n existing: readonly R[],\n): R[] {\n const key = plan.keys[table];\n if (!key) {\n throw new Error(\n `Sync plan declares no key for \"${table}\"; give the extension that owns it a \\`mirror\\` entry.`,\n );\n }\n const managed = new Set(plan.surfaces.map((surface) => surface.name));\n const declared = new Set(\n (plan.tables.find((entry) => entry.table === table)?.rows ?? []).map((row) => mirrorRowKey(row, key)),\n );\n return existing.filter(\n (row) => !isDatabaseDeclared(row) && managed.has(String(row.surface_name)) && !declared.has(mirrorRowKey(row, key)),\n );\n}\n\n/** Every column a table's rows carry, in first-seen order. */\nexport function planColumns(table: SyncTablePlan): string[] {\n const columns: string[] = [];\n for (const row of table.rows) for (const key of Object.keys(row)) if (!columns.includes(key)) columns.push(key);\n return columns;\n}\n\n/** Columns a conflict rewrites: everything except the key and the insert-only set. */\nexport function conflictUpdateColumns(table: SyncTablePlan): string[] {\n return planColumns(table).filter((c) => !table.conflict.includes(c) && !table.insertOnly.includes(c));\n}\n\nexport function sqlLiteral(value: Json | undefined): string {\n if (value === null || value === undefined) return \"NULL\";\n if (typeof value === \"boolean\" || typeof value === \"number\") return String(value);\n if (typeof value === \"string\") return `'${value.replace(/'/g, \"''\")}'`;\n if (Array.isArray(value) && value.every((entry) => typeof entry === \"string\")) {\n return `ARRAY[${value.map((entry) => sqlLiteral(entry)).join(\", \")}]::text[]`;\n }\n return `'${JSON.stringify(value).replace(/'/g, \"''\")}'::jsonb`;\n}\n\n/**\n * The plan as SQL statements, one per entry: surface insert, one UPDATE per\n * surface, one upsert per table. Callers may run them, or EXPLAIN each one in a\n * read-only transaction to prove the plan against a live schema with no write.\n */\nexport function renderSurfaceSyncStatements(plan: SurfaceSyncPlan): string[] {\n const statements: string[] = [];\n if (plan.surfaces.length > 0) {\n const insertColumns = Object.keys(plan.surfaces[0]!.insert);\n statements.push(\n `INSERT INTO ui.ui_surface (${insertColumns.join(\", \")}) VALUES\\n${plan.surfaces\n .map((s) => `(${insertColumns.map((c) => sqlLiteral(s.insert[c])).join(\", \")})`)\n .join(\",\\n\")}\\nON CONFLICT (name) DO NOTHING`,\n );\n for (const s of plan.surfaces) {\n statements.push(\n `UPDATE ui.ui_surface SET ${Object.entries(s.update)\n .map(([c, v]) => `${c} = ${sqlLiteral(v)}`)\n .join(\", \")}, updated_at = now() WHERE name = ${sqlLiteral(s.name)}`,\n );\n }\n }\n for (const table of plan.tables) {\n const columns = planColumns(table);\n const sets = conflictUpdateColumns(table).map((c) => `${c} = EXCLUDED.${c}`);\n statements.push(\n `INSERT INTO ${table.table} (${columns.join(\", \")}) VALUES\\n${table.rows\n .map((row) => `(${columns.map((c) => sqlLiteral(row[c])).join(\", \")})`)\n .join(\",\\n\")}\\nON CONFLICT (${table.conflict.join(\", \")}) DO UPDATE SET ${[...sets, \"updated_at = now()\"].join(\", \")}`,\n );\n }\n return statements;\n}\n\n/** The plan as one SQL script (governance columns insert-only). */\nexport function renderSurfaceSyncSql(plan: SurfaceSyncPlan): string {\n return renderSurfaceSyncStatements(plan)\n .map((statement) => `${statement};`)\n .join(\"\\n\\n\");\n}\n","/**\n * The declaration half of the route check (CONTRACT §2.6 `/checks`): does each\n * declared `urlPattern` address a live route? The host supplies its route list\n * (it owns the filesystem); the matching rule lives here, once. Ported from\n * matrx-frontend `scripts/check-surface-routes.ts` (ALC-14).\n */\n\n/** Route or pattern → segments; `[x]` / `:x` → \"*\", `[...x]` / `[[...x]]` → \"**\". */\nexport function routeSegments(path: string): string[] {\n return (path.split(\"?\")[0] ?? \"\")\n .split(\"#\")[0]!\n .split(\"/\")\n .filter(Boolean)\n .map((segment) => {\n if (/^\\[\\[?\\.\\.\\..+\\]\\]?$/.test(segment)) return \"**\";\n if (segment.startsWith(\"[\") || segment.startsWith(\":\")) return \"*\";\n return segment;\n });\n}\n\n/**\n * Does a declared `urlPattern` address a live route? A literal pattern segment\n * must be a literal route segment — a dynamic route segment does NOT satisfy it,\n * or `/marketing/[brandId]/…` would \"cover\" a retired `/marketing/brands/…`.\n * A trailing `*` makes the pattern a prefix.\n */\nexport function urlPatternMatchesRoute(pattern: string, route: readonly string[]): boolean {\n const isPrefix = pattern.trimEnd().endsWith(\"*\") && !pattern.endsWith(\"]*\");\n const segments = routeSegments(pattern.replace(/\\*+\\s*$/, \"\"));\n for (let i = 0; i < segments.length; i += 1) {\n const routeSegment = route[i];\n if (routeSegment === \"**\") return true;\n if (routeSegment === undefined) return false;\n const declared = segments[i];\n if (declared === \"**\") return true;\n if (declared === \"*\" ? routeSegment !== \"*\" : routeSegment !== declared) return false;\n }\n return isPrefix || route.length === segments.length;\n}\n\n/** Every declaration whose `urlPattern` no live route can produce. */\nexport function findDeadUrlPatterns(\n declarations: readonly { surfaceName: string; urlPattern?: string | undefined }[],\n liveRoutes: readonly string[],\n): { surfaceName: string; urlPattern: string }[] {\n const routes = liveRoutes.map(routeSegments);\n const dead: { surfaceName: string; urlPattern: string }[] = [];\n for (const declaration of declarations) {\n const pattern = declaration.urlPattern;\n if (!pattern) continue;\n if (!routes.some((route) => urlPatternMatchesRoute(pattern, route))) {\n dead.push({ surfaceName: declaration.surfaceName, urlPattern: pattern });\n }\n }\n return dead;\n}\n"],"mappings":"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","names":[]}
@@ -1,2 +1,2 @@
1
- "use strict";var __create=Object.create;var __defProp=Object.defineProperty;var __getOwnPropDesc=Object.getOwnPropertyDescriptor;var __getOwnPropNames=Object.getOwnPropertyNames;var __getProtoOf=Object.getPrototypeOf,__hasOwnProp=Object.prototype.hasOwnProperty;var __export=(target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:!0})},__copyProps=(to,from,except,desc)=>{if(from&&typeof from=="object"||typeof from=="function")for(let key of __getOwnPropNames(from))!__hasOwnProp.call(to,key)&&key!==except&&__defProp(to,key,{get:()=>from[key],enumerable:!(desc=__getOwnPropDesc(from,key))||desc.enumerable});return to};var __toESM=(mod,isNodeMode,target)=>(target=mod!=null?__create(__getProtoOf(mod)):{},__copyProps(isNodeMode||!mod||!mod.__esModule?__defProp(target,"default",{value:mod,enumerable:!0}):target,mod)),__toCommonJS=mod=>__copyProps(__defProp({},"__esModule",{value:!0}),mod);var operate_capture_exports={};__export(operate_capture_exports,{canvasToBlob:()=>canvasToBlob,captureElement:()=>captureElement,elementToImage:()=>elementToImage,elementsToPdf:()=>elementsToPdf,pagesToPdf:()=>pagesToPdf,renderElement:()=>renderElement,renderElementToDataUrl:()=>renderElementToDataUrl});module.exports=__toCommonJS(operate_capture_exports);async function captureElement(element,options={}){let{captureCanvas}=await import("@ai-matrx/print/pdf");return captureCanvas(element,options)}async function canvasToBlob(canvas,type="image/png",quality){let blob=await new Promise(resolve=>canvas.toBlob(resolve,type,quality));if(!blob)throw new Error("The image could not be encoded.");return blob}async function elementToImage(element,options={}){let{type,quality,...capture}=options;return canvasToBlob(await captureElement(element,capture),type,quality)}async function renderElement(element,options={}){let{toCanvas}=await import("html-to-image");return toCanvas(element,options)}async function renderElementToDataUrl(element,options={}){let{toPng}=await import("html-to-image");return toPng(element,options)}async function pagesToPdf(pages,options={}){let{pagesToPdf:layOut}=await import("@ai-matrx/print/pdf");return layOut(pages,options)}async function elementsToPdf(elements,options={}){let pages=[];for(let element of elements)pages.push({...options.page,image:await captureElement(element,options.capture)});return pagesToPdf(pages,options)}
1
+ "use strict";var __create=Object.create;var __defProp=Object.defineProperty;var __getOwnPropDesc=Object.getOwnPropertyDescriptor;var __getOwnPropNames=Object.getOwnPropertyNames;var __getProtoOf=Object.getPrototypeOf,__hasOwnProp=Object.prototype.hasOwnProperty;var __export=(target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:!0})},__copyProps=(to,from,except,desc)=>{if(from&&typeof from=="object"||typeof from=="function")for(let key of __getOwnPropNames(from))!__hasOwnProp.call(to,key)&&key!==except&&__defProp(to,key,{get:()=>from[key],enumerable:!(desc=__getOwnPropDesc(from,key))||desc.enumerable});return to};var __toESM=(mod,isNodeMode,target)=>(target=mod!=null?__create(__getProtoOf(mod)):{},__copyProps(isNodeMode||!mod||!mod.__esModule?__defProp(target,"default",{value:mod,enumerable:!0}):target,mod)),__toCommonJS=mod=>__copyProps(__defProp({},"__esModule",{value:!0}),mod);var operate_capture_exports={};__export(operate_capture_exports,{canvasToBlob:()=>canvasToBlob,captureDocumentPdf:()=>captureDocumentPdf,captureElement:()=>captureElement,elementToImage:()=>elementToImage,elementsToPdf:()=>elementsToPdf,pagesToPdf:()=>pagesToPdf,renderElement:()=>renderElement,renderElementToDataUrl:()=>renderElementToDataUrl});module.exports=__toCommonJS(operate_capture_exports);async function captureElement(element,options={}){let{captureCanvas}=await import("@ai-matrx/print/pdf");return captureCanvas(element,options)}async function canvasToBlob(canvas,type="image/png",quality){let blob=await new Promise(resolve=>canvas.toBlob(resolve,type,quality));if(!blob)throw new Error("The image could not be encoded.");return blob}async function elementToImage(element,options={}){let{type,quality,...capture}=options;return canvasToBlob(await captureElement(element,capture),type,quality)}async function renderElement(element,options={}){let{toCanvas}=await import("html-to-image");return toCanvas(element,options)}async function renderElementToDataUrl(element,options={}){let{toPng}=await import("html-to-image");return toPng(element,options)}async function pagesToPdf(pages,options={}){let{pagesToPdf:layOut}=await import("@ai-matrx/print/pdf");return layOut(pages,options)}async function captureDocumentPdf(element,options={}){let engine=await import("@ai-matrx/print/pdf");return Array.isArray(element)?engine.captureElementsToPDFBlob([...element],options):engine.captureToPDFBlob(element,options)}async function elementsToPdf(elements,options={}){let pages=[];for(let element of elements)pages.push({...options.page,image:await captureElement(element,options.capture)});return pagesToPdf(pages,options)}
2
2
  //# sourceMappingURL=operate-capture.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/operate-capture.ts","../src/operate/capture.ts"],"sourcesContent":["/** `@ai-matrx/alchemy/operate/capture` — the one DOM capture engine (element → image / PDF). html2canvas, html-to-image and jsPDF load on first use. */\nexport {\n canvasToBlob,\n captureElement,\n elementToImage,\n elementsToPdf,\n pagesToPdf,\n renderElement,\n renderElementToDataUrl,\n type CaptureOptions,\n type PdfDocumentOptions,\n type PdfMargins,\n type PdfPage,\n type RenderOptions,\n} from \"./operate/capture\";\n","/**\n * `@ai-matrx/alchemy/operate/capture` — THE DOM capture door (ALC-19 T11): an element on screen →\n * an image or a PDF, with nothing else in the app importing html2canvas or jsPDF. The html2canvas +\n * jsPDF engine itself is `@ai-matrx/print/pdf` (`captureCanvas`, `pagesToPdf`), loaded on first use\n * (`import()`), so a page that never exports never downloads it; html-to-image (`renderElement`) is\n * this module's own.\n *\n * Tailwind 4 colours (oklch/lab/color()) are not parseable by html2canvas, which logs an error per\n * value; `safeColors` substitutes theme-appropriate hex values for the duration of the capture.\n */\n\n// THE one html2canvas + jsPDF engine is print's (AP-2 engine ruling: print sits below alchemy, so\n// alchemy loads it — never a second copy here). Type-only imports; the module loads on first use.\nimport type { CanvasCaptureOptions, PdfDocumentOptions, PdfPage } from \"@ai-matrx/print/pdf\";\nexport type { CanvasCaptureOptions as CaptureOptions, PdfDocumentOptions, PdfMargins, PdfPage } from \"@ai-matrx/print/pdf\";\ntype CaptureOptions = CanvasCaptureOptions;\n\n/** Rasterise a live element to a canvas (what the person sees, at `scale`) — print's `captureCanvas`. */\nexport async function captureElement(element: HTMLElement, options: CaptureOptions = {}): Promise<HTMLCanvasElement> {\n const { captureCanvas } = await import(\"@ai-matrx/print/pdf\");\n return captureCanvas(element, options);\n}\n\n/** Encode a canvas (PNG by default; JPEG/WebP take a 0–1 quality). Throws when the browser refuses. */\nexport async function canvasToBlob(canvas: HTMLCanvasElement, type: \"image/png\" | \"image/jpeg\" | \"image/webp\" = \"image/png\", quality?: number): Promise<Blob> {\n const blob = await new Promise<Blob | null>((resolve) => canvas.toBlob(resolve, type, quality));\n if (!blob) throw new Error(\"The image could not be encoded.\");\n return blob;\n}\n\n/** Capture an element straight to an image Blob. */\nexport async function elementToImage(\n element: HTMLElement,\n options: CaptureOptions & { type?: \"image/png\" | \"image/jpeg\" | \"image/webp\"; quality?: number } = {},\n): Promise<Blob> {\n const { type, quality, ...capture } = options;\n return canvasToBlob(await captureElement(element, capture), type, quality);\n}\n\n/** Options for `renderElement` (html-to-image): the SVG foreignObject renderer. */\nexport type RenderOptions = {\n /** Device-pixel ratio (default 1). */\n pixelRatio?: number;\n width?: number;\n height?: number;\n /** Final canvas size; the render is scaled to it. */\n canvasWidth?: number;\n canvasHeight?: number;\n backgroundColor?: string;\n /** Inline style applied to the cloned root (e.g. a translate to pad the chart). */\n style?: Partial<CSSStyleDeclaration>;\n /** Return false to leave a node out of the picture. */\n filter?(node: HTMLElement): boolean;\n skipFonts?: boolean;\n /** Bust the image cache so a CORS fetch is fresh. */\n cacheBust?: boolean;\n /** Data URL drawn instead of an image that fails to load. */\n imagePlaceholder?: string;\n onImageErrorHandler?(...args: unknown[]): void;\n};\n\n/**\n * Render a live element to a canvas through the SVG foreignObject renderer (html-to-image). It keeps\n * the browser's own layout and fonts where html2canvas re-implements them, and it takes a `filter`\n * and a root `style` override — the right tool for a transformed or panned surface. Loads on first use.\n */\nexport async function renderElement(element: HTMLElement, options: RenderOptions = {}): Promise<HTMLCanvasElement> {\n const { toCanvas } = await import(\"html-to-image\");\n return toCanvas(element, options as Parameters<typeof toCanvas>[1]);\n}\n\n/** `renderElement` answering a PNG data URL (what a chat attachment preview needs besides the Blob). */\nexport async function renderElementToDataUrl(element: HTMLElement, options: RenderOptions = {}): Promise<string> {\n const { toPng } = await import(\"html-to-image\");\n return toPng(element, options as Parameters<typeof toPng>[1]);\n}\n\n/** Lay canvases out as PDF pages (one page each) and answer the PDF Blob — print's `pagesToPdf`. */\nexport async function pagesToPdf(pages: readonly PdfPage[], options: PdfDocumentOptions = {}): Promise<Blob> {\n const { pagesToPdf: layOut } = await import(\"@ai-matrx/print/pdf\");\n return layOut(pages, options);\n}\n\n/** Capture each element and lay them out one per PDF page. */\nexport async function elementsToPdf(\n elements: readonly HTMLElement[],\n options: PdfDocumentOptions & { capture?: CaptureOptions; page?: Omit<PdfPage, \"image\"> } = {},\n): Promise<Blob> {\n const pages: PdfPage[] = [];\n for (const element of elements) pages.push({ ...options.page, image: await captureElement(element, options.capture) });\n return pagesToPdf(pages, options);\n}\n"],"mappings":"y5BAAA,sWCkBA,eAAsB,eAAe,QAAsB,QAA0B,CAAC,EAA+B,CACnH,GAAM,CAAE,aAAc,EAAI,KAAM,QAAO,qBAAqB,EAC5D,OAAO,cAAc,QAAS,OAAO,CACvC,CAGA,eAAsB,aAAa,OAA2B,KAAkD,YAAa,QAAiC,CAC5J,IAAM,KAAO,MAAM,IAAI,QAAsB,SAAY,OAAO,OAAO,QAAS,KAAM,OAAO,CAAC,EAC9F,GAAI,CAAC,KAAM,MAAM,IAAI,MAAM,iCAAiC,EAC5D,OAAO,IACT,CAGA,eAAsB,eACpB,QACA,QAAmG,CAAC,EACrF,CACf,GAAM,CAAE,KAAM,QAAS,GAAG,OAAQ,EAAI,QACtC,OAAO,aAAa,MAAM,eAAe,QAAS,OAAO,EAAG,KAAM,OAAO,CAC3E,CA6BA,eAAsB,cAAc,QAAsB,QAAyB,CAAC,EAA+B,CACjH,GAAM,CAAE,QAAS,EAAI,KAAM,QAAO,eAAe,EACjD,OAAO,SAAS,QAAS,OAAyC,CACpE,CAGA,eAAsB,uBAAuB,QAAsB,QAAyB,CAAC,EAAoB,CAC/G,GAAM,CAAE,KAAM,EAAI,KAAM,QAAO,eAAe,EAC9C,OAAO,MAAM,QAAS,OAAsC,CAC9D,CAGA,eAAsB,WAAW,MAA2B,QAA8B,CAAC,EAAkB,CAC3G,GAAM,CAAE,WAAY,MAAO,EAAI,KAAM,QAAO,qBAAqB,EACjE,OAAO,OAAO,MAAO,OAAO,CAC9B,CAGA,eAAsB,cACpB,SACA,QAA4F,CAAC,EAC9E,CACf,IAAM,MAAmB,CAAC,EAC1B,QAAW,WAAW,SAAU,MAAM,KAAK,CAAE,GAAG,QAAQ,KAAM,MAAO,MAAM,eAAe,QAAS,QAAQ,OAAO,CAAE,CAAC,EACrH,OAAO,WAAW,MAAO,OAAO,CAClC","names":[]}
1
+ {"version":3,"sources":["../src/operate-capture.ts","../src/operate/capture.ts"],"sourcesContent":["/** `@ai-matrx/alchemy/operate/capture` — the one DOM capture engine (element → image / PDF). html2canvas, html-to-image and jsPDF load on first use. */\nexport {\n canvasToBlob,\n captureElement,\n captureDocumentPdf,\n elementToImage,\n elementsToPdf,\n pagesToPdf,\n renderElement,\n renderElementToDataUrl,\n type CaptureOptions,\n type DocumentPdfOptions,\n type PdfDocumentOptions,\n type PdfMargins,\n type PdfPage,\n type RenderOptions,\n} from \"./operate/capture\";\n","/**\n * `@ai-matrx/alchemy/operate/capture` — THE DOM capture door (ALC-19 T11): an element on screen →\n * an image or a PDF, with nothing else in the app importing html2canvas or jsPDF. The html2canvas +\n * jsPDF engine itself is `@ai-matrx/print/pdf` (`captureCanvas`, `pagesToPdf`), loaded on first use\n * (`import()`), so a page that never exports never downloads it; html-to-image (`renderElement`) is\n * this module's own.\n *\n * Tailwind 4 colours (oklch/lab/color()) are not parseable by html2canvas, which logs an error per\n * value; `safeColors` substitutes theme-appropriate hex values for the duration of the capture.\n */\n\n// THE one html2canvas + jsPDF engine is print's (AP-2 engine ruling: print sits below alchemy, so\n// alchemy loads it — never a second copy here). Type-only imports; the module loads on first use.\nimport type { CanvasCaptureOptions, DomCaptureOptions, PdfDocumentOptions, PdfPage } from \"@ai-matrx/print/pdf\";\nexport type { CanvasCaptureOptions as CaptureOptions, PdfDocumentOptions, PdfMargins, PdfPage } from \"@ai-matrx/print/pdf\";\n/** Options for `captureDocumentPdf` (print's DOCUMENT capture: paper, orientation, scale, progress, theme). */\nexport type DocumentPdfOptions = Omit<DomCaptureOptions, \"filename\">;\ntype CaptureOptions = CanvasCaptureOptions;\n\n/** Rasterise a live element to a canvas (what the person sees, at `scale`) — print's `captureCanvas`. */\nexport async function captureElement(element: HTMLElement, options: CaptureOptions = {}): Promise<HTMLCanvasElement> {\n const { captureCanvas } = await import(\"@ai-matrx/print/pdf\");\n return captureCanvas(element, options);\n}\n\n/** Encode a canvas (PNG by default; JPEG/WebP take a 0–1 quality). Throws when the browser refuses. */\nexport async function canvasToBlob(canvas: HTMLCanvasElement, type: \"image/png\" | \"image/jpeg\" | \"image/webp\" = \"image/png\", quality?: number): Promise<Blob> {\n const blob = await new Promise<Blob | null>((resolve) => canvas.toBlob(resolve, type, quality));\n if (!blob) throw new Error(\"The image could not be encoded.\");\n return blob;\n}\n\n/** Capture an element straight to an image Blob. */\nexport async function elementToImage(\n element: HTMLElement,\n options: CaptureOptions & { type?: \"image/png\" | \"image/jpeg\" | \"image/webp\"; quality?: number } = {},\n): Promise<Blob> {\n const { type, quality, ...capture } = options;\n return canvasToBlob(await captureElement(element, capture), type, quality);\n}\n\n/** Options for `renderElement` (html-to-image): the SVG foreignObject renderer. */\nexport type RenderOptions = {\n /** Device-pixel ratio (default 1). */\n pixelRatio?: number;\n width?: number;\n height?: number;\n /** Final canvas size; the render is scaled to it. */\n canvasWidth?: number;\n canvasHeight?: number;\n backgroundColor?: string;\n /** Inline style applied to the cloned root (e.g. a translate to pad the chart). */\n style?: Partial<CSSStyleDeclaration>;\n /** Return false to leave a node out of the picture. */\n filter?(node: HTMLElement): boolean;\n skipFonts?: boolean;\n /** Bust the image cache so a CORS fetch is fresh. */\n cacheBust?: boolean;\n /** Data URL drawn instead of an image that fails to load. */\n imagePlaceholder?: string;\n onImageErrorHandler?(...args: unknown[]): void;\n};\n\n/**\n * Render a live element to a canvas through the SVG foreignObject renderer (html-to-image). It keeps\n * the browser's own layout and fonts where html2canvas re-implements them, and it takes a `filter`\n * and a root `style` override — the right tool for a transformed or panned surface. Loads on first use.\n */\nexport async function renderElement(element: HTMLElement, options: RenderOptions = {}): Promise<HTMLCanvasElement> {\n const { toCanvas } = await import(\"html-to-image\");\n return toCanvas(element, options as Parameters<typeof toCanvas>[1]);\n}\n\n/** `renderElement` answering a PNG data URL (what a chat attachment preview needs besides the Blob). */\nexport async function renderElementToDataUrl(element: HTMLElement, options: RenderOptions = {}): Promise<string> {\n const { toPng } = await import(\"html-to-image\");\n return toPng(element, options as Parameters<typeof toPng>[1]);\n}\n\n/** Lay canvases out as PDF pages (one page each) and answer the PDF Blob — print's `pagesToPdf`. */\nexport async function pagesToPdf(pages: readonly PdfPage[], options: PdfDocumentOptions = {}): Promise<Blob> {\n const { pagesToPdf: layOut } = await import(\"@ai-matrx/print/pdf\");\n return layOut(pages, options);\n}\n\n/**\n * A DOCUMENT as a PDF Blob, through print's html2canvas + jsPDF engine. One element (a page, a\n * response — arbitrarily tall) is sliced into paper pages (print's `captureToPDFBlob`); a list of\n * elements is a designed report whose pages are its own, one PDF page each (`captureElementsToPDFBlob`).\n * Save it with kit `downloadFile`, or upload it. A PICTURE of a surface is `renderElement` →\n * `pagesToPdf` instead (rasteriser ruling A6).\n */\nexport async function captureDocumentPdf(\n element: HTMLElement | readonly HTMLElement[],\n options: DocumentPdfOptions = {},\n): Promise<Blob> {\n const engine = await import(\"@ai-matrx/print/pdf\");\n return Array.isArray(element)\n ? engine.captureElementsToPDFBlob([...element], options)\n : engine.captureToPDFBlob(element as HTMLElement, options);\n}\n\n/** Capture each element and lay them out one per PDF page. */\nexport async function elementsToPdf(\n elements: readonly HTMLElement[],\n options: PdfDocumentOptions & { capture?: CaptureOptions; page?: Omit<PdfPage, \"image\"> } = {},\n): Promise<Blob> {\n const pages: PdfPage[] = [];\n for (const element of elements) pages.push({ ...options.page, image: await captureElement(element, options.capture) });\n return pagesToPdf(pages, options);\n}\n"],"mappings":"y5BAAA,gZCoBA,eAAsB,eAAe,QAAsB,QAA0B,CAAC,EAA+B,CACnH,GAAM,CAAE,aAAc,EAAI,KAAM,QAAO,qBAAqB,EAC5D,OAAO,cAAc,QAAS,OAAO,CACvC,CAGA,eAAsB,aAAa,OAA2B,KAAkD,YAAa,QAAiC,CAC5J,IAAM,KAAO,MAAM,IAAI,QAAsB,SAAY,OAAO,OAAO,QAAS,KAAM,OAAO,CAAC,EAC9F,GAAI,CAAC,KAAM,MAAM,IAAI,MAAM,iCAAiC,EAC5D,OAAO,IACT,CAGA,eAAsB,eACpB,QACA,QAAmG,CAAC,EACrF,CACf,GAAM,CAAE,KAAM,QAAS,GAAG,OAAQ,EAAI,QACtC,OAAO,aAAa,MAAM,eAAe,QAAS,OAAO,EAAG,KAAM,OAAO,CAC3E,CA6BA,eAAsB,cAAc,QAAsB,QAAyB,CAAC,EAA+B,CACjH,GAAM,CAAE,QAAS,EAAI,KAAM,QAAO,eAAe,EACjD,OAAO,SAAS,QAAS,OAAyC,CACpE,CAGA,eAAsB,uBAAuB,QAAsB,QAAyB,CAAC,EAAoB,CAC/G,GAAM,CAAE,KAAM,EAAI,KAAM,QAAO,eAAe,EAC9C,OAAO,MAAM,QAAS,OAAsC,CAC9D,CAGA,eAAsB,WAAW,MAA2B,QAA8B,CAAC,EAAkB,CAC3G,GAAM,CAAE,WAAY,MAAO,EAAI,KAAM,QAAO,qBAAqB,EACjE,OAAO,OAAO,MAAO,OAAO,CAC9B,CASA,eAAsB,mBACpB,QACA,QAA8B,CAAC,EAChB,CACf,IAAM,OAAS,KAAM,QAAO,qBAAqB,EACjD,OAAO,MAAM,QAAQ,OAAO,EACxB,OAAO,yBAAyB,CAAC,GAAG,OAAO,EAAG,OAAO,EACrD,OAAO,iBAAiB,QAAwB,OAAO,CAC7D,CAGA,eAAsB,cACpB,SACA,QAA4F,CAAC,EAC9E,CACf,IAAM,MAAmB,CAAC,EAC1B,QAAW,WAAW,SAAU,MAAM,KAAK,CAAE,GAAG,QAAQ,KAAM,MAAO,MAAM,eAAe,QAAS,QAAQ,OAAO,CAAE,CAAC,EACrH,OAAO,WAAW,MAAO,OAAO,CAClC","names":[]}
@@ -1,4 +1,4 @@
1
- import { CanvasCaptureOptions, PdfDocumentOptions, PdfPage } from '@ai-matrx/print/pdf';
1
+ import { DomCaptureOptions, CanvasCaptureOptions, PdfDocumentOptions, PdfPage } from '@ai-matrx/print/pdf';
2
2
  export { CanvasCaptureOptions as CaptureOptions, PdfDocumentOptions, PdfMargins, PdfPage } from '@ai-matrx/print/pdf';
3
3
 
4
4
  /**
@@ -12,6 +12,8 @@ export { CanvasCaptureOptions as CaptureOptions, PdfDocumentOptions, PdfMargins,
12
12
  * value; `safeColors` substitutes theme-appropriate hex values for the duration of the capture.
13
13
  */
14
14
 
15
+ /** Options for `captureDocumentPdf` (print's DOCUMENT capture: paper, orientation, scale, progress, theme). */
16
+ type DocumentPdfOptions = Omit<DomCaptureOptions, "filename">;
15
17
  type CaptureOptions = CanvasCaptureOptions;
16
18
  /** Rasterise a live element to a canvas (what the person sees, at `scale`) — print's `captureCanvas`. */
17
19
  declare function captureElement(element: HTMLElement, options?: CaptureOptions): Promise<HTMLCanvasElement>;
@@ -53,10 +55,18 @@ declare function renderElement(element: HTMLElement, options?: RenderOptions): P
53
55
  declare function renderElementToDataUrl(element: HTMLElement, options?: RenderOptions): Promise<string>;
54
56
  /** Lay canvases out as PDF pages (one page each) and answer the PDF Blob — print's `pagesToPdf`. */
55
57
  declare function pagesToPdf(pages: readonly PdfPage[], options?: PdfDocumentOptions): Promise<Blob>;
58
+ /**
59
+ * A DOCUMENT as a PDF Blob, through print's html2canvas + jsPDF engine. One element (a page, a
60
+ * response — arbitrarily tall) is sliced into paper pages (print's `captureToPDFBlob`); a list of
61
+ * elements is a designed report whose pages are its own, one PDF page each (`captureElementsToPDFBlob`).
62
+ * Save it with kit `downloadFile`, or upload it. A PICTURE of a surface is `renderElement` →
63
+ * `pagesToPdf` instead (rasteriser ruling A6).
64
+ */
65
+ declare function captureDocumentPdf(element: HTMLElement | readonly HTMLElement[], options?: DocumentPdfOptions): Promise<Blob>;
56
66
  /** Capture each element and lay them out one per PDF page. */
57
67
  declare function elementsToPdf(elements: readonly HTMLElement[], options?: PdfDocumentOptions & {
58
68
  capture?: CaptureOptions;
59
69
  page?: Omit<PdfPage, "image">;
60
70
  }): Promise<Blob>;
61
71
 
62
- export { type RenderOptions, canvasToBlob, captureElement, elementToImage, elementsToPdf, pagesToPdf, renderElement, renderElementToDataUrl };
72
+ export { type DocumentPdfOptions, type RenderOptions, canvasToBlob, captureDocumentPdf, captureElement, elementToImage, elementsToPdf, pagesToPdf, renderElement, renderElementToDataUrl };
@@ -1,4 +1,4 @@
1
- import { CanvasCaptureOptions, PdfDocumentOptions, PdfPage } from '@ai-matrx/print/pdf';
1
+ import { DomCaptureOptions, CanvasCaptureOptions, PdfDocumentOptions, PdfPage } from '@ai-matrx/print/pdf';
2
2
  export { CanvasCaptureOptions as CaptureOptions, PdfDocumentOptions, PdfMargins, PdfPage } from '@ai-matrx/print/pdf';
3
3
 
4
4
  /**
@@ -12,6 +12,8 @@ export { CanvasCaptureOptions as CaptureOptions, PdfDocumentOptions, PdfMargins,
12
12
  * value; `safeColors` substitutes theme-appropriate hex values for the duration of the capture.
13
13
  */
14
14
 
15
+ /** Options for `captureDocumentPdf` (print's DOCUMENT capture: paper, orientation, scale, progress, theme). */
16
+ type DocumentPdfOptions = Omit<DomCaptureOptions, "filename">;
15
17
  type CaptureOptions = CanvasCaptureOptions;
16
18
  /** Rasterise a live element to a canvas (what the person sees, at `scale`) — print's `captureCanvas`. */
17
19
  declare function captureElement(element: HTMLElement, options?: CaptureOptions): Promise<HTMLCanvasElement>;
@@ -53,10 +55,18 @@ declare function renderElement(element: HTMLElement, options?: RenderOptions): P
53
55
  declare function renderElementToDataUrl(element: HTMLElement, options?: RenderOptions): Promise<string>;
54
56
  /** Lay canvases out as PDF pages (one page each) and answer the PDF Blob — print's `pagesToPdf`. */
55
57
  declare function pagesToPdf(pages: readonly PdfPage[], options?: PdfDocumentOptions): Promise<Blob>;
58
+ /**
59
+ * A DOCUMENT as a PDF Blob, through print's html2canvas + jsPDF engine. One element (a page, a
60
+ * response — arbitrarily tall) is sliced into paper pages (print's `captureToPDFBlob`); a list of
61
+ * elements is a designed report whose pages are its own, one PDF page each (`captureElementsToPDFBlob`).
62
+ * Save it with kit `downloadFile`, or upload it. A PICTURE of a surface is `renderElement` →
63
+ * `pagesToPdf` instead (rasteriser ruling A6).
64
+ */
65
+ declare function captureDocumentPdf(element: HTMLElement | readonly HTMLElement[], options?: DocumentPdfOptions): Promise<Blob>;
56
66
  /** Capture each element and lay them out one per PDF page. */
57
67
  declare function elementsToPdf(elements: readonly HTMLElement[], options?: PdfDocumentOptions & {
58
68
  capture?: CaptureOptions;
59
69
  page?: Omit<PdfPage, "image">;
60
70
  }): Promise<Blob>;
61
71
 
62
- export { type RenderOptions, canvasToBlob, captureElement, elementToImage, elementsToPdf, pagesToPdf, renderElement, renderElementToDataUrl };
72
+ export { type DocumentPdfOptions, type RenderOptions, canvasToBlob, captureDocumentPdf, captureElement, elementToImage, elementsToPdf, pagesToPdf, renderElement, renderElementToDataUrl };
@@ -1,2 +1,2 @@
1
- async function captureElement(element,options={}){let{captureCanvas}=await import("@ai-matrx/print/pdf");return captureCanvas(element,options)}async function canvasToBlob(canvas,type="image/png",quality){let blob=await new Promise(resolve=>canvas.toBlob(resolve,type,quality));if(!blob)throw new Error("The image could not be encoded.");return blob}async function elementToImage(element,options={}){let{type,quality,...capture}=options;return canvasToBlob(await captureElement(element,capture),type,quality)}async function renderElement(element,options={}){let{toCanvas}=await import("html-to-image");return toCanvas(element,options)}async function renderElementToDataUrl(element,options={}){let{toPng}=await import("html-to-image");return toPng(element,options)}async function pagesToPdf(pages,options={}){let{pagesToPdf:layOut}=await import("@ai-matrx/print/pdf");return layOut(pages,options)}async function elementsToPdf(elements,options={}){let pages=[];for(let element of elements)pages.push({...options.page,image:await captureElement(element,options.capture)});return pagesToPdf(pages,options)}export{canvasToBlob,captureElement,elementToImage,elementsToPdf,pagesToPdf,renderElement,renderElementToDataUrl};
1
+ async function captureElement(element,options={}){let{captureCanvas}=await import("@ai-matrx/print/pdf");return captureCanvas(element,options)}async function canvasToBlob(canvas,type="image/png",quality){let blob=await new Promise(resolve=>canvas.toBlob(resolve,type,quality));if(!blob)throw new Error("The image could not be encoded.");return blob}async function elementToImage(element,options={}){let{type,quality,...capture}=options;return canvasToBlob(await captureElement(element,capture),type,quality)}async function renderElement(element,options={}){let{toCanvas}=await import("html-to-image");return toCanvas(element,options)}async function renderElementToDataUrl(element,options={}){let{toPng}=await import("html-to-image");return toPng(element,options)}async function pagesToPdf(pages,options={}){let{pagesToPdf:layOut}=await import("@ai-matrx/print/pdf");return layOut(pages,options)}async function captureDocumentPdf(element,options={}){let engine=await import("@ai-matrx/print/pdf");return Array.isArray(element)?engine.captureElementsToPDFBlob([...element],options):engine.captureToPDFBlob(element,options)}async function elementsToPdf(elements,options={}){let pages=[];for(let element of elements)pages.push({...options.page,image:await captureElement(element,options.capture)});return pagesToPdf(pages,options)}export{canvasToBlob,captureDocumentPdf,captureElement,elementToImage,elementsToPdf,pagesToPdf,renderElement,renderElementToDataUrl};
2
2
  //# sourceMappingURL=operate-capture.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/operate/capture.ts"],"sourcesContent":["/**\n * `@ai-matrx/alchemy/operate/capture` — THE DOM capture door (ALC-19 T11): an element on screen →\n * an image or a PDF, with nothing else in the app importing html2canvas or jsPDF. The html2canvas +\n * jsPDF engine itself is `@ai-matrx/print/pdf` (`captureCanvas`, `pagesToPdf`), loaded on first use\n * (`import()`), so a page that never exports never downloads it; html-to-image (`renderElement`) is\n * this module's own.\n *\n * Tailwind 4 colours (oklch/lab/color()) are not parseable by html2canvas, which logs an error per\n * value; `safeColors` substitutes theme-appropriate hex values for the duration of the capture.\n */\n\n// THE one html2canvas + jsPDF engine is print's (AP-2 engine ruling: print sits below alchemy, so\n// alchemy loads it — never a second copy here). Type-only imports; the module loads on first use.\nimport type { CanvasCaptureOptions, PdfDocumentOptions, PdfPage } from \"@ai-matrx/print/pdf\";\nexport type { CanvasCaptureOptions as CaptureOptions, PdfDocumentOptions, PdfMargins, PdfPage } from \"@ai-matrx/print/pdf\";\ntype CaptureOptions = CanvasCaptureOptions;\n\n/** Rasterise a live element to a canvas (what the person sees, at `scale`) — print's `captureCanvas`. */\nexport async function captureElement(element: HTMLElement, options: CaptureOptions = {}): Promise<HTMLCanvasElement> {\n const { captureCanvas } = await import(\"@ai-matrx/print/pdf\");\n return captureCanvas(element, options);\n}\n\n/** Encode a canvas (PNG by default; JPEG/WebP take a 0–1 quality). Throws when the browser refuses. */\nexport async function canvasToBlob(canvas: HTMLCanvasElement, type: \"image/png\" | \"image/jpeg\" | \"image/webp\" = \"image/png\", quality?: number): Promise<Blob> {\n const blob = await new Promise<Blob | null>((resolve) => canvas.toBlob(resolve, type, quality));\n if (!blob) throw new Error(\"The image could not be encoded.\");\n return blob;\n}\n\n/** Capture an element straight to an image Blob. */\nexport async function elementToImage(\n element: HTMLElement,\n options: CaptureOptions & { type?: \"image/png\" | \"image/jpeg\" | \"image/webp\"; quality?: number } = {},\n): Promise<Blob> {\n const { type, quality, ...capture } = options;\n return canvasToBlob(await captureElement(element, capture), type, quality);\n}\n\n/** Options for `renderElement` (html-to-image): the SVG foreignObject renderer. */\nexport type RenderOptions = {\n /** Device-pixel ratio (default 1). */\n pixelRatio?: number;\n width?: number;\n height?: number;\n /** Final canvas size; the render is scaled to it. */\n canvasWidth?: number;\n canvasHeight?: number;\n backgroundColor?: string;\n /** Inline style applied to the cloned root (e.g. a translate to pad the chart). */\n style?: Partial<CSSStyleDeclaration>;\n /** Return false to leave a node out of the picture. */\n filter?(node: HTMLElement): boolean;\n skipFonts?: boolean;\n /** Bust the image cache so a CORS fetch is fresh. */\n cacheBust?: boolean;\n /** Data URL drawn instead of an image that fails to load. */\n imagePlaceholder?: string;\n onImageErrorHandler?(...args: unknown[]): void;\n};\n\n/**\n * Render a live element to a canvas through the SVG foreignObject renderer (html-to-image). It keeps\n * the browser's own layout and fonts where html2canvas re-implements them, and it takes a `filter`\n * and a root `style` override — the right tool for a transformed or panned surface. Loads on first use.\n */\nexport async function renderElement(element: HTMLElement, options: RenderOptions = {}): Promise<HTMLCanvasElement> {\n const { toCanvas } = await import(\"html-to-image\");\n return toCanvas(element, options as Parameters<typeof toCanvas>[1]);\n}\n\n/** `renderElement` answering a PNG data URL (what a chat attachment preview needs besides the Blob). */\nexport async function renderElementToDataUrl(element: HTMLElement, options: RenderOptions = {}): Promise<string> {\n const { toPng } = await import(\"html-to-image\");\n return toPng(element, options as Parameters<typeof toPng>[1]);\n}\n\n/** Lay canvases out as PDF pages (one page each) and answer the PDF Blob — print's `pagesToPdf`. */\nexport async function pagesToPdf(pages: readonly PdfPage[], options: PdfDocumentOptions = {}): Promise<Blob> {\n const { pagesToPdf: layOut } = await import(\"@ai-matrx/print/pdf\");\n return layOut(pages, options);\n}\n\n/** Capture each element and lay them out one per PDF page. */\nexport async function elementsToPdf(\n elements: readonly HTMLElement[],\n options: PdfDocumentOptions & { capture?: CaptureOptions; page?: Omit<PdfPage, \"image\"> } = {},\n): Promise<Blob> {\n const pages: PdfPage[] = [];\n for (const element of elements) pages.push({ ...options.page, image: await captureElement(element, options.capture) });\n return pagesToPdf(pages, options);\n}\n"],"mappings":"AAkBA,eAAsB,eAAe,QAAsB,QAA0B,CAAC,EAA+B,CACnH,GAAM,CAAE,aAAc,EAAI,KAAM,QAAO,qBAAqB,EAC5D,OAAO,cAAc,QAAS,OAAO,CACvC,CAGA,eAAsB,aAAa,OAA2B,KAAkD,YAAa,QAAiC,CAC5J,IAAM,KAAO,MAAM,IAAI,QAAsB,SAAY,OAAO,OAAO,QAAS,KAAM,OAAO,CAAC,EAC9F,GAAI,CAAC,KAAM,MAAM,IAAI,MAAM,iCAAiC,EAC5D,OAAO,IACT,CAGA,eAAsB,eACpB,QACA,QAAmG,CAAC,EACrF,CACf,GAAM,CAAE,KAAM,QAAS,GAAG,OAAQ,EAAI,QACtC,OAAO,aAAa,MAAM,eAAe,QAAS,OAAO,EAAG,KAAM,OAAO,CAC3E,CA6BA,eAAsB,cAAc,QAAsB,QAAyB,CAAC,EAA+B,CACjH,GAAM,CAAE,QAAS,EAAI,KAAM,QAAO,eAAe,EACjD,OAAO,SAAS,QAAS,OAAyC,CACpE,CAGA,eAAsB,uBAAuB,QAAsB,QAAyB,CAAC,EAAoB,CAC/G,GAAM,CAAE,KAAM,EAAI,KAAM,QAAO,eAAe,EAC9C,OAAO,MAAM,QAAS,OAAsC,CAC9D,CAGA,eAAsB,WAAW,MAA2B,QAA8B,CAAC,EAAkB,CAC3G,GAAM,CAAE,WAAY,MAAO,EAAI,KAAM,QAAO,qBAAqB,EACjE,OAAO,OAAO,MAAO,OAAO,CAC9B,CAGA,eAAsB,cACpB,SACA,QAA4F,CAAC,EAC9E,CACf,IAAM,MAAmB,CAAC,EAC1B,QAAW,WAAW,SAAU,MAAM,KAAK,CAAE,GAAG,QAAQ,KAAM,MAAO,MAAM,eAAe,QAAS,QAAQ,OAAO,CAAE,CAAC,EACrH,OAAO,WAAW,MAAO,OAAO,CAClC","names":[]}
1
+ {"version":3,"sources":["../src/operate/capture.ts"],"sourcesContent":["/**\n * `@ai-matrx/alchemy/operate/capture` — THE DOM capture door (ALC-19 T11): an element on screen →\n * an image or a PDF, with nothing else in the app importing html2canvas or jsPDF. The html2canvas +\n * jsPDF engine itself is `@ai-matrx/print/pdf` (`captureCanvas`, `pagesToPdf`), loaded on first use\n * (`import()`), so a page that never exports never downloads it; html-to-image (`renderElement`) is\n * this module's own.\n *\n * Tailwind 4 colours (oklch/lab/color()) are not parseable by html2canvas, which logs an error per\n * value; `safeColors` substitutes theme-appropriate hex values for the duration of the capture.\n */\n\n// THE one html2canvas + jsPDF engine is print's (AP-2 engine ruling: print sits below alchemy, so\n// alchemy loads it — never a second copy here). Type-only imports; the module loads on first use.\nimport type { CanvasCaptureOptions, DomCaptureOptions, PdfDocumentOptions, PdfPage } from \"@ai-matrx/print/pdf\";\nexport type { CanvasCaptureOptions as CaptureOptions, PdfDocumentOptions, PdfMargins, PdfPage } from \"@ai-matrx/print/pdf\";\n/** Options for `captureDocumentPdf` (print's DOCUMENT capture: paper, orientation, scale, progress, theme). */\nexport type DocumentPdfOptions = Omit<DomCaptureOptions, \"filename\">;\ntype CaptureOptions = CanvasCaptureOptions;\n\n/** Rasterise a live element to a canvas (what the person sees, at `scale`) — print's `captureCanvas`. */\nexport async function captureElement(element: HTMLElement, options: CaptureOptions = {}): Promise<HTMLCanvasElement> {\n const { captureCanvas } = await import(\"@ai-matrx/print/pdf\");\n return captureCanvas(element, options);\n}\n\n/** Encode a canvas (PNG by default; JPEG/WebP take a 0–1 quality). Throws when the browser refuses. */\nexport async function canvasToBlob(canvas: HTMLCanvasElement, type: \"image/png\" | \"image/jpeg\" | \"image/webp\" = \"image/png\", quality?: number): Promise<Blob> {\n const blob = await new Promise<Blob | null>((resolve) => canvas.toBlob(resolve, type, quality));\n if (!blob) throw new Error(\"The image could not be encoded.\");\n return blob;\n}\n\n/** Capture an element straight to an image Blob. */\nexport async function elementToImage(\n element: HTMLElement,\n options: CaptureOptions & { type?: \"image/png\" | \"image/jpeg\" | \"image/webp\"; quality?: number } = {},\n): Promise<Blob> {\n const { type, quality, ...capture } = options;\n return canvasToBlob(await captureElement(element, capture), type, quality);\n}\n\n/** Options for `renderElement` (html-to-image): the SVG foreignObject renderer. */\nexport type RenderOptions = {\n /** Device-pixel ratio (default 1). */\n pixelRatio?: number;\n width?: number;\n height?: number;\n /** Final canvas size; the render is scaled to it. */\n canvasWidth?: number;\n canvasHeight?: number;\n backgroundColor?: string;\n /** Inline style applied to the cloned root (e.g. a translate to pad the chart). */\n style?: Partial<CSSStyleDeclaration>;\n /** Return false to leave a node out of the picture. */\n filter?(node: HTMLElement): boolean;\n skipFonts?: boolean;\n /** Bust the image cache so a CORS fetch is fresh. */\n cacheBust?: boolean;\n /** Data URL drawn instead of an image that fails to load. */\n imagePlaceholder?: string;\n onImageErrorHandler?(...args: unknown[]): void;\n};\n\n/**\n * Render a live element to a canvas through the SVG foreignObject renderer (html-to-image). It keeps\n * the browser's own layout and fonts where html2canvas re-implements them, and it takes a `filter`\n * and a root `style` override — the right tool for a transformed or panned surface. Loads on first use.\n */\nexport async function renderElement(element: HTMLElement, options: RenderOptions = {}): Promise<HTMLCanvasElement> {\n const { toCanvas } = await import(\"html-to-image\");\n return toCanvas(element, options as Parameters<typeof toCanvas>[1]);\n}\n\n/** `renderElement` answering a PNG data URL (what a chat attachment preview needs besides the Blob). */\nexport async function renderElementToDataUrl(element: HTMLElement, options: RenderOptions = {}): Promise<string> {\n const { toPng } = await import(\"html-to-image\");\n return toPng(element, options as Parameters<typeof toPng>[1]);\n}\n\n/** Lay canvases out as PDF pages (one page each) and answer the PDF Blob — print's `pagesToPdf`. */\nexport async function pagesToPdf(pages: readonly PdfPage[], options: PdfDocumentOptions = {}): Promise<Blob> {\n const { pagesToPdf: layOut } = await import(\"@ai-matrx/print/pdf\");\n return layOut(pages, options);\n}\n\n/**\n * A DOCUMENT as a PDF Blob, through print's html2canvas + jsPDF engine. One element (a page, a\n * response — arbitrarily tall) is sliced into paper pages (print's `captureToPDFBlob`); a list of\n * elements is a designed report whose pages are its own, one PDF page each (`captureElementsToPDFBlob`).\n * Save it with kit `downloadFile`, or upload it. A PICTURE of a surface is `renderElement` →\n * `pagesToPdf` instead (rasteriser ruling A6).\n */\nexport async function captureDocumentPdf(\n element: HTMLElement | readonly HTMLElement[],\n options: DocumentPdfOptions = {},\n): Promise<Blob> {\n const engine = await import(\"@ai-matrx/print/pdf\");\n return Array.isArray(element)\n ? engine.captureElementsToPDFBlob([...element], options)\n : engine.captureToPDFBlob(element as HTMLElement, options);\n}\n\n/** Capture each element and lay them out one per PDF page. */\nexport async function elementsToPdf(\n elements: readonly HTMLElement[],\n options: PdfDocumentOptions & { capture?: CaptureOptions; page?: Omit<PdfPage, \"image\"> } = {},\n): Promise<Blob> {\n const pages: PdfPage[] = [];\n for (const element of elements) pages.push({ ...options.page, image: await captureElement(element, options.capture) });\n return pagesToPdf(pages, options);\n}\n"],"mappings":"AAoBA,eAAsB,eAAe,QAAsB,QAA0B,CAAC,EAA+B,CACnH,GAAM,CAAE,aAAc,EAAI,KAAM,QAAO,qBAAqB,EAC5D,OAAO,cAAc,QAAS,OAAO,CACvC,CAGA,eAAsB,aAAa,OAA2B,KAAkD,YAAa,QAAiC,CAC5J,IAAM,KAAO,MAAM,IAAI,QAAsB,SAAY,OAAO,OAAO,QAAS,KAAM,OAAO,CAAC,EAC9F,GAAI,CAAC,KAAM,MAAM,IAAI,MAAM,iCAAiC,EAC5D,OAAO,IACT,CAGA,eAAsB,eACpB,QACA,QAAmG,CAAC,EACrF,CACf,GAAM,CAAE,KAAM,QAAS,GAAG,OAAQ,EAAI,QACtC,OAAO,aAAa,MAAM,eAAe,QAAS,OAAO,EAAG,KAAM,OAAO,CAC3E,CA6BA,eAAsB,cAAc,QAAsB,QAAyB,CAAC,EAA+B,CACjH,GAAM,CAAE,QAAS,EAAI,KAAM,QAAO,eAAe,EACjD,OAAO,SAAS,QAAS,OAAyC,CACpE,CAGA,eAAsB,uBAAuB,QAAsB,QAAyB,CAAC,EAAoB,CAC/G,GAAM,CAAE,KAAM,EAAI,KAAM,QAAO,eAAe,EAC9C,OAAO,MAAM,QAAS,OAAsC,CAC9D,CAGA,eAAsB,WAAW,MAA2B,QAA8B,CAAC,EAAkB,CAC3G,GAAM,CAAE,WAAY,MAAO,EAAI,KAAM,QAAO,qBAAqB,EACjE,OAAO,OAAO,MAAO,OAAO,CAC9B,CASA,eAAsB,mBACpB,QACA,QAA8B,CAAC,EAChB,CACf,IAAM,OAAS,KAAM,QAAO,qBAAqB,EACjD,OAAO,MAAM,QAAQ,OAAO,EACxB,OAAO,yBAAyB,CAAC,GAAG,OAAO,EAAG,OAAO,EACrD,OAAO,iBAAiB,QAAwB,OAAO,CAC7D,CAGA,eAAsB,cACpB,SACA,QAA4F,CAAC,EAC9E,CACf,IAAM,MAAmB,CAAC,EAC1B,QAAW,WAAW,SAAU,MAAM,KAAK,CAAE,GAAG,QAAQ,KAAM,MAAO,MAAM,eAAe,QAAS,QAAQ,OAAO,CAAE,CAAC,EACrH,OAAO,WAAW,MAAO,OAAO,CAClC","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-matrx/alchemy",
3
- "version": "0.19.1",
3
+ "version": "0.20.4",
4
4
  "description": "Matrx Alchemy: the complete Matrx copy, prepare-for-AI, and export experience, with framework-neutral core, React menu, glyph and styles; Declare (what a screen holds: types, registry, validation, JSON schema) and the one surface sync; Actions (the one action registry: eligibility, duplicate refusal, projected reads) and the one menu engine (model + bar, overflow, context, selection, sheet and palette layouts as per-layout React subpaths); Surface (the one SurfacePort runtime: the database surface chain with organization extensions, values, live values a mounted page publishes, item sources, and Action runs).",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -361,12 +361,12 @@
361
361
  ],
362
362
  "dependencies": {
363
363
  "html-to-image": "^1.11.13",
364
- "@ai-matrx/design-system": "latest",
365
364
  "@ai-matrx/kit": "latest",
366
- "@ai-matrx/print": "latest"
365
+ "@ai-matrx/print": "latest",
366
+ "@ai-matrx/design-system": "latest"
367
367
  },
368
368
  "scripts": {
369
- "build": "pnpm clean && tsup && node ./scripts/ignore-node-builtins.mjs && node ./scripts/copy-assets.mjs && node ./scripts/assert-distribution-artifacts.mjs && node ./scripts/check-subpath-sizes.mjs && node ../scripts/check-node-env-not-inlined.mjs",
369
+ "build": "pnpm clean && node ./scripts/build.mjs && node ./scripts/ignore-node-builtins.mjs && node ./scripts/copy-assets.mjs && node ./scripts/assert-distribution-artifacts.mjs && node ./scripts/check-subpath-sizes.mjs && node ../scripts/check-node-env-not-inlined.mjs",
370
370
  "clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"",
371
371
  "check:package": "node ./scripts/generate-declare-schema.mjs --check && node ../scripts/check-kit-never-imports-print.mjs && pnpm build && node ./scripts/check-operate-sizes.mjs && node ./scripts/check-operate-sizes.mjs --self-test && node ./scripts/check-bundled-deps.mjs --self-test && node ./scripts/check-bundled-deps.mjs && node ../scripts/check-exports-agreement.mjs && node ../scripts/check-alchemy-below-agents.mjs && publint && pnpm verify:tarball",
372
372
  "typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.shipped.json --noEmit",