@agimon-ai/doompi 0.0.1-alpha.74 → 0.0.1-alpha.77

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.
Files changed (59) hide show
  1. package/dist/builders/apiContractExport/index.cjs +2 -1
  2. package/dist/builders/apiContractExport/index.cjs.map +1 -1
  3. package/dist/builders/apiContractExport/index.mjs +2 -1
  4. package/dist/builders/apiContractExport/index.mjs.map +1 -1
  5. package/dist/builders/cli/bootstrapLocator/index.cjs +49 -16
  6. package/dist/builders/cli/bootstrapLocator/index.cjs.map +1 -1
  7. package/dist/builders/cli/bootstrapLocator/index.d.cts.map +1 -1
  8. package/dist/builders/cli/bootstrapLocator/index.d.mts.map +1 -1
  9. package/dist/builders/cli/bootstrapLocator/index.mjs +48 -16
  10. package/dist/builders/cli/bootstrapLocator/index.mjs.map +1 -1
  11. package/dist/builders/cli/piExtensionDispatcher/index.cjs +14 -5
  12. package/dist/builders/cli/piExtensionDispatcher/index.cjs.map +1 -1
  13. package/dist/builders/cli/piExtensionDispatcher/index.mjs +15 -6
  14. package/dist/builders/cli/piExtensionDispatcher/index.mjs.map +1 -1
  15. package/dist/builders/server/runtime.cjs +10 -0
  16. package/dist/builders/server/runtime.cjs.map +1 -1
  17. package/dist/builders/server/runtime.mjs +11 -1
  18. package/dist/builders/server/runtime.mjs.map +1 -1
  19. package/dist/cli/commands/sync/help.cjs +5 -2
  20. package/dist/cli/commands/sync/help.cjs.map +1 -1
  21. package/dist/cli/commands/sync/help.mjs +5 -2
  22. package/dist/cli/commands/sync/help.mjs.map +1 -1
  23. package/dist/cli/commands/sync/index.cjs +54 -31
  24. package/dist/cli/commands/sync/index.cjs.map +1 -1
  25. package/dist/cli/commands/sync/index.d.cts +12 -0
  26. package/dist/cli/commands/sync/index.d.cts.map +1 -1
  27. package/dist/cli/commands/sync/index.d.mts +12 -0
  28. package/dist/cli/commands/sync/index.d.mts.map +1 -1
  29. package/dist/cli/commands/sync/index.mjs +53 -33
  30. package/dist/cli/commands/sync/index.mjs.map +1 -1
  31. package/dist/cli/commands/sync/prepare.cjs +6 -9
  32. package/dist/cli/commands/sync/prepare.cjs.map +1 -1
  33. package/dist/cli/commands/sync/prepare.mjs +6 -8
  34. package/dist/cli/commands/sync/prepare.mjs.map +1 -1
  35. package/dist/cli/commands/sync/workflow.cjs +21 -22
  36. package/dist/cli/commands/sync/workflow.cjs.map +1 -1
  37. package/dist/cli/commands/sync/workflow.mjs +16 -17
  38. package/dist/cli/commands/sync/workflow.mjs.map +1 -1
  39. package/dist/cli/server/run.cjs +8 -1
  40. package/dist/cli/server/run.cjs.map +1 -1
  41. package/dist/cli/server/run.mjs +8 -1
  42. package/dist/cli/server/run.mjs.map +1 -1
  43. package/dist/compiler/index.cjs +19 -0
  44. package/dist/compiler/index.cjs.map +1 -1
  45. package/dist/compiler/index.mjs +19 -0
  46. package/dist/compiler/index.mjs.map +1 -1
  47. package/dist/composition/syncDrift/index.cjs +42 -14
  48. package/dist/composition/syncDrift/index.cjs.map +1 -1
  49. package/dist/composition/syncDrift/index.d.cts +2 -0
  50. package/dist/composition/syncDrift/index.d.cts.map +1 -1
  51. package/dist/composition/syncDrift/index.d.mts +2 -0
  52. package/dist/composition/syncDrift/index.d.mts.map +1 -1
  53. package/dist/composition/syncDrift/index.mjs +43 -16
  54. package/dist/composition/syncDrift/index.mjs.map +1 -1
  55. package/dist/sync-command.cjs +3 -0
  56. package/dist/sync-command.d.cts +2 -2
  57. package/dist/sync-command.d.mts +2 -2
  58. package/dist/sync-command.mjs +2 -2
  59. package/package.json +17 -16
@@ -20,7 +20,8 @@ function exportApiContracts(options) {
20
20
  const read = (root) => {
21
21
  const drift = require_index$1.readSyncDrift({
22
22
  repoRoot: root,
23
- homeDirectory: options.homeDirectory
23
+ homeDirectory: options.homeDirectory,
24
+ requireFreshSources: false
24
25
  });
25
26
  if (!drift.fresh) throw new Error(`Run doompi sync for '${root}' before exporting API contracts: ${drift.reasons.join(", ")}`);
26
27
  const registration = (0, _agimon_ai_doompi_core_sync_registration.readSyncRegistration)(root, options.homeDirectory);
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["globalDoomConfigDirectory","resolveDoomConfigurationRoot","readSyncDrift","readSyncRegistration","parseDoomServerBundle","fs","path","crypto","parseCompiledContracts","loadMajorModesConfig","resolveLayers","createApiDocuments","canonicalContractJson"],"sources":["../../../src/builders/apiContractExport/index.ts"],"sourcesContent":["import crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nimport { globalDoomConfigDirectory } from '@agimon-ai/doompi-config/config';\nimport { loadMajorModesConfig, resolveLayers } from '@agimon-ai/doompi-config/majorModes';\nimport {\n canonicalContractJson,\n createApiDocuments,\n parseCompiledContracts,\n type ApiContractSelection,\n type ApiDocuments,\n} from '@agimon-ai/doompi-core/api-contracts';\nimport { parseDoomServerBundle } from '@agimon-ai/doompi-core/server-facet';\nimport { readSyncRegistration } from '@agimon-ai/doompi-core/sync-registration';\n\nimport { resolveDoomConfigurationRoot } from '../../composition/repository';\nimport { readSyncDrift } from '../../composition/syncDrift';\n\n/** Read only published generations. Export must never implicitly sync or activate a facet. */\nexport function exportApiContracts(options: {\n cwd: string;\n homeDirectory: string;\n outputDirectory: string;\n majorMode?: string;\n}): ApiDocuments['manifest'] {\n const globalRoot = globalDoomConfigDirectory(options.homeDirectory);\n const workspaceRoot = resolveDoomConfigurationRoot(options.cwd, options.homeDirectory);\n const read = (root: string) => {\n const drift = readSyncDrift({ repoRoot: root, homeDirectory: options.homeDirectory });\n if (!drift.fresh)\n throw new Error(`Run doompi sync for '${root}' before exporting API contracts: ${drift.reasons.join(', ')}`);\n const registration = readSyncRegistration(root, options.homeDirectory);\n if (!registration?.serverBundle) throw new Error(`No synced server bundle for '${root}'`);\n const descriptor = parseDoomServerBundle(JSON.parse(fs.readFileSync(registration.serverBundle.path, 'utf8')));\n if (!descriptor.contracts) throw new Error(`Resync '${root}' to generate API contracts`);\n const file = path.resolve(path.dirname(registration.serverBundle.path), descriptor.contracts.file);\n const bytes = fs.readFileSync(file);\n if (crypto.createHash('sha256').update(bytes).digest('hex') !== descriptor.contracts.sha256)\n throw new Error(`API contract checksum mismatch for '${root}'`);\n return {\n compiled: parseCompiledContracts(JSON.parse(bytes.toString('utf8'))),\n config: loadMajorModesConfig(root, options.homeDirectory),\n };\n };\n const global = read(globalRoot);\n const workspace = globalRoot === workspaceRoot ? global : read(workspaceRoot);\n const sessionMode = options.majorMode ?? workspace.config.defaultMajorMode;\n const selection = (\n source: typeof global,\n scope: ApiContractSelection['scope'],\n majorMode: string,\n ): ApiContractSelection => ({\n scope,\n majorMode,\n activeLayers: resolveLayers(source.config, majorMode),\n compiled: source.compiled,\n });\n // A typo must not silently export an empty session surface.\n if (!Object.hasOwn(workspace.config.majorMode, sessionMode)) throw new Error(`Unknown major mode '${sessionMode}'`);\n const documents = createApiDocuments([\n selection(global, 'global', global.config.defaultMajorMode),\n selection(workspace, 'workspace', workspace.config.defaultMajorMode),\n selection(workspace, 'session', sessionMode),\n ]);\n const files = {\n 'openapi.json': canonicalContractJson(documents.openapi),\n 'asyncapi.json': canonicalContractJson(documents.asyncapi),\n };\n const manifest = {\n ...documents.manifest,\n artifacts: Object.fromEntries(\n Object.entries(files).map(([name, bytes]) => [\n name,\n {\n sha256: crypto.createHash('sha256').update(bytes).digest('hex'),\n },\n ]),\n ),\n };\n fs.mkdirSync(options.outputDirectory, { recursive: true });\n // Write the manifest last. Consumers verify both checksums before accepting the bundle.\n for (const [name, bytes] of Object.entries({ ...files, 'manifest.json': canonicalContractJson(manifest) })) {\n const target = path.join(options.outputDirectory, name);\n const temporary = `${target}.${crypto.randomUUID()}.tmp`;\n try {\n fs.writeFileSync(temporary, bytes, { flag: 'wx' });\n fs.renameSync(temporary, target);\n } finally {\n fs.rmSync(temporary, { force: true });\n }\n }\n return documents.manifest;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["globalDoomConfigDirectory","resolveDoomConfigurationRoot","readSyncDrift","readSyncRegistration","parseDoomServerBundle","fs","path","crypto","parseCompiledContracts","loadMajorModesConfig","resolveLayers","createApiDocuments","canonicalContractJson"],"sources":["../../../src/builders/apiContractExport/index.ts"],"sourcesContent":["import crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nimport { globalDoomConfigDirectory } from '@agimon-ai/doompi-config/config';\nimport { loadMajorModesConfig, resolveLayers } from '@agimon-ai/doompi-config/majorModes';\nimport {\n canonicalContractJson,\n createApiDocuments,\n parseCompiledContracts,\n type ApiContractSelection,\n type ApiDocuments,\n} from '@agimon-ai/doompi-core/api-contracts';\nimport { parseDoomServerBundle } from '@agimon-ai/doompi-core/server-facet';\nimport { readSyncRegistration } from '@agimon-ai/doompi-core/sync-registration';\n\nimport { resolveDoomConfigurationRoot } from '../../composition/repository';\nimport { readSyncDrift } from '../../composition/syncDrift';\n\n/** Read only published generations. Export must never implicitly sync or activate a facet. */\nexport function exportApiContracts(options: {\n cwd: string;\n homeDirectory: string;\n outputDirectory: string;\n majorMode?: string;\n}): ApiDocuments['manifest'] {\n const globalRoot = globalDoomConfigDirectory(options.homeDirectory);\n const workspaceRoot = resolveDoomConfigurationRoot(options.cwd, options.homeDirectory);\n const read = (root: string) => {\n const drift = readSyncDrift({\n repoRoot: root,\n homeDirectory: options.homeDirectory,\n requireFreshSources: false,\n });\n if (!drift.fresh)\n throw new Error(`Run doompi sync for '${root}' before exporting API contracts: ${drift.reasons.join(', ')}`);\n const registration = readSyncRegistration(root, options.homeDirectory);\n if (!registration?.serverBundle) throw new Error(`No synced server bundle for '${root}'`);\n const descriptor = parseDoomServerBundle(JSON.parse(fs.readFileSync(registration.serverBundle.path, 'utf8')));\n if (!descriptor.contracts) throw new Error(`Resync '${root}' to generate API contracts`);\n const file = path.resolve(path.dirname(registration.serverBundle.path), descriptor.contracts.file);\n const bytes = fs.readFileSync(file);\n if (crypto.createHash('sha256').update(bytes).digest('hex') !== descriptor.contracts.sha256)\n throw new Error(`API contract checksum mismatch for '${root}'`);\n return {\n compiled: parseCompiledContracts(JSON.parse(bytes.toString('utf8'))),\n config: loadMajorModesConfig(root, options.homeDirectory),\n };\n };\n const global = read(globalRoot);\n const workspace = globalRoot === workspaceRoot ? global : read(workspaceRoot);\n const sessionMode = options.majorMode ?? workspace.config.defaultMajorMode;\n const selection = (\n source: typeof global,\n scope: ApiContractSelection['scope'],\n majorMode: string,\n ): ApiContractSelection => ({\n scope,\n majorMode,\n activeLayers: resolveLayers(source.config, majorMode),\n compiled: source.compiled,\n });\n // A typo must not silently export an empty session surface.\n if (!Object.hasOwn(workspace.config.majorMode, sessionMode)) throw new Error(`Unknown major mode '${sessionMode}'`);\n const documents = createApiDocuments([\n selection(global, 'global', global.config.defaultMajorMode),\n selection(workspace, 'workspace', workspace.config.defaultMajorMode),\n selection(workspace, 'session', sessionMode),\n ]);\n const files = {\n 'openapi.json': canonicalContractJson(documents.openapi),\n 'asyncapi.json': canonicalContractJson(documents.asyncapi),\n };\n const manifest = {\n ...documents.manifest,\n artifacts: Object.fromEntries(\n Object.entries(files).map(([name, bytes]) => [\n name,\n {\n sha256: crypto.createHash('sha256').update(bytes).digest('hex'),\n },\n ]),\n ),\n };\n fs.mkdirSync(options.outputDirectory, { recursive: true });\n // Write the manifest last. Consumers verify both checksums before accepting the bundle.\n for (const [name, bytes] of Object.entries({ ...files, 'manifest.json': canonicalContractJson(manifest) })) {\n const target = path.join(options.outputDirectory, name);\n const temporary = `${target}.${crypto.randomUUID()}.tmp`;\n try {\n fs.writeFileSync(temporary, bytes, { flag: 'wx' });\n fs.renameSync(temporary, target);\n } finally {\n fs.rmSync(temporary, { force: true });\n }\n }\n return documents.manifest;\n}\n"],"mappings":"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"}
@@ -16,7 +16,8 @@ function exportApiContracts(options) {
16
16
  const read = (root) => {
17
17
  const drift = readSyncDrift({
18
18
  repoRoot: root,
19
- homeDirectory: options.homeDirectory
19
+ homeDirectory: options.homeDirectory,
20
+ requireFreshSources: false
20
21
  });
21
22
  if (!drift.fresh) throw new Error(`Run doompi sync for '${root}' before exporting API contracts: ${drift.reasons.join(", ")}`);
22
23
  const registration = readSyncRegistration(root, options.homeDirectory);
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../../src/builders/apiContractExport/index.ts"],"sourcesContent":["import crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nimport { globalDoomConfigDirectory } from '@agimon-ai/doompi-config/config';\nimport { loadMajorModesConfig, resolveLayers } from '@agimon-ai/doompi-config/majorModes';\nimport {\n canonicalContractJson,\n createApiDocuments,\n parseCompiledContracts,\n type ApiContractSelection,\n type ApiDocuments,\n} from '@agimon-ai/doompi-core/api-contracts';\nimport { parseDoomServerBundle } from '@agimon-ai/doompi-core/server-facet';\nimport { readSyncRegistration } from '@agimon-ai/doompi-core/sync-registration';\n\nimport { resolveDoomConfigurationRoot } from '../../composition/repository';\nimport { readSyncDrift } from '../../composition/syncDrift';\n\n/** Read only published generations. Export must never implicitly sync or activate a facet. */\nexport function exportApiContracts(options: {\n cwd: string;\n homeDirectory: string;\n outputDirectory: string;\n majorMode?: string;\n}): ApiDocuments['manifest'] {\n const globalRoot = globalDoomConfigDirectory(options.homeDirectory);\n const workspaceRoot = resolveDoomConfigurationRoot(options.cwd, options.homeDirectory);\n const read = (root: string) => {\n const drift = readSyncDrift({ repoRoot: root, homeDirectory: options.homeDirectory });\n if (!drift.fresh)\n throw new Error(`Run doompi sync for '${root}' before exporting API contracts: ${drift.reasons.join(', ')}`);\n const registration = readSyncRegistration(root, options.homeDirectory);\n if (!registration?.serverBundle) throw new Error(`No synced server bundle for '${root}'`);\n const descriptor = parseDoomServerBundle(JSON.parse(fs.readFileSync(registration.serverBundle.path, 'utf8')));\n if (!descriptor.contracts) throw new Error(`Resync '${root}' to generate API contracts`);\n const file = path.resolve(path.dirname(registration.serverBundle.path), descriptor.contracts.file);\n const bytes = fs.readFileSync(file);\n if (crypto.createHash('sha256').update(bytes).digest('hex') !== descriptor.contracts.sha256)\n throw new Error(`API contract checksum mismatch for '${root}'`);\n return {\n compiled: parseCompiledContracts(JSON.parse(bytes.toString('utf8'))),\n config: loadMajorModesConfig(root, options.homeDirectory),\n };\n };\n const global = read(globalRoot);\n const workspace = globalRoot === workspaceRoot ? global : read(workspaceRoot);\n const sessionMode = options.majorMode ?? workspace.config.defaultMajorMode;\n const selection = (\n source: typeof global,\n scope: ApiContractSelection['scope'],\n majorMode: string,\n ): ApiContractSelection => ({\n scope,\n majorMode,\n activeLayers: resolveLayers(source.config, majorMode),\n compiled: source.compiled,\n });\n // A typo must not silently export an empty session surface.\n if (!Object.hasOwn(workspace.config.majorMode, sessionMode)) throw new Error(`Unknown major mode '${sessionMode}'`);\n const documents = createApiDocuments([\n selection(global, 'global', global.config.defaultMajorMode),\n selection(workspace, 'workspace', workspace.config.defaultMajorMode),\n selection(workspace, 'session', sessionMode),\n ]);\n const files = {\n 'openapi.json': canonicalContractJson(documents.openapi),\n 'asyncapi.json': canonicalContractJson(documents.asyncapi),\n };\n const manifest = {\n ...documents.manifest,\n artifacts: Object.fromEntries(\n Object.entries(files).map(([name, bytes]) => [\n name,\n {\n sha256: crypto.createHash('sha256').update(bytes).digest('hex'),\n },\n ]),\n ),\n };\n fs.mkdirSync(options.outputDirectory, { recursive: true });\n // Write the manifest last. Consumers verify both checksums before accepting the bundle.\n for (const [name, bytes] of Object.entries({ ...files, 'manifest.json': canonicalContractJson(manifest) })) {\n const target = path.join(options.outputDirectory, name);\n const temporary = `${target}.${crypto.randomUUID()}.tmp`;\n try {\n fs.writeFileSync(temporary, bytes, { flag: 'wx' });\n fs.renameSync(temporary, target);\n } finally {\n fs.rmSync(temporary, { force: true });\n }\n }\n return documents.manifest;\n}\n"],"mappings":";;;;;;;;;;;;AAoBA,SAAgB,mBAAmB,SAKN;CAC3B,MAAM,aAAa,0BAA0B,QAAQ,aAAa;CAClE,MAAM,gBAAgB,6BAA6B,QAAQ,KAAK,QAAQ,aAAa;CACrF,MAAM,QAAQ,SAAiB;EAC7B,MAAM,QAAQ,cAAc;GAAE,UAAU;GAAM,eAAe,QAAQ;EAAc,CAAC;EACpF,IAAI,CAAC,MAAM,OACT,MAAM,IAAI,MAAM,wBAAwB,KAAK,oCAAoC,MAAM,QAAQ,KAAK,IAAI,GAAG;EAC7G,MAAM,eAAe,qBAAqB,MAAM,QAAQ,aAAa;EACrE,IAAI,CAAC,cAAc,cAAc,MAAM,IAAI,MAAM,gCAAgC,KAAK,EAAE;EACxF,MAAM,aAAa,sBAAsB,KAAK,MAAM,GAAG,aAAa,aAAa,aAAa,MAAM,MAAM,CAAC,CAAC;EAC5G,IAAI,CAAC,WAAW,WAAW,MAAM,IAAI,MAAM,WAAW,KAAK,4BAA4B;EACvF,MAAM,OAAO,KAAK,QAAQ,KAAK,QAAQ,aAAa,aAAa,IAAI,GAAG,WAAW,UAAU,IAAI;EACjG,MAAM,QAAQ,GAAG,aAAa,IAAI;EAClC,IAAI,OAAO,WAAW,QAAQ,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,OAAO,KAAK,MAAM,WAAW,UAAU,QACnF,MAAM,IAAI,MAAM,uCAAuC,KAAK,EAAE;EAChE,OAAO;GACL,UAAU,uBAAuB,KAAK,MAAM,MAAM,SAAS,MAAM,CAAC,CAAC;GACnE,QAAQ,qBAAqB,MAAM,QAAQ,aAAa;EAC1D;CACF;CACA,MAAM,SAAS,KAAK,UAAU;CAC9B,MAAM,YAAY,eAAe,gBAAgB,SAAS,KAAK,aAAa;CAC5E,MAAM,cAAc,QAAQ,aAAa,UAAU,OAAO;CAC1D,MAAM,aACJ,QACA,OACA,eAC0B;EAC1B;EACA;EACA,cAAc,cAAc,OAAO,QAAQ,SAAS;EACpD,UAAU,OAAO;CACnB;CAEA,IAAI,CAAC,OAAO,OAAO,UAAU,OAAO,WAAW,WAAW,GAAG,MAAM,IAAI,MAAM,uBAAuB,YAAY,EAAE;CAClH,MAAM,YAAY,mBAAmB;EACnC,UAAU,QAAQ,UAAU,OAAO,OAAO,gBAAgB;EAC1D,UAAU,WAAW,aAAa,UAAU,OAAO,gBAAgB;EACnE,UAAU,WAAW,WAAW,WAAW;CAC7C,CAAC;CACD,MAAM,QAAQ;EACZ,gBAAgB,sBAAsB,UAAU,OAAO;EACvD,iBAAiB,sBAAsB,UAAU,QAAQ;CAC3D;CACA,MAAM,WAAW;EACf,GAAG,UAAU;EACb,WAAW,OAAO,YAChB,OAAO,QAAQ,KAAK,CAAC,CAAC,KAAK,CAAC,MAAM,WAAW,CAC3C,MACA,EACE,QAAQ,OAAO,WAAW,QAAQ,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,OAAO,KAAK,EAChE,CACF,CAAC,CACH;CACF;CACA,GAAG,UAAU,QAAQ,iBAAiB,EAAE,WAAW,KAAK,CAAC;CAEzD,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ;EAAE,GAAG;EAAO,iBAAiB,sBAAsB,QAAQ;CAAE,CAAC,GAAG;EAC1G,MAAM,SAAS,KAAK,KAAK,QAAQ,iBAAiB,IAAI;EACtD,MAAM,YAAY,GAAG,OAAO,GAAG,OAAO,WAAW,EAAE;EACnD,IAAI;GACF,GAAG,cAAc,WAAW,OAAO,EAAE,MAAM,KAAK,CAAC;GACjD,GAAG,WAAW,WAAW,MAAM;EACjC,UAAU;GACR,GAAG,OAAO,WAAW,EAAE,OAAO,KAAK,CAAC;EACtC;CACF;CACA,OAAO,UAAU;AACnB"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../../src/builders/apiContractExport/index.ts"],"sourcesContent":["import crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nimport { globalDoomConfigDirectory } from '@agimon-ai/doompi-config/config';\nimport { loadMajorModesConfig, resolveLayers } from '@agimon-ai/doompi-config/majorModes';\nimport {\n canonicalContractJson,\n createApiDocuments,\n parseCompiledContracts,\n type ApiContractSelection,\n type ApiDocuments,\n} from '@agimon-ai/doompi-core/api-contracts';\nimport { parseDoomServerBundle } from '@agimon-ai/doompi-core/server-facet';\nimport { readSyncRegistration } from '@agimon-ai/doompi-core/sync-registration';\n\nimport { resolveDoomConfigurationRoot } from '../../composition/repository';\nimport { readSyncDrift } from '../../composition/syncDrift';\n\n/** Read only published generations. Export must never implicitly sync or activate a facet. */\nexport function exportApiContracts(options: {\n cwd: string;\n homeDirectory: string;\n outputDirectory: string;\n majorMode?: string;\n}): ApiDocuments['manifest'] {\n const globalRoot = globalDoomConfigDirectory(options.homeDirectory);\n const workspaceRoot = resolveDoomConfigurationRoot(options.cwd, options.homeDirectory);\n const read = (root: string) => {\n const drift = readSyncDrift({\n repoRoot: root,\n homeDirectory: options.homeDirectory,\n requireFreshSources: false,\n });\n if (!drift.fresh)\n throw new Error(`Run doompi sync for '${root}' before exporting API contracts: ${drift.reasons.join(', ')}`);\n const registration = readSyncRegistration(root, options.homeDirectory);\n if (!registration?.serverBundle) throw new Error(`No synced server bundle for '${root}'`);\n const descriptor = parseDoomServerBundle(JSON.parse(fs.readFileSync(registration.serverBundle.path, 'utf8')));\n if (!descriptor.contracts) throw new Error(`Resync '${root}' to generate API contracts`);\n const file = path.resolve(path.dirname(registration.serverBundle.path), descriptor.contracts.file);\n const bytes = fs.readFileSync(file);\n if (crypto.createHash('sha256').update(bytes).digest('hex') !== descriptor.contracts.sha256)\n throw new Error(`API contract checksum mismatch for '${root}'`);\n return {\n compiled: parseCompiledContracts(JSON.parse(bytes.toString('utf8'))),\n config: loadMajorModesConfig(root, options.homeDirectory),\n };\n };\n const global = read(globalRoot);\n const workspace = globalRoot === workspaceRoot ? global : read(workspaceRoot);\n const sessionMode = options.majorMode ?? workspace.config.defaultMajorMode;\n const selection = (\n source: typeof global,\n scope: ApiContractSelection['scope'],\n majorMode: string,\n ): ApiContractSelection => ({\n scope,\n majorMode,\n activeLayers: resolveLayers(source.config, majorMode),\n compiled: source.compiled,\n });\n // A typo must not silently export an empty session surface.\n if (!Object.hasOwn(workspace.config.majorMode, sessionMode)) throw new Error(`Unknown major mode '${sessionMode}'`);\n const documents = createApiDocuments([\n selection(global, 'global', global.config.defaultMajorMode),\n selection(workspace, 'workspace', workspace.config.defaultMajorMode),\n selection(workspace, 'session', sessionMode),\n ]);\n const files = {\n 'openapi.json': canonicalContractJson(documents.openapi),\n 'asyncapi.json': canonicalContractJson(documents.asyncapi),\n };\n const manifest = {\n ...documents.manifest,\n artifacts: Object.fromEntries(\n Object.entries(files).map(([name, bytes]) => [\n name,\n {\n sha256: crypto.createHash('sha256').update(bytes).digest('hex'),\n },\n ]),\n ),\n };\n fs.mkdirSync(options.outputDirectory, { recursive: true });\n // Write the manifest last. Consumers verify both checksums before accepting the bundle.\n for (const [name, bytes] of Object.entries({ ...files, 'manifest.json': canonicalContractJson(manifest) })) {\n const target = path.join(options.outputDirectory, name);\n const temporary = `${target}.${crypto.randomUUID()}.tmp`;\n try {\n fs.writeFileSync(temporary, bytes, { flag: 'wx' });\n fs.renameSync(temporary, target);\n } finally {\n fs.rmSync(temporary, { force: true });\n }\n }\n return documents.manifest;\n}\n"],"mappings":";;;;;;;;;;;;AAoBA,SAAgB,mBAAmB,SAKN;CAC3B,MAAM,aAAa,0BAA0B,QAAQ,aAAa;CAClE,MAAM,gBAAgB,6BAA6B,QAAQ,KAAK,QAAQ,aAAa;CACrF,MAAM,QAAQ,SAAiB;EAC7B,MAAM,QAAQ,cAAc;GAC1B,UAAU;GACV,eAAe,QAAQ;GACvB,qBAAqB;EACvB,CAAC;EACD,IAAI,CAAC,MAAM,OACT,MAAM,IAAI,MAAM,wBAAwB,KAAK,oCAAoC,MAAM,QAAQ,KAAK,IAAI,GAAG;EAC7G,MAAM,eAAe,qBAAqB,MAAM,QAAQ,aAAa;EACrE,IAAI,CAAC,cAAc,cAAc,MAAM,IAAI,MAAM,gCAAgC,KAAK,EAAE;EACxF,MAAM,aAAa,sBAAsB,KAAK,MAAM,GAAG,aAAa,aAAa,aAAa,MAAM,MAAM,CAAC,CAAC;EAC5G,IAAI,CAAC,WAAW,WAAW,MAAM,IAAI,MAAM,WAAW,KAAK,4BAA4B;EACvF,MAAM,OAAO,KAAK,QAAQ,KAAK,QAAQ,aAAa,aAAa,IAAI,GAAG,WAAW,UAAU,IAAI;EACjG,MAAM,QAAQ,GAAG,aAAa,IAAI;EAClC,IAAI,OAAO,WAAW,QAAQ,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,OAAO,KAAK,MAAM,WAAW,UAAU,QACnF,MAAM,IAAI,MAAM,uCAAuC,KAAK,EAAE;EAChE,OAAO;GACL,UAAU,uBAAuB,KAAK,MAAM,MAAM,SAAS,MAAM,CAAC,CAAC;GACnE,QAAQ,qBAAqB,MAAM,QAAQ,aAAa;EAC1D;CACF;CACA,MAAM,SAAS,KAAK,UAAU;CAC9B,MAAM,YAAY,eAAe,gBAAgB,SAAS,KAAK,aAAa;CAC5E,MAAM,cAAc,QAAQ,aAAa,UAAU,OAAO;CAC1D,MAAM,aACJ,QACA,OACA,eAC0B;EAC1B;EACA;EACA,cAAc,cAAc,OAAO,QAAQ,SAAS;EACpD,UAAU,OAAO;CACnB;CAEA,IAAI,CAAC,OAAO,OAAO,UAAU,OAAO,WAAW,WAAW,GAAG,MAAM,IAAI,MAAM,uBAAuB,YAAY,EAAE;CAClH,MAAM,YAAY,mBAAmB;EACnC,UAAU,QAAQ,UAAU,OAAO,OAAO,gBAAgB;EAC1D,UAAU,WAAW,aAAa,UAAU,OAAO,gBAAgB;EACnE,UAAU,WAAW,WAAW,WAAW;CAC7C,CAAC;CACD,MAAM,QAAQ;EACZ,gBAAgB,sBAAsB,UAAU,OAAO;EACvD,iBAAiB,sBAAsB,UAAU,QAAQ;CAC3D;CACA,MAAM,WAAW;EACf,GAAG,UAAU;EACb,WAAW,OAAO,YAChB,OAAO,QAAQ,KAAK,CAAC,CAAC,KAAK,CAAC,MAAM,WAAW,CAC3C,MACA,EACE,QAAQ,OAAO,WAAW,QAAQ,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,OAAO,KAAK,EAChE,CACF,CAAC,CACH;CACF;CACA,GAAG,UAAU,QAAQ,iBAAiB,EAAE,WAAW,KAAK,CAAC;CAEzD,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ;EAAE,GAAG;EAAO,iBAAiB,sBAAsB,QAAQ;CAAE,CAAC,GAAG;EAC1G,MAAM,SAAS,KAAK,KAAK,QAAQ,iBAAiB,IAAI;EACtD,MAAM,YAAY,GAAG,OAAO,GAAG,OAAO,WAAW,EAAE;EACnD,IAAI;GACF,GAAG,cAAc,WAAW,OAAO,EAAE,MAAM,KAAK,CAAC;GACjD,GAAG,WAAW,WAAW,MAAM;EACjC,UAAU;GACR,GAAG,OAAO,WAAW,EAAE,OAAO,KAAK,CAAC;EACtC;CACF;CACA,OAAO,UAAU;AACnB"}
@@ -10,9 +10,12 @@ node_path = require_runtime.__toESM(node_path, 1);
10
10
  let _agimon_ai_doompi_core_sync_registration = require("@agimon-ai/doompi-core/sync-registration");
11
11
  let node_os = require("node:os");
12
12
  node_os = require_runtime.__toESM(node_os, 1);
13
+ let node_crypto = require("node:crypto");
14
+ node_crypto = require_runtime.__toESM(node_crypto, 1);
13
15
  let _agimon_ai_doompi_core_sync_state_contract = require("@agimon-ai/doompi-core/sync-state-contract");
14
16
  //#region src/builders/cli/bootstrapLocator/index.ts
15
17
  const BOOTSTRAP_ENTRY_ENV = "DOOMPI_BOOTSTRAP_ENTRY";
18
+ const SHA256 = /^[a-f0-9]{64}$/u;
16
19
  function isRecord(value) {
17
20
  return typeof value === "object" && value !== null && !Array.isArray(value);
18
21
  }
@@ -34,6 +37,11 @@ function isInside(directory, target) {
34
37
  const relative = node_path.default.relative(canonicalPath(directory), canonicalPath(target));
35
38
  return relative === "" || !relative.startsWith("..") && !node_path.default.isAbsolute(relative);
36
39
  }
40
+ function parseFingerprints(value) {
41
+ if (!Array.isArray(value)) return void 0;
42
+ const fingerprints = value.map(require_inputs.parseInputFingerprint);
43
+ return fingerprints.some((fingerprint) => fingerprint === void 0) ? void 0 : fingerprints;
44
+ }
37
45
  function packagedDoomEntry() {
38
46
  return process.env[BOOTSTRAP_ENTRY_ENV] || require_entryResolution.ownEntry("composedPi");
39
47
  }
@@ -79,13 +87,10 @@ function readCompilerManifest(manifestPath, generatedDirectory) {
79
87
  if (!isInside(generatedDirectory, manifestPath)) return void 0;
80
88
  try {
81
89
  const parsed = JSON.parse(node_fs.default.readFileSync(manifestPath, "utf8"));
82
- if (!isRecord(parsed) || typeof parsed.output !== "string" || !Array.isArray(parsed.artifacts) || parsed.artifacts.some((artifact) => typeof artifact !== "string") || !Array.isArray(parsed.entries) || parsed.entries.some((entry) => typeof entry !== "string") || !Array.isArray(parsed.inputs)) return;
83
- const inputs = [];
84
- for (const input of parsed.inputs) {
85
- const fingerprint = require_inputs.parseInputFingerprint(input);
86
- if (fingerprint === void 0) return void 0;
87
- inputs.push(fingerprint);
88
- }
90
+ if (!isRecord(parsed) || typeof parsed.output !== "string" || !Array.isArray(parsed.artifacts) || parsed.artifacts.some((artifact) => typeof artifact !== "string") || !Array.isArray(parsed.entries) || parsed.entries.some((entry) => typeof entry !== "string") || !Array.isArray(parsed.inputs) || parsed.artifactInputs !== void 0 && !Array.isArray(parsed.artifactInputs)) return;
91
+ const inputs = parseFingerprints(parsed.inputs);
92
+ const artifactInputs = parsed.artifactInputs === void 0 ? void 0 : parseFingerprints(parsed.artifactInputs);
93
+ if (!inputs || parsed.artifactInputs !== void 0 && !artifactInputs) return void 0;
89
94
  const artifacts = parsed.artifacts;
90
95
  const entries = parsed.entries;
91
96
  if (!isInside(generatedDirectory, parsed.output) || artifacts.some((artifact) => !isInside(generatedDirectory, artifact))) return;
@@ -93,23 +98,51 @@ function readCompilerManifest(manifestPath, generatedDirectory) {
93
98
  output: parsed.output,
94
99
  artifacts,
95
100
  entries,
96
- inputs
101
+ inputs,
102
+ ...artifactInputs ? { artifactInputs } : {}
97
103
  };
98
104
  } catch {
99
105
  return;
100
106
  }
101
107
  }
102
- function compilerManifestIsFresh(manifest) {
103
- if (!node_fs.default.existsSync(manifest.output) || manifest.artifacts.some((artifact) => !node_fs.default.existsSync(artifact))) return false;
104
- return require_inputs.inputsAreFresh(manifest.inputs);
108
+ function artifactInputsAreIntact(manifest, generatedDirectory) {
109
+ try {
110
+ if (!manifest.artifactInputs || manifest.artifactInputs.length === 0) return false;
111
+ const expected = new Set([manifest.output, ...manifest.artifacts].map(canonicalPath));
112
+ const seen = /* @__PURE__ */ new Set();
113
+ for (const input of manifest.artifactInputs) {
114
+ if (!SHA256.test(input.sha256) || !isInside(generatedDirectory, input.path)) return false;
115
+ const target = canonicalPath(input.path);
116
+ if (!expected.has(target)) return false;
117
+ const stat = node_fs.default.statSync(target);
118
+ if (!stat.isFile() || stat.size !== input.size) return false;
119
+ if (node_crypto.default.createHash("sha256").update(node_fs.default.readFileSync(target)).digest("hex") !== input.sha256) return false;
120
+ seen.add(target);
121
+ }
122
+ return seen.size === expected.size;
123
+ } catch {
124
+ return false;
125
+ }
126
+ }
127
+ function compilerManifestIsUsable(manifest, generatedDirectory) {
128
+ return artifactInputsAreIntact(manifest, generatedDirectory);
105
129
  }
106
- function freshBootstrapRecord(state, expectedBootstrapEntry) {
130
+ function compilerManifestIsFresh(manifest, generatedDirectory) {
131
+ return compilerManifestIsUsable(manifest, generatedDirectory) && require_inputs.inputsAreFresh(manifest.inputs);
132
+ }
133
+ function bootstrapRecord(state, expectedBootstrapEntry, requireFreshInputs) {
107
134
  if (!state.bootstrap || !state.precompile) return void 0;
108
135
  if (state.precompile.version !== _agimon_ai_doompi_core_sync_state_contract.PRECOMPILE_STATE_VERSION || canonicalPath(state.precompile.bootstrapEntry) !== canonicalPath(expectedBootstrapEntry)) return;
109
136
  const record = readCompilerManifest(state.precompile.bootstrapManifest, state.generatedDirectory);
110
- if (!record || !compilerManifestIsFresh(record) || record.output !== state.bootstrap || record.entries.length !== 1 || canonicalPath(record.entries[0] ?? "") !== canonicalPath(expectedBootstrapEntry)) return;
137
+ if (!record || !compilerManifestIsUsable(record, state.generatedDirectory) || requireFreshInputs && !compilerManifestIsFresh(record, state.generatedDirectory) || record.output !== state.bootstrap || record.entries.length !== 1 || canonicalPath(record.entries[0] ?? "") !== canonicalPath(expectedBootstrapEntry)) return;
111
138
  return record;
112
139
  }
140
+ function usableBootstrapRecord(state, expectedBootstrapEntry) {
141
+ return bootstrapRecord(state, expectedBootstrapEntry, false);
142
+ }
143
+ function freshBootstrapRecord(state, expectedBootstrapEntry) {
144
+ return bootstrapRecord(state, expectedBootstrapEntry, true);
145
+ }
113
146
  /** Validates only the bootstrap graph needed before the generated bootstrap is imported. */
114
147
  function readStartupBootstrapStatus(repoRoot, expectedBootstrapEntry, homeDirectory = node_os.default.homedir()) {
115
148
  const state = readBootstrapState(repoRoot, homeDirectory);
@@ -120,7 +153,7 @@ function readStartupBootstrapStatus(repoRoot, expectedBootstrapEntry, homeDirect
120
153
  };
121
154
  return {
122
155
  bootstrap: state.bootstrap,
123
- fresh: freshBootstrapRecord(state, expected) !== void 0
156
+ fresh: usableBootstrapRecord(state, expected) !== void 0
124
157
  };
125
158
  }
126
159
  /** Validates one selected composition without touching any inactive bundle inputs. */
@@ -135,7 +168,7 @@ function readBundleStatus(repoRoot, compositionFingerprint, homeDirectory = node
135
168
  const manifest = readCompilerManifest(manifestPath, state.generatedDirectory);
136
169
  return {
137
170
  bundle,
138
- fresh: Boolean(manifest && manifest.output === bundle && compilerManifestIsFresh(manifest))
171
+ fresh: Boolean(manifest && manifest.output === bundle && compilerManifestIsUsable(manifest, state.generatedDirectory))
139
172
  };
140
173
  }
141
174
  /** Validates the bootstrap and every bundle for `doompi sync --check` diagnostics. */
@@ -155,7 +188,7 @@ function readBootstrapStatus(repoRoot, expectedBootstrapEntry, homeDirectory = n
155
188
  const manifestPath = state.precompile?.bundleManifests[fingerprint];
156
189
  if (!manifestPath) return false;
157
190
  const manifest = readCompilerManifest(manifestPath, state.generatedDirectory);
158
- return Boolean(manifest && manifest.output === bundle && compilerManifestIsFresh(manifest));
191
+ return Boolean(manifest && manifest.output === bundle && compilerManifestIsFresh(manifest, state.generatedDirectory));
159
192
  });
160
193
  return {
161
194
  bootstrap: state.bootstrap,
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["path","fs","ownEntry","readSyncRegistration","os","resolveDoomConfigurationRoot","readSyncState","BUNDLED_PRECOMPILE_STRATEGY","parseInputFingerprint","inputsAreFresh","PRECOMPILE_STATE_VERSION"],"sources":["../../../../src/builders/cli/bootstrapLocator/index.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport { readSyncRegistration } from '@agimon-ai/doompi-core/sync-registration';\nimport { BUNDLED_PRECOMPILE_STRATEGY, PRECOMPILE_STATE_VERSION } from '@agimon-ai/doompi-core/sync-state-contract';\n\nimport { inputsAreFresh, parseInputFingerprint, type InputFingerprint } from '../../../compiler/inputs';\nimport { resolveDoomConfigurationRoot } from '../../../composition/repository';\nimport { readSyncState } from '../../../composition/syncState';\nimport { ownEntry } from '../entryResolution';\n\nconst BOOTSTRAP_ENTRY_ENV = 'DOOMPI_BOOTSTRAP_ENTRY';\n\ninterface CompilerManifest {\n output: string;\n artifacts: string[];\n entries: string[];\n inputs: InputFingerprint[];\n}\n\ninterface BootstrapState {\n statePath: string;\n generatedDirectory: string;\n bootstrap?: string;\n bundles: Record<string, string>;\n precompile?: {\n version: number;\n strategy: 'bundle';\n bootstrapEntry: string;\n bootstrapManifest: string;\n bundleManifests: Record<string, string>;\n };\n}\n\nexport interface BootstrapStatus {\n bootstrap?: string;\n fresh: boolean;\n}\n\nexport interface BundleStatus {\n bundle?: string;\n fresh: boolean;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction stringRecord(value: unknown): Record<string, string> {\n if (!isRecord(value)) return {};\n return Object.fromEntries(\n Object.entries(value).filter((entry): entry is [string, string] => typeof entry[1] === 'string'),\n );\n}\n\nfunction canonicalPath(target: string): string {\n const absolute = path.resolve(target);\n try {\n return fs.realpathSync(absolute);\n } catch {\n const parent = path.dirname(absolute);\n if (parent === absolute) return absolute;\n return path.join(canonicalPath(parent), path.basename(absolute));\n }\n}\n\nfunction isInside(directory: string, target: string): boolean {\n const relative = path.relative(canonicalPath(directory), canonicalPath(target));\n return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));\n}\n\nfunction packagedDoomEntry(): string {\n return process.env[BOOTSTRAP_ENTRY_ENV] || ownEntry('composedPi');\n}\n\nfunction locationHasState(repoRoot: string, homeDirectory: string): boolean {\n return readSyncRegistration(repoRoot, homeDirectory) !== undefined;\n}\n\n/** Finds the nearest configured repository with generated Doom sync state. */\nexport function findSyncedRoot(cwd: string, homeDirectory: string = os.homedir()): string | undefined {\n if (locationHasState(cwd, homeDirectory)) return canonicalPath(cwd);\n const root = resolveDoomConfigurationRoot(cwd, homeDirectory);\n return locationHasState(root, homeDirectory) ? canonicalPath(root) : undefined;\n}\n\nfunction readBootstrapState(repoRoot: string, homeDirectory: string = os.homedir()): BootstrapState | undefined {\n const registration = readSyncRegistration(repoRoot, homeDirectory);\n if (!registration) return undefined;\n const statePath = registration.statePath;\n const generatedDirectory = registration.generationRoot;\n const state = readSyncState(repoRoot, homeDirectory);\n if (!state) return undefined;\n const bootstrap = typeof state.bootstrap === 'string' ? state.bootstrap : undefined;\n if (bootstrap && !isInside(generatedDirectory, bootstrap)) {\n throw new Error(`Doom bootstrap must stay inside ${generatedDirectory}: ${bootstrap}`);\n }\n\n let precompile: BootstrapState['precompile'];\n if (state.precompile !== undefined) {\n const value = state.precompile;\n if (\n !isRecord(value) ||\n typeof value.version !== 'number' ||\n value.strategy !== BUNDLED_PRECOMPILE_STRATEGY ||\n typeof value.bootstrapEntry !== 'string' ||\n typeof value.bootstrapManifest !== 'string' ||\n !isRecord(value.bundleManifests) ||\n Object.values(value.bundleManifests).some((manifest) => typeof manifest !== 'string')\n ) {\n throw new Error(`Doom sync state at ${statePath} has an invalid precompile record`);\n }\n precompile = {\n version: value.version,\n strategy: value.strategy,\n bootstrapEntry: value.bootstrapEntry,\n bootstrapManifest: value.bootstrapManifest,\n bundleManifests: value.bundleManifests as Record<string, string>,\n };\n }\n\n return {\n statePath,\n generatedDirectory,\n bootstrap,\n bundles: stringRecord(state.bundles),\n precompile,\n };\n}\n\nfunction readCompilerManifest(manifestPath: string, generatedDirectory: string): CompilerManifest | undefined {\n if (!isInside(generatedDirectory, manifestPath)) return undefined;\n try {\n const parsed = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) as unknown;\n if (\n !isRecord(parsed) ||\n typeof parsed.output !== 'string' ||\n !Array.isArray(parsed.artifacts) ||\n parsed.artifacts.some((artifact) => typeof artifact !== 'string') ||\n !Array.isArray(parsed.entries) ||\n parsed.entries.some((entry) => typeof entry !== 'string') ||\n !Array.isArray(parsed.inputs)\n ) {\n return undefined;\n }\n const inputs: InputFingerprint[] = [];\n for (const input of parsed.inputs) {\n // A manifest written before digests were recorded cannot be confirmed by\n // content, so it reads as absent and the next sync rewrites it.\n const fingerprint = parseInputFingerprint(input);\n if (fingerprint === undefined) return undefined;\n inputs.push(fingerprint);\n }\n const artifacts = parsed.artifacts as string[];\n const entries = parsed.entries as string[];\n if (\n !isInside(generatedDirectory, parsed.output) ||\n artifacts.some((artifact) => !isInside(generatedDirectory, artifact))\n ) {\n return undefined;\n }\n return { output: parsed.output, artifacts, entries, inputs };\n } catch {\n return undefined;\n }\n}\n\nfunction compilerManifestIsFresh(manifest: CompilerManifest): boolean {\n if (!fs.existsSync(manifest.output) || manifest.artifacts.some((artifact) => !fs.existsSync(artifact))) return false;\n return inputsAreFresh(manifest.inputs);\n}\n\nfunction freshBootstrapRecord(state: BootstrapState, expectedBootstrapEntry: string): CompilerManifest | undefined {\n if (!state.bootstrap || !state.precompile) return undefined;\n if (\n state.precompile.version !== PRECOMPILE_STATE_VERSION ||\n canonicalPath(state.precompile.bootstrapEntry) !== canonicalPath(expectedBootstrapEntry)\n ) {\n return undefined;\n }\n const record = readCompilerManifest(state.precompile.bootstrapManifest, state.generatedDirectory);\n if (\n !record ||\n !compilerManifestIsFresh(record) ||\n record.output !== state.bootstrap ||\n record.entries.length !== 1 ||\n canonicalPath(record.entries[0] ?? '') !== canonicalPath(expectedBootstrapEntry)\n ) {\n return undefined;\n }\n return record;\n}\n\n/** Validates only the bootstrap graph needed before the generated bootstrap is imported. */\nexport function readStartupBootstrapStatus(\n repoRoot: string,\n expectedBootstrapEntry?: string,\n homeDirectory: string = os.homedir(),\n): BootstrapStatus {\n const state = readBootstrapState(repoRoot, homeDirectory);\n const expected = expectedBootstrapEntry ?? packagedDoomEntry();\n if (!state) return { bootstrap: undefined, fresh: false };\n return {\n bootstrap: state.bootstrap,\n fresh: freshBootstrapRecord(state, expected) !== undefined,\n };\n}\n\n/** Validates one selected composition without touching any inactive bundle inputs. */\nexport function readBundleStatus(\n repoRoot: string,\n compositionFingerprint: string,\n homeDirectory: string = os.homedir(),\n): BundleStatus {\n const state = readBootstrapState(repoRoot, homeDirectory);\n const bundle = state?.bundles[compositionFingerprint];\n const manifestPath = state?.precompile?.bundleManifests[compositionFingerprint];\n if (!state || state.precompile?.version !== PRECOMPILE_STATE_VERSION || !bundle || !manifestPath) {\n return { bundle, fresh: false };\n }\n const manifest = readCompilerManifest(manifestPath, state.generatedDirectory);\n return {\n bundle,\n fresh: Boolean(manifest && manifest.output === bundle && compilerManifestIsFresh(manifest)),\n };\n}\n\n/** Validates the bootstrap and every bundle for `doompi sync --check` diagnostics. */\nexport function readBootstrapStatus(\n repoRoot: string,\n expectedBootstrapEntry?: string,\n homeDirectory: string = os.homedir(),\n): BootstrapStatus {\n const state = readBootstrapState(repoRoot, homeDirectory);\n const expected = expectedBootstrapEntry ?? packagedDoomEntry();\n if (!state?.bootstrap || !state.precompile || !freshBootstrapRecord(state, expected)) {\n return { bootstrap: state?.bootstrap, fresh: false };\n }\n const manifests = Object.values(state.precompile.bundleManifests);\n if (new Set(manifests).size !== manifests.length) return { bootstrap: state.bootstrap, fresh: false };\n const fresh = Object.entries(state.bundles).every(([fingerprint, bundle]) => {\n const manifestPath = state.precompile?.bundleManifests[fingerprint];\n if (!manifestPath) return false;\n const manifest = readCompilerManifest(manifestPath, state.generatedDirectory);\n return Boolean(manifest && manifest.output === bundle && compilerManifestIsFresh(manifest));\n });\n return { bootstrap: state.bootstrap, fresh };\n}\n\n/** Reads only the generated bootstrap pointer while retaining state validation. */\nexport function readBootstrapPointer(repoRoot: string, homeDirectory: string = os.homedir()): string | undefined {\n return readBootstrapState(repoRoot, homeDirectory)?.bootstrap;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["path","fs","parseInputFingerprint","ownEntry","readSyncRegistration","os","resolveDoomConfigurationRoot","readSyncState","BUNDLED_PRECOMPILE_STRATEGY","crypto","inputsAreFresh","PRECOMPILE_STATE_VERSION"],"sources":["../../../../src/builders/cli/bootstrapLocator/index.ts"],"sourcesContent":["import crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport { readSyncRegistration } from '@agimon-ai/doompi-core/sync-registration';\nimport { BUNDLED_PRECOMPILE_STRATEGY, PRECOMPILE_STATE_VERSION } from '@agimon-ai/doompi-core/sync-state-contract';\n\nimport { inputsAreFresh, parseInputFingerprint, type InputFingerprint } from '../../../compiler/inputs';\nimport { resolveDoomConfigurationRoot } from '../../../composition/repository';\nimport { readSyncState } from '../../../composition/syncState';\nimport { ownEntry } from '../entryResolution';\n\nconst BOOTSTRAP_ENTRY_ENV = 'DOOMPI_BOOTSTRAP_ENTRY';\nconst SHA256 = /^[a-f0-9]{64}$/u;\n\ninterface CompilerManifest {\n output: string;\n artifacts: string[];\n entries: string[];\n inputs: InputFingerprint[];\n artifactInputs?: InputFingerprint[];\n}\n\ninterface BootstrapState {\n statePath: string;\n generatedDirectory: string;\n bootstrap?: string;\n bundles: Record<string, string>;\n precompile?: {\n version: number;\n strategy: 'bundle';\n bootstrapEntry: string;\n bootstrapManifest: string;\n bundleManifests: Record<string, string>;\n };\n}\n\nexport interface BootstrapStatus {\n bootstrap?: string;\n fresh: boolean;\n}\n\nexport interface BundleStatus {\n bundle?: string;\n fresh: boolean;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction stringRecord(value: unknown): Record<string, string> {\n if (!isRecord(value)) return {};\n return Object.fromEntries(\n Object.entries(value).filter((entry): entry is [string, string] => typeof entry[1] === 'string'),\n );\n}\n\nfunction canonicalPath(target: string): string {\n const absolute = path.resolve(target);\n try {\n return fs.realpathSync(absolute);\n } catch {\n const parent = path.dirname(absolute);\n if (parent === absolute) return absolute;\n return path.join(canonicalPath(parent), path.basename(absolute));\n }\n}\n\nfunction isInside(directory: string, target: string): boolean {\n const relative = path.relative(canonicalPath(directory), canonicalPath(target));\n return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));\n}\n\nfunction parseFingerprints(value: unknown): InputFingerprint[] | undefined {\n if (!Array.isArray(value)) return undefined;\n const fingerprints = value.map(parseInputFingerprint);\n return fingerprints.some((fingerprint) => fingerprint === undefined)\n ? undefined\n : (fingerprints as InputFingerprint[]);\n}\n\nfunction packagedDoomEntry(): string {\n return process.env[BOOTSTRAP_ENTRY_ENV] || ownEntry('composedPi');\n}\n\nfunction locationHasState(repoRoot: string, homeDirectory: string): boolean {\n return readSyncRegistration(repoRoot, homeDirectory) !== undefined;\n}\n\n/** Finds the nearest configured repository with generated Doom sync state. */\nexport function findSyncedRoot(cwd: string, homeDirectory: string = os.homedir()): string | undefined {\n if (locationHasState(cwd, homeDirectory)) return canonicalPath(cwd);\n const root = resolveDoomConfigurationRoot(cwd, homeDirectory);\n return locationHasState(root, homeDirectory) ? canonicalPath(root) : undefined;\n}\n\nfunction readBootstrapState(repoRoot: string, homeDirectory: string = os.homedir()): BootstrapState | undefined {\n const registration = readSyncRegistration(repoRoot, homeDirectory);\n if (!registration) return undefined;\n const statePath = registration.statePath;\n const generatedDirectory = registration.generationRoot;\n const state = readSyncState(repoRoot, homeDirectory);\n if (!state) return undefined;\n const bootstrap = typeof state.bootstrap === 'string' ? state.bootstrap : undefined;\n if (bootstrap && !isInside(generatedDirectory, bootstrap)) {\n throw new Error(`Doom bootstrap must stay inside ${generatedDirectory}: ${bootstrap}`);\n }\n\n let precompile: BootstrapState['precompile'];\n if (state.precompile !== undefined) {\n const value = state.precompile;\n if (\n !isRecord(value) ||\n typeof value.version !== 'number' ||\n value.strategy !== BUNDLED_PRECOMPILE_STRATEGY ||\n typeof value.bootstrapEntry !== 'string' ||\n typeof value.bootstrapManifest !== 'string' ||\n !isRecord(value.bundleManifests) ||\n Object.values(value.bundleManifests).some((manifest) => typeof manifest !== 'string')\n ) {\n throw new Error(`Doom sync state at ${statePath} has an invalid precompile record`);\n }\n precompile = {\n version: value.version,\n strategy: value.strategy,\n bootstrapEntry: value.bootstrapEntry,\n bootstrapManifest: value.bootstrapManifest,\n bundleManifests: value.bundleManifests as Record<string, string>,\n };\n }\n\n return {\n statePath,\n generatedDirectory,\n bootstrap,\n bundles: stringRecord(state.bundles),\n precompile,\n };\n}\n\nfunction readCompilerManifest(manifestPath: string, generatedDirectory: string): CompilerManifest | undefined {\n if (!isInside(generatedDirectory, manifestPath)) return undefined;\n try {\n const parsed = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) as unknown;\n if (\n !isRecord(parsed) ||\n typeof parsed.output !== 'string' ||\n !Array.isArray(parsed.artifacts) ||\n parsed.artifacts.some((artifact) => typeof artifact !== 'string') ||\n !Array.isArray(parsed.entries) ||\n parsed.entries.some((entry) => typeof entry !== 'string') ||\n !Array.isArray(parsed.inputs) ||\n (parsed.artifactInputs !== undefined && !Array.isArray(parsed.artifactInputs))\n ) {\n return undefined;\n }\n const inputs = parseFingerprints(parsed.inputs);\n const artifactInputs = parsed.artifactInputs === undefined ? undefined : parseFingerprints(parsed.artifactInputs);\n if (!inputs || (parsed.artifactInputs !== undefined && !artifactInputs)) return undefined;\n const artifacts = parsed.artifacts as string[];\n const entries = parsed.entries as string[];\n if (\n !isInside(generatedDirectory, parsed.output) ||\n artifacts.some((artifact) => !isInside(generatedDirectory, artifact))\n ) {\n return undefined;\n }\n return { output: parsed.output, artifacts, entries, inputs, ...(artifactInputs ? { artifactInputs } : {}) };\n } catch {\n return undefined;\n }\n}\n\nfunction artifactInputsAreIntact(manifest: CompilerManifest, generatedDirectory: string): boolean {\n try {\n if (!manifest.artifactInputs || manifest.artifactInputs.length === 0) return false;\n const expected = new Set([manifest.output, ...manifest.artifacts].map(canonicalPath));\n const seen = new Set<string>();\n for (const input of manifest.artifactInputs) {\n if (!SHA256.test(input.sha256) || !isInside(generatedDirectory, input.path)) return false;\n const target = canonicalPath(input.path);\n if (!expected.has(target)) return false;\n const stat = fs.statSync(target);\n if (!stat.isFile() || stat.size !== input.size) return false;\n if (crypto.createHash('sha256').update(fs.readFileSync(target)).digest('hex') !== input.sha256) return false;\n seen.add(target);\n }\n return seen.size === expected.size;\n } catch {\n return false;\n }\n}\n\nfunction compilerManifestIsUsable(manifest: CompilerManifest, generatedDirectory: string): boolean {\n return artifactInputsAreIntact(manifest, generatedDirectory);\n}\n\nfunction compilerManifestIsFresh(manifest: CompilerManifest, generatedDirectory: string): boolean {\n return compilerManifestIsUsable(manifest, generatedDirectory) && inputsAreFresh(manifest.inputs);\n}\n\nfunction bootstrapRecord(\n state: BootstrapState,\n expectedBootstrapEntry: string,\n requireFreshInputs: boolean,\n): CompilerManifest | undefined {\n if (!state.bootstrap || !state.precompile) return undefined;\n if (\n state.precompile.version !== PRECOMPILE_STATE_VERSION ||\n canonicalPath(state.precompile.bootstrapEntry) !== canonicalPath(expectedBootstrapEntry)\n ) {\n return undefined;\n }\n const record = readCompilerManifest(state.precompile.bootstrapManifest, state.generatedDirectory);\n if (\n !record ||\n !compilerManifestIsUsable(record, state.generatedDirectory) ||\n (requireFreshInputs && !compilerManifestIsFresh(record, state.generatedDirectory)) ||\n record.output !== state.bootstrap ||\n record.entries.length !== 1 ||\n canonicalPath(record.entries[0] ?? '') !== canonicalPath(expectedBootstrapEntry)\n ) {\n return undefined;\n }\n return record;\n}\n\nfunction usableBootstrapRecord(state: BootstrapState, expectedBootstrapEntry: string): CompilerManifest | undefined {\n return bootstrapRecord(state, expectedBootstrapEntry, false);\n}\n\nfunction freshBootstrapRecord(state: BootstrapState, expectedBootstrapEntry: string): CompilerManifest | undefined {\n return bootstrapRecord(state, expectedBootstrapEntry, true);\n}\n\n/** Validates only the bootstrap graph needed before the generated bootstrap is imported. */\nexport function readStartupBootstrapStatus(\n repoRoot: string,\n expectedBootstrapEntry?: string,\n homeDirectory: string = os.homedir(),\n): BootstrapStatus {\n const state = readBootstrapState(repoRoot, homeDirectory);\n const expected = expectedBootstrapEntry ?? packagedDoomEntry();\n if (!state) return { bootstrap: undefined, fresh: false };\n return {\n bootstrap: state.bootstrap,\n fresh: usableBootstrapRecord(state, expected) !== undefined,\n };\n}\n\n/** Validates one selected composition without touching any inactive bundle inputs. */\nexport function readBundleStatus(\n repoRoot: string,\n compositionFingerprint: string,\n homeDirectory: string = os.homedir(),\n): BundleStatus {\n const state = readBootstrapState(repoRoot, homeDirectory);\n const bundle = state?.bundles[compositionFingerprint];\n const manifestPath = state?.precompile?.bundleManifests[compositionFingerprint];\n if (!state || state.precompile?.version !== PRECOMPILE_STATE_VERSION || !bundle || !manifestPath) {\n return { bundle, fresh: false };\n }\n const manifest = readCompilerManifest(manifestPath, state.generatedDirectory);\n return {\n bundle,\n fresh: Boolean(\n manifest && manifest.output === bundle && compilerManifestIsUsable(manifest, state.generatedDirectory),\n ),\n };\n}\n\n/** Validates the bootstrap and every bundle for `doompi sync --check` diagnostics. */\nexport function readBootstrapStatus(\n repoRoot: string,\n expectedBootstrapEntry?: string,\n homeDirectory: string = os.homedir(),\n): BootstrapStatus {\n const state = readBootstrapState(repoRoot, homeDirectory);\n const expected = expectedBootstrapEntry ?? packagedDoomEntry();\n if (!state?.bootstrap || !state.precompile || !freshBootstrapRecord(state, expected)) {\n return { bootstrap: state?.bootstrap, fresh: false };\n }\n const manifests = Object.values(state.precompile.bundleManifests);\n if (new Set(manifests).size !== manifests.length) return { bootstrap: state.bootstrap, fresh: false };\n const fresh = Object.entries(state.bundles).every(([fingerprint, bundle]) => {\n const manifestPath = state.precompile?.bundleManifests[fingerprint];\n if (!manifestPath) return false;\n const manifest = readCompilerManifest(manifestPath, state.generatedDirectory);\n return Boolean(\n manifest && manifest.output === bundle && compilerManifestIsFresh(manifest, state.generatedDirectory),\n );\n });\n return { bootstrap: state.bootstrap, fresh };\n}\n\n/** Reads only the generated bootstrap pointer while retaining state validation. */\nexport function readBootstrapPointer(repoRoot: string, homeDirectory: string = os.homedir()): string | undefined {\n return readBootstrapState(repoRoot, homeDirectory)?.bootstrap;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../../../../src/builders/cli/bootstrapLocator/index.ts"],"mappings":";;wBAiFgB,eAAe,aAAa"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../../../../src/builders/cli/bootstrapLocator/index.ts"],"mappings":";;wBA4FgB,eAAe,aAAa"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../../../src/builders/cli/bootstrapLocator/index.ts"],"mappings":";;wBAiFgB,eAAe,aAAa"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../../../src/builders/cli/bootstrapLocator/index.ts"],"mappings":";;wBA4FgB,eAAe,aAAa"}
@@ -6,9 +6,11 @@ import fs from "node:fs";
6
6
  import path from "node:path";
7
7
  import { readSyncRegistration } from "@agimon-ai/doompi-core/sync-registration";
8
8
  import os from "node:os";
9
+ import crypto from "node:crypto";
9
10
  import { BUNDLED_PRECOMPILE_STRATEGY, PRECOMPILE_STATE_VERSION } from "@agimon-ai/doompi-core/sync-state-contract";
10
11
  //#region src/builders/cli/bootstrapLocator/index.ts
11
12
  const BOOTSTRAP_ENTRY_ENV = "DOOMPI_BOOTSTRAP_ENTRY";
13
+ const SHA256 = /^[a-f0-9]{64}$/u;
12
14
  function isRecord(value) {
13
15
  return typeof value === "object" && value !== null && !Array.isArray(value);
14
16
  }
@@ -30,6 +32,11 @@ function isInside(directory, target) {
30
32
  const relative = path.relative(canonicalPath(directory), canonicalPath(target));
31
33
  return relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative);
32
34
  }
35
+ function parseFingerprints(value) {
36
+ if (!Array.isArray(value)) return void 0;
37
+ const fingerprints = value.map(parseInputFingerprint);
38
+ return fingerprints.some((fingerprint) => fingerprint === void 0) ? void 0 : fingerprints;
39
+ }
33
40
  function packagedDoomEntry() {
34
41
  return process.env[BOOTSTRAP_ENTRY_ENV] || ownEntry("composedPi");
35
42
  }
@@ -75,13 +82,10 @@ function readCompilerManifest(manifestPath, generatedDirectory) {
75
82
  if (!isInside(generatedDirectory, manifestPath)) return void 0;
76
83
  try {
77
84
  const parsed = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
78
- if (!isRecord(parsed) || typeof parsed.output !== "string" || !Array.isArray(parsed.artifacts) || parsed.artifacts.some((artifact) => typeof artifact !== "string") || !Array.isArray(parsed.entries) || parsed.entries.some((entry) => typeof entry !== "string") || !Array.isArray(parsed.inputs)) return;
79
- const inputs = [];
80
- for (const input of parsed.inputs) {
81
- const fingerprint = parseInputFingerprint(input);
82
- if (fingerprint === void 0) return void 0;
83
- inputs.push(fingerprint);
84
- }
85
+ if (!isRecord(parsed) || typeof parsed.output !== "string" || !Array.isArray(parsed.artifacts) || parsed.artifacts.some((artifact) => typeof artifact !== "string") || !Array.isArray(parsed.entries) || parsed.entries.some((entry) => typeof entry !== "string") || !Array.isArray(parsed.inputs) || parsed.artifactInputs !== void 0 && !Array.isArray(parsed.artifactInputs)) return;
86
+ const inputs = parseFingerprints(parsed.inputs);
87
+ const artifactInputs = parsed.artifactInputs === void 0 ? void 0 : parseFingerprints(parsed.artifactInputs);
88
+ if (!inputs || parsed.artifactInputs !== void 0 && !artifactInputs) return void 0;
85
89
  const artifacts = parsed.artifacts;
86
90
  const entries = parsed.entries;
87
91
  if (!isInside(generatedDirectory, parsed.output) || artifacts.some((artifact) => !isInside(generatedDirectory, artifact))) return;
@@ -89,23 +93,51 @@ function readCompilerManifest(manifestPath, generatedDirectory) {
89
93
  output: parsed.output,
90
94
  artifacts,
91
95
  entries,
92
- inputs
96
+ inputs,
97
+ ...artifactInputs ? { artifactInputs } : {}
93
98
  };
94
99
  } catch {
95
100
  return;
96
101
  }
97
102
  }
98
- function compilerManifestIsFresh(manifest) {
99
- if (!fs.existsSync(manifest.output) || manifest.artifacts.some((artifact) => !fs.existsSync(artifact))) return false;
100
- return inputsAreFresh(manifest.inputs);
103
+ function artifactInputsAreIntact(manifest, generatedDirectory) {
104
+ try {
105
+ if (!manifest.artifactInputs || manifest.artifactInputs.length === 0) return false;
106
+ const expected = new Set([manifest.output, ...manifest.artifacts].map(canonicalPath));
107
+ const seen = /* @__PURE__ */ new Set();
108
+ for (const input of manifest.artifactInputs) {
109
+ if (!SHA256.test(input.sha256) || !isInside(generatedDirectory, input.path)) return false;
110
+ const target = canonicalPath(input.path);
111
+ if (!expected.has(target)) return false;
112
+ const stat = fs.statSync(target);
113
+ if (!stat.isFile() || stat.size !== input.size) return false;
114
+ if (crypto.createHash("sha256").update(fs.readFileSync(target)).digest("hex") !== input.sha256) return false;
115
+ seen.add(target);
116
+ }
117
+ return seen.size === expected.size;
118
+ } catch {
119
+ return false;
120
+ }
121
+ }
122
+ function compilerManifestIsUsable(manifest, generatedDirectory) {
123
+ return artifactInputsAreIntact(manifest, generatedDirectory);
101
124
  }
102
- function freshBootstrapRecord(state, expectedBootstrapEntry) {
125
+ function compilerManifestIsFresh(manifest, generatedDirectory) {
126
+ return compilerManifestIsUsable(manifest, generatedDirectory) && inputsAreFresh(manifest.inputs);
127
+ }
128
+ function bootstrapRecord(state, expectedBootstrapEntry, requireFreshInputs) {
103
129
  if (!state.bootstrap || !state.precompile) return void 0;
104
130
  if (state.precompile.version !== PRECOMPILE_STATE_VERSION || canonicalPath(state.precompile.bootstrapEntry) !== canonicalPath(expectedBootstrapEntry)) return;
105
131
  const record = readCompilerManifest(state.precompile.bootstrapManifest, state.generatedDirectory);
106
- if (!record || !compilerManifestIsFresh(record) || record.output !== state.bootstrap || record.entries.length !== 1 || canonicalPath(record.entries[0] ?? "") !== canonicalPath(expectedBootstrapEntry)) return;
132
+ if (!record || !compilerManifestIsUsable(record, state.generatedDirectory) || requireFreshInputs && !compilerManifestIsFresh(record, state.generatedDirectory) || record.output !== state.bootstrap || record.entries.length !== 1 || canonicalPath(record.entries[0] ?? "") !== canonicalPath(expectedBootstrapEntry)) return;
107
133
  return record;
108
134
  }
135
+ function usableBootstrapRecord(state, expectedBootstrapEntry) {
136
+ return bootstrapRecord(state, expectedBootstrapEntry, false);
137
+ }
138
+ function freshBootstrapRecord(state, expectedBootstrapEntry) {
139
+ return bootstrapRecord(state, expectedBootstrapEntry, true);
140
+ }
109
141
  /** Validates only the bootstrap graph needed before the generated bootstrap is imported. */
110
142
  function readStartupBootstrapStatus(repoRoot, expectedBootstrapEntry, homeDirectory = os.homedir()) {
111
143
  const state = readBootstrapState(repoRoot, homeDirectory);
@@ -116,7 +148,7 @@ function readStartupBootstrapStatus(repoRoot, expectedBootstrapEntry, homeDirect
116
148
  };
117
149
  return {
118
150
  bootstrap: state.bootstrap,
119
- fresh: freshBootstrapRecord(state, expected) !== void 0
151
+ fresh: usableBootstrapRecord(state, expected) !== void 0
120
152
  };
121
153
  }
122
154
  /** Validates one selected composition without touching any inactive bundle inputs. */
@@ -131,7 +163,7 @@ function readBundleStatus(repoRoot, compositionFingerprint, homeDirectory = os.h
131
163
  const manifest = readCompilerManifest(manifestPath, state.generatedDirectory);
132
164
  return {
133
165
  bundle,
134
- fresh: Boolean(manifest && manifest.output === bundle && compilerManifestIsFresh(manifest))
166
+ fresh: Boolean(manifest && manifest.output === bundle && compilerManifestIsUsable(manifest, state.generatedDirectory))
135
167
  };
136
168
  }
137
169
  /** Validates the bootstrap and every bundle for `doompi sync --check` diagnostics. */
@@ -151,7 +183,7 @@ function readBootstrapStatus(repoRoot, expectedBootstrapEntry, homeDirectory = o
151
183
  const manifestPath = state.precompile?.bundleManifests[fingerprint];
152
184
  if (!manifestPath) return false;
153
185
  const manifest = readCompilerManifest(manifestPath, state.generatedDirectory);
154
- return Boolean(manifest && manifest.output === bundle && compilerManifestIsFresh(manifest));
186
+ return Boolean(manifest && manifest.output === bundle && compilerManifestIsFresh(manifest, state.generatedDirectory));
155
187
  });
156
188
  return {
157
189
  bootstrap: state.bootstrap,
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/builders/cli/bootstrapLocator/index.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport { readSyncRegistration } from '@agimon-ai/doompi-core/sync-registration';\nimport { BUNDLED_PRECOMPILE_STRATEGY, PRECOMPILE_STATE_VERSION } from '@agimon-ai/doompi-core/sync-state-contract';\n\nimport { inputsAreFresh, parseInputFingerprint, type InputFingerprint } from '../../../compiler/inputs';\nimport { resolveDoomConfigurationRoot } from '../../../composition/repository';\nimport { readSyncState } from '../../../composition/syncState';\nimport { ownEntry } from '../entryResolution';\n\nconst BOOTSTRAP_ENTRY_ENV = 'DOOMPI_BOOTSTRAP_ENTRY';\n\ninterface CompilerManifest {\n output: string;\n artifacts: string[];\n entries: string[];\n inputs: InputFingerprint[];\n}\n\ninterface BootstrapState {\n statePath: string;\n generatedDirectory: string;\n bootstrap?: string;\n bundles: Record<string, string>;\n precompile?: {\n version: number;\n strategy: 'bundle';\n bootstrapEntry: string;\n bootstrapManifest: string;\n bundleManifests: Record<string, string>;\n };\n}\n\nexport interface BootstrapStatus {\n bootstrap?: string;\n fresh: boolean;\n}\n\nexport interface BundleStatus {\n bundle?: string;\n fresh: boolean;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction stringRecord(value: unknown): Record<string, string> {\n if (!isRecord(value)) return {};\n return Object.fromEntries(\n Object.entries(value).filter((entry): entry is [string, string] => typeof entry[1] === 'string'),\n );\n}\n\nfunction canonicalPath(target: string): string {\n const absolute = path.resolve(target);\n try {\n return fs.realpathSync(absolute);\n } catch {\n const parent = path.dirname(absolute);\n if (parent === absolute) return absolute;\n return path.join(canonicalPath(parent), path.basename(absolute));\n }\n}\n\nfunction isInside(directory: string, target: string): boolean {\n const relative = path.relative(canonicalPath(directory), canonicalPath(target));\n return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));\n}\n\nfunction packagedDoomEntry(): string {\n return process.env[BOOTSTRAP_ENTRY_ENV] || ownEntry('composedPi');\n}\n\nfunction locationHasState(repoRoot: string, homeDirectory: string): boolean {\n return readSyncRegistration(repoRoot, homeDirectory) !== undefined;\n}\n\n/** Finds the nearest configured repository with generated Doom sync state. */\nexport function findSyncedRoot(cwd: string, homeDirectory: string = os.homedir()): string | undefined {\n if (locationHasState(cwd, homeDirectory)) return canonicalPath(cwd);\n const root = resolveDoomConfigurationRoot(cwd, homeDirectory);\n return locationHasState(root, homeDirectory) ? canonicalPath(root) : undefined;\n}\n\nfunction readBootstrapState(repoRoot: string, homeDirectory: string = os.homedir()): BootstrapState | undefined {\n const registration = readSyncRegistration(repoRoot, homeDirectory);\n if (!registration) return undefined;\n const statePath = registration.statePath;\n const generatedDirectory = registration.generationRoot;\n const state = readSyncState(repoRoot, homeDirectory);\n if (!state) return undefined;\n const bootstrap = typeof state.bootstrap === 'string' ? state.bootstrap : undefined;\n if (bootstrap && !isInside(generatedDirectory, bootstrap)) {\n throw new Error(`Doom bootstrap must stay inside ${generatedDirectory}: ${bootstrap}`);\n }\n\n let precompile: BootstrapState['precompile'];\n if (state.precompile !== undefined) {\n const value = state.precompile;\n if (\n !isRecord(value) ||\n typeof value.version !== 'number' ||\n value.strategy !== BUNDLED_PRECOMPILE_STRATEGY ||\n typeof value.bootstrapEntry !== 'string' ||\n typeof value.bootstrapManifest !== 'string' ||\n !isRecord(value.bundleManifests) ||\n Object.values(value.bundleManifests).some((manifest) => typeof manifest !== 'string')\n ) {\n throw new Error(`Doom sync state at ${statePath} has an invalid precompile record`);\n }\n precompile = {\n version: value.version,\n strategy: value.strategy,\n bootstrapEntry: value.bootstrapEntry,\n bootstrapManifest: value.bootstrapManifest,\n bundleManifests: value.bundleManifests as Record<string, string>,\n };\n }\n\n return {\n statePath,\n generatedDirectory,\n bootstrap,\n bundles: stringRecord(state.bundles),\n precompile,\n };\n}\n\nfunction readCompilerManifest(manifestPath: string, generatedDirectory: string): CompilerManifest | undefined {\n if (!isInside(generatedDirectory, manifestPath)) return undefined;\n try {\n const parsed = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) as unknown;\n if (\n !isRecord(parsed) ||\n typeof parsed.output !== 'string' ||\n !Array.isArray(parsed.artifacts) ||\n parsed.artifacts.some((artifact) => typeof artifact !== 'string') ||\n !Array.isArray(parsed.entries) ||\n parsed.entries.some((entry) => typeof entry !== 'string') ||\n !Array.isArray(parsed.inputs)\n ) {\n return undefined;\n }\n const inputs: InputFingerprint[] = [];\n for (const input of parsed.inputs) {\n // A manifest written before digests were recorded cannot be confirmed by\n // content, so it reads as absent and the next sync rewrites it.\n const fingerprint = parseInputFingerprint(input);\n if (fingerprint === undefined) return undefined;\n inputs.push(fingerprint);\n }\n const artifacts = parsed.artifacts as string[];\n const entries = parsed.entries as string[];\n if (\n !isInside(generatedDirectory, parsed.output) ||\n artifacts.some((artifact) => !isInside(generatedDirectory, artifact))\n ) {\n return undefined;\n }\n return { output: parsed.output, artifacts, entries, inputs };\n } catch {\n return undefined;\n }\n}\n\nfunction compilerManifestIsFresh(manifest: CompilerManifest): boolean {\n if (!fs.existsSync(manifest.output) || manifest.artifacts.some((artifact) => !fs.existsSync(artifact))) return false;\n return inputsAreFresh(manifest.inputs);\n}\n\nfunction freshBootstrapRecord(state: BootstrapState, expectedBootstrapEntry: string): CompilerManifest | undefined {\n if (!state.bootstrap || !state.precompile) return undefined;\n if (\n state.precompile.version !== PRECOMPILE_STATE_VERSION ||\n canonicalPath(state.precompile.bootstrapEntry) !== canonicalPath(expectedBootstrapEntry)\n ) {\n return undefined;\n }\n const record = readCompilerManifest(state.precompile.bootstrapManifest, state.generatedDirectory);\n if (\n !record ||\n !compilerManifestIsFresh(record) ||\n record.output !== state.bootstrap ||\n record.entries.length !== 1 ||\n canonicalPath(record.entries[0] ?? '') !== canonicalPath(expectedBootstrapEntry)\n ) {\n return undefined;\n }\n return record;\n}\n\n/** Validates only the bootstrap graph needed before the generated bootstrap is imported. */\nexport function readStartupBootstrapStatus(\n repoRoot: string,\n expectedBootstrapEntry?: string,\n homeDirectory: string = os.homedir(),\n): BootstrapStatus {\n const state = readBootstrapState(repoRoot, homeDirectory);\n const expected = expectedBootstrapEntry ?? packagedDoomEntry();\n if (!state) return { bootstrap: undefined, fresh: false };\n return {\n bootstrap: state.bootstrap,\n fresh: freshBootstrapRecord(state, expected) !== undefined,\n };\n}\n\n/** Validates one selected composition without touching any inactive bundle inputs. */\nexport function readBundleStatus(\n repoRoot: string,\n compositionFingerprint: string,\n homeDirectory: string = os.homedir(),\n): BundleStatus {\n const state = readBootstrapState(repoRoot, homeDirectory);\n const bundle = state?.bundles[compositionFingerprint];\n const manifestPath = state?.precompile?.bundleManifests[compositionFingerprint];\n if (!state || state.precompile?.version !== PRECOMPILE_STATE_VERSION || !bundle || !manifestPath) {\n return { bundle, fresh: false };\n }\n const manifest = readCompilerManifest(manifestPath, state.generatedDirectory);\n return {\n bundle,\n fresh: Boolean(manifest && manifest.output === bundle && compilerManifestIsFresh(manifest)),\n };\n}\n\n/** Validates the bootstrap and every bundle for `doompi sync --check` diagnostics. */\nexport function readBootstrapStatus(\n repoRoot: string,\n expectedBootstrapEntry?: string,\n homeDirectory: string = os.homedir(),\n): BootstrapStatus {\n const state = readBootstrapState(repoRoot, homeDirectory);\n const expected = expectedBootstrapEntry ?? packagedDoomEntry();\n if (!state?.bootstrap || !state.precompile || !freshBootstrapRecord(state, expected)) {\n return { bootstrap: state?.bootstrap, fresh: false };\n }\n const manifests = Object.values(state.precompile.bundleManifests);\n if (new Set(manifests).size !== manifests.length) return { bootstrap: state.bootstrap, fresh: false };\n const fresh = Object.entries(state.bundles).every(([fingerprint, bundle]) => {\n const manifestPath = state.precompile?.bundleManifests[fingerprint];\n if (!manifestPath) return false;\n const manifest = readCompilerManifest(manifestPath, state.generatedDirectory);\n return Boolean(manifest && manifest.output === bundle && compilerManifestIsFresh(manifest));\n });\n return { bootstrap: state.bootstrap, fresh };\n}\n\n/** Reads only the generated bootstrap pointer while retaining state validation. */\nexport function readBootstrapPointer(repoRoot: string, homeDirectory: string = os.homedir()): string | undefined {\n return readBootstrapState(repoRoot, homeDirectory)?.bootstrap;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/builders/cli/bootstrapLocator/index.ts"],"sourcesContent":["import crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport { readSyncRegistration } from '@agimon-ai/doompi-core/sync-registration';\nimport { BUNDLED_PRECOMPILE_STRATEGY, PRECOMPILE_STATE_VERSION } from '@agimon-ai/doompi-core/sync-state-contract';\n\nimport { inputsAreFresh, parseInputFingerprint, type InputFingerprint } from '../../../compiler/inputs';\nimport { resolveDoomConfigurationRoot } from '../../../composition/repository';\nimport { readSyncState } from '../../../composition/syncState';\nimport { ownEntry } from '../entryResolution';\n\nconst BOOTSTRAP_ENTRY_ENV = 'DOOMPI_BOOTSTRAP_ENTRY';\nconst SHA256 = /^[a-f0-9]{64}$/u;\n\ninterface CompilerManifest {\n output: string;\n artifacts: string[];\n entries: string[];\n inputs: InputFingerprint[];\n artifactInputs?: InputFingerprint[];\n}\n\ninterface BootstrapState {\n statePath: string;\n generatedDirectory: string;\n bootstrap?: string;\n bundles: Record<string, string>;\n precompile?: {\n version: number;\n strategy: 'bundle';\n bootstrapEntry: string;\n bootstrapManifest: string;\n bundleManifests: Record<string, string>;\n };\n}\n\nexport interface BootstrapStatus {\n bootstrap?: string;\n fresh: boolean;\n}\n\nexport interface BundleStatus {\n bundle?: string;\n fresh: boolean;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction stringRecord(value: unknown): Record<string, string> {\n if (!isRecord(value)) return {};\n return Object.fromEntries(\n Object.entries(value).filter((entry): entry is [string, string] => typeof entry[1] === 'string'),\n );\n}\n\nfunction canonicalPath(target: string): string {\n const absolute = path.resolve(target);\n try {\n return fs.realpathSync(absolute);\n } catch {\n const parent = path.dirname(absolute);\n if (parent === absolute) return absolute;\n return path.join(canonicalPath(parent), path.basename(absolute));\n }\n}\n\nfunction isInside(directory: string, target: string): boolean {\n const relative = path.relative(canonicalPath(directory), canonicalPath(target));\n return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));\n}\n\nfunction parseFingerprints(value: unknown): InputFingerprint[] | undefined {\n if (!Array.isArray(value)) return undefined;\n const fingerprints = value.map(parseInputFingerprint);\n return fingerprints.some((fingerprint) => fingerprint === undefined)\n ? undefined\n : (fingerprints as InputFingerprint[]);\n}\n\nfunction packagedDoomEntry(): string {\n return process.env[BOOTSTRAP_ENTRY_ENV] || ownEntry('composedPi');\n}\n\nfunction locationHasState(repoRoot: string, homeDirectory: string): boolean {\n return readSyncRegistration(repoRoot, homeDirectory) !== undefined;\n}\n\n/** Finds the nearest configured repository with generated Doom sync state. */\nexport function findSyncedRoot(cwd: string, homeDirectory: string = os.homedir()): string | undefined {\n if (locationHasState(cwd, homeDirectory)) return canonicalPath(cwd);\n const root = resolveDoomConfigurationRoot(cwd, homeDirectory);\n return locationHasState(root, homeDirectory) ? canonicalPath(root) : undefined;\n}\n\nfunction readBootstrapState(repoRoot: string, homeDirectory: string = os.homedir()): BootstrapState | undefined {\n const registration = readSyncRegistration(repoRoot, homeDirectory);\n if (!registration) return undefined;\n const statePath = registration.statePath;\n const generatedDirectory = registration.generationRoot;\n const state = readSyncState(repoRoot, homeDirectory);\n if (!state) return undefined;\n const bootstrap = typeof state.bootstrap === 'string' ? state.bootstrap : undefined;\n if (bootstrap && !isInside(generatedDirectory, bootstrap)) {\n throw new Error(`Doom bootstrap must stay inside ${generatedDirectory}: ${bootstrap}`);\n }\n\n let precompile: BootstrapState['precompile'];\n if (state.precompile !== undefined) {\n const value = state.precompile;\n if (\n !isRecord(value) ||\n typeof value.version !== 'number' ||\n value.strategy !== BUNDLED_PRECOMPILE_STRATEGY ||\n typeof value.bootstrapEntry !== 'string' ||\n typeof value.bootstrapManifest !== 'string' ||\n !isRecord(value.bundleManifests) ||\n Object.values(value.bundleManifests).some((manifest) => typeof manifest !== 'string')\n ) {\n throw new Error(`Doom sync state at ${statePath} has an invalid precompile record`);\n }\n precompile = {\n version: value.version,\n strategy: value.strategy,\n bootstrapEntry: value.bootstrapEntry,\n bootstrapManifest: value.bootstrapManifest,\n bundleManifests: value.bundleManifests as Record<string, string>,\n };\n }\n\n return {\n statePath,\n generatedDirectory,\n bootstrap,\n bundles: stringRecord(state.bundles),\n precompile,\n };\n}\n\nfunction readCompilerManifest(manifestPath: string, generatedDirectory: string): CompilerManifest | undefined {\n if (!isInside(generatedDirectory, manifestPath)) return undefined;\n try {\n const parsed = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) as unknown;\n if (\n !isRecord(parsed) ||\n typeof parsed.output !== 'string' ||\n !Array.isArray(parsed.artifacts) ||\n parsed.artifacts.some((artifact) => typeof artifact !== 'string') ||\n !Array.isArray(parsed.entries) ||\n parsed.entries.some((entry) => typeof entry !== 'string') ||\n !Array.isArray(parsed.inputs) ||\n (parsed.artifactInputs !== undefined && !Array.isArray(parsed.artifactInputs))\n ) {\n return undefined;\n }\n const inputs = parseFingerprints(parsed.inputs);\n const artifactInputs = parsed.artifactInputs === undefined ? undefined : parseFingerprints(parsed.artifactInputs);\n if (!inputs || (parsed.artifactInputs !== undefined && !artifactInputs)) return undefined;\n const artifacts = parsed.artifacts as string[];\n const entries = parsed.entries as string[];\n if (\n !isInside(generatedDirectory, parsed.output) ||\n artifacts.some((artifact) => !isInside(generatedDirectory, artifact))\n ) {\n return undefined;\n }\n return { output: parsed.output, artifacts, entries, inputs, ...(artifactInputs ? { artifactInputs } : {}) };\n } catch {\n return undefined;\n }\n}\n\nfunction artifactInputsAreIntact(manifest: CompilerManifest, generatedDirectory: string): boolean {\n try {\n if (!manifest.artifactInputs || manifest.artifactInputs.length === 0) return false;\n const expected = new Set([manifest.output, ...manifest.artifacts].map(canonicalPath));\n const seen = new Set<string>();\n for (const input of manifest.artifactInputs) {\n if (!SHA256.test(input.sha256) || !isInside(generatedDirectory, input.path)) return false;\n const target = canonicalPath(input.path);\n if (!expected.has(target)) return false;\n const stat = fs.statSync(target);\n if (!stat.isFile() || stat.size !== input.size) return false;\n if (crypto.createHash('sha256').update(fs.readFileSync(target)).digest('hex') !== input.sha256) return false;\n seen.add(target);\n }\n return seen.size === expected.size;\n } catch {\n return false;\n }\n}\n\nfunction compilerManifestIsUsable(manifest: CompilerManifest, generatedDirectory: string): boolean {\n return artifactInputsAreIntact(manifest, generatedDirectory);\n}\n\nfunction compilerManifestIsFresh(manifest: CompilerManifest, generatedDirectory: string): boolean {\n return compilerManifestIsUsable(manifest, generatedDirectory) && inputsAreFresh(manifest.inputs);\n}\n\nfunction bootstrapRecord(\n state: BootstrapState,\n expectedBootstrapEntry: string,\n requireFreshInputs: boolean,\n): CompilerManifest | undefined {\n if (!state.bootstrap || !state.precompile) return undefined;\n if (\n state.precompile.version !== PRECOMPILE_STATE_VERSION ||\n canonicalPath(state.precompile.bootstrapEntry) !== canonicalPath(expectedBootstrapEntry)\n ) {\n return undefined;\n }\n const record = readCompilerManifest(state.precompile.bootstrapManifest, state.generatedDirectory);\n if (\n !record ||\n !compilerManifestIsUsable(record, state.generatedDirectory) ||\n (requireFreshInputs && !compilerManifestIsFresh(record, state.generatedDirectory)) ||\n record.output !== state.bootstrap ||\n record.entries.length !== 1 ||\n canonicalPath(record.entries[0] ?? '') !== canonicalPath(expectedBootstrapEntry)\n ) {\n return undefined;\n }\n return record;\n}\n\nfunction usableBootstrapRecord(state: BootstrapState, expectedBootstrapEntry: string): CompilerManifest | undefined {\n return bootstrapRecord(state, expectedBootstrapEntry, false);\n}\n\nfunction freshBootstrapRecord(state: BootstrapState, expectedBootstrapEntry: string): CompilerManifest | undefined {\n return bootstrapRecord(state, expectedBootstrapEntry, true);\n}\n\n/** Validates only the bootstrap graph needed before the generated bootstrap is imported. */\nexport function readStartupBootstrapStatus(\n repoRoot: string,\n expectedBootstrapEntry?: string,\n homeDirectory: string = os.homedir(),\n): BootstrapStatus {\n const state = readBootstrapState(repoRoot, homeDirectory);\n const expected = expectedBootstrapEntry ?? packagedDoomEntry();\n if (!state) return { bootstrap: undefined, fresh: false };\n return {\n bootstrap: state.bootstrap,\n fresh: usableBootstrapRecord(state, expected) !== undefined,\n };\n}\n\n/** Validates one selected composition without touching any inactive bundle inputs. */\nexport function readBundleStatus(\n repoRoot: string,\n compositionFingerprint: string,\n homeDirectory: string = os.homedir(),\n): BundleStatus {\n const state = readBootstrapState(repoRoot, homeDirectory);\n const bundle = state?.bundles[compositionFingerprint];\n const manifestPath = state?.precompile?.bundleManifests[compositionFingerprint];\n if (!state || state.precompile?.version !== PRECOMPILE_STATE_VERSION || !bundle || !manifestPath) {\n return { bundle, fresh: false };\n }\n const manifest = readCompilerManifest(manifestPath, state.generatedDirectory);\n return {\n bundle,\n fresh: Boolean(\n manifest && manifest.output === bundle && compilerManifestIsUsable(manifest, state.generatedDirectory),\n ),\n };\n}\n\n/** Validates the bootstrap and every bundle for `doompi sync --check` diagnostics. */\nexport function readBootstrapStatus(\n repoRoot: string,\n expectedBootstrapEntry?: string,\n homeDirectory: string = os.homedir(),\n): BootstrapStatus {\n const state = readBootstrapState(repoRoot, homeDirectory);\n const expected = expectedBootstrapEntry ?? packagedDoomEntry();\n if (!state?.bootstrap || !state.precompile || !freshBootstrapRecord(state, expected)) {\n return { bootstrap: state?.bootstrap, fresh: false };\n }\n const manifests = Object.values(state.precompile.bundleManifests);\n if (new Set(manifests).size !== manifests.length) return { bootstrap: state.bootstrap, fresh: false };\n const fresh = Object.entries(state.bundles).every(([fingerprint, bundle]) => {\n const manifestPath = state.precompile?.bundleManifests[fingerprint];\n if (!manifestPath) return false;\n const manifest = readCompilerManifest(manifestPath, state.generatedDirectory);\n return Boolean(\n manifest && manifest.output === bundle && compilerManifestIsFresh(manifest, state.generatedDirectory),\n );\n });\n return { bootstrap: state.bootstrap, fresh };\n}\n\n/** Reads only the generated bootstrap pointer while retaining state validation. */\nexport function readBootstrapPointer(repoRoot: string, homeDirectory: string = os.homedir()): string | undefined {\n return readBootstrapState(repoRoot, homeDirectory)?.bootstrap;\n}\n"],"mappings":"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"}
@@ -15,7 +15,7 @@ function dispatcherManifest() {
15
15
  name: _agimon_ai_doompi_core_doom_package.DOOM_PACKAGE_NAME,
16
16
  private: true,
17
17
  type: "module",
18
- doompiDispatcher: 2,
18
+ doompiDispatcher: 3,
19
19
  pi: { extensions: [`./${DISPATCHER_ENTRY}`] }
20
20
  }, null, 2)}\n`;
21
21
  }
@@ -27,6 +27,8 @@ import path from 'node:path';
27
27
  import { pathToFileURL } from 'node:url';
28
28
 
29
29
  const REGISTRATION_VERSION = ${String(_agimon_ai_doompi_core_sync_registration.SYNC_REGISTRATION_VERSION)};
30
+ const LEGACY_REGISTRATION_VERSION = ${String(_agimon_ai_doompi_core_sync_registration.LEGACY_SYNC_REGISTRATION_VERSION)};
31
+ const API_VERSION = ${String(_agimon_ai_doompi_core_sync_registration.DOOMPI_API_VERSION)};
30
32
  const PACKAGE_NAME = ${JSON.stringify(_agimon_ai_doompi_core_doom_package.DOOM_PACKAGE_NAME)};
31
33
  const WARNING = 'warning';
32
34
 
@@ -89,7 +91,7 @@ function registration(root) {
89
91
  const ids = identity(root);
90
92
  const recordPath = path.join(os.homedir(), '.pi', '.doom', 'sync', 'registrations', ids.repositoryId, \`\${ids.worktreeId}.json\`);
91
93
  const value = JSON.parse(fs.readFileSync(recordPath, 'utf8'));
92
- if (value.version !== REGISTRATION_VERSION || canonical(value.root) !== root) throw new Error('registration identity mismatch');
94
+ if ((value.version !== REGISTRATION_VERSION && value.version !== LEGACY_REGISTRATION_VERSION) || canonical(value.root) !== root) throw new Error('registration identity mismatch');
93
95
  if (value.identity?.repositoryId !== ids.repositoryId || value.identity?.worktreeId !== ids.worktreeId) throw new Error('registration worktree mismatch');
94
96
  if (!inside(value.generationRoot, value.statePath) || !inside(value.package.root, value.package.entry)) throw new Error('registration path escapes its owner');
95
97
  const stateHash = crypto.createHash('sha256').update(fs.readFileSync(value.statePath)).digest('hex');
@@ -97,7 +99,14 @@ function registration(root) {
97
99
  const manifestPath = path.join(canonical(value.package.root), 'package.json');
98
100
  if (canonical(value.package.manifestPath) !== canonical(manifestPath)) throw new Error('registration package manifest mismatch');
99
101
  const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
100
- if (manifest.name !== PACKAGE_NAME || manifest.version !== value.package.version) throw new Error('registration package mismatch');
102
+ if (manifest.name !== PACKAGE_NAME || typeof manifest.version !== 'string') throw new Error('registration package mismatch');
103
+ if (value.version === LEGACY_REGISTRATION_VERSION && value.package.apiVersion === undefined) {
104
+ if (manifest.version !== value.package.version) throw new Error('registration package mismatch');
105
+ } else {
106
+ if (!Number.isSafeInteger(value.package.apiVersion) || value.package.apiVersion < 1) throw new Error('invalid package API version');
107
+ if (manifest.doompiApiVersion !== value.package.apiVersion) throw new Error('registration package API mismatch');
108
+ if (value.package.apiVersion !== API_VERSION) throw new Error('unsupported package API version');
109
+ }
101
110
  const entries = manifest.pi?.extensions;
102
111
  if (!Array.isArray(entries) || !entries.some((entry) => typeof entry === 'string' && canonical(path.resolve(value.package.root, entry)) === canonical(value.package.entry))) throw new Error('registration package entry mismatch');
103
112
  return value;
@@ -133,11 +142,11 @@ function managedManifestVersion(directory) {
133
142
  }
134
143
  }
135
144
  function managedManifest(directory) {
136
- return managedManifestVersion(directory) === 2;
145
+ return managedManifestVersion(directory) === 3;
137
146
  }
138
147
  function upgradeableManagedManifest(directory) {
139
148
  const version = managedManifestVersion(directory);
140
- return version !== void 0 && version > 0 && version <= 2;
149
+ return version !== void 0 && version > 0 && version <= 3;
141
150
  }
142
151
  /** Whether init's dispatcher package has a supported protocol and complete entry. */
143
152
  function piExtensionDispatcherIsCurrent(piDirectory) {