@wildo-ai/wildo-module-client 1.1.4 → 1.1.6

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 (53) hide show
  1. package/dist/esm/index.d.ts.map +1 -1
  2. package/dist/esm/services/_http-client.d.ts +32 -3
  3. package/dist/esm/services/_http-client.d.ts.map +1 -1
  4. package/dist/esm/services/_http-client.js +100 -6
  5. package/dist/esm/services/_http-client.js.map +1 -1
  6. package/dist/esm/services/_zod-issue-formatting.d.ts.map +1 -1
  7. package/dist/esm/services/index.d.ts.map +1 -1
  8. package/dist/esm/services/lockfile-repair.service.d.ts +8 -23
  9. package/dist/esm/services/lockfile-repair.service.d.ts.map +1 -1
  10. package/dist/esm/services/lockfile-repair.service.js +10 -28
  11. package/dist/esm/services/lockfile-repair.service.js.map +1 -1
  12. package/dist/esm/services/module-adopter.service.d.ts +1 -1
  13. package/dist/esm/services/module-adopter.service.d.ts.map +1 -1
  14. package/dist/esm/services/module-adopter.service.js +7 -8
  15. package/dist/esm/services/module-adopter.service.js.map +1 -1
  16. package/dist/esm/services/module-packager.service.d.ts.map +1 -1
  17. package/dist/esm/services/module-packager.service.js +16 -19
  18. package/dist/esm/services/module-packager.service.js.map +1 -1
  19. package/dist/esm/services/provider-materialization-hook.d.ts.map +1 -1
  20. package/dist/esm/services/provider-sync-runner.d.ts +2 -3
  21. package/dist/esm/services/provider-sync-runner.d.ts.map +1 -1
  22. package/dist/esm/services/provider-sync-runner.js +14 -2
  23. package/dist/esm/services/provider-sync-runner.js.map +1 -1
  24. package/dist/esm/services/registry-companion-client.d.ts +10 -25
  25. package/dist/esm/services/registry-companion-client.d.ts.map +1 -1
  26. package/dist/esm/services/registry-companion-client.js +9 -83
  27. package/dist/esm/services/registry-companion-client.js.map +1 -1
  28. package/dist/esm/services/registry-lockfile.service.d.ts +7 -3
  29. package/dist/esm/services/registry-lockfile.service.d.ts.map +1 -1
  30. package/dist/esm/services/registry-lockfile.service.js +8 -3
  31. package/dist/esm/services/registry-lockfile.service.js.map +1 -1
  32. package/dist/esm/services/registry-server-client.d.ts +35 -9
  33. package/dist/esm/services/registry-server-client.d.ts.map +1 -1
  34. package/dist/esm/services/registry-server-client.js +115 -58
  35. package/dist/esm/services/registry-server-client.js.map +1 -1
  36. package/dist/tsconfig.build.tsbuildinfo +1 -1
  37. package/dist/tsconfig.tsbuildinfo +1 -1
  38. package/package.json +2 -2
  39. package/src/__tests__/lockfile-repair.service.test.ts +2 -2
  40. package/src/__tests__/module-adopter.service.test.ts +6 -4
  41. package/src/__tests__/module-packager.service.test.ts +36 -34
  42. package/src/__tests__/provider-sync-runner.test.ts +12 -0
  43. package/src/__tests__/registry-lockfile.service.test.ts +3 -3
  44. package/src/__tests__/registry-server-client.test.ts +129 -0
  45. package/src/__tests__/registry-transfer-timeouts.test.ts +94 -0
  46. package/src/services/_http-client.ts +100 -6
  47. package/src/services/lockfile-repair.service.ts +9 -34
  48. package/src/services/module-adopter.service.ts +6 -9
  49. package/src/services/module-packager.service.ts +19 -16
  50. package/src/services/provider-sync-runner.ts +16 -5
  51. package/src/services/registry-companion-client.ts +10 -132
  52. package/src/services/registry-lockfile.service.ts +9 -3
  53. package/src/services/registry-server-client.ts +147 -57
@@ -1 +1 @@
1
- {"version":3,"file":"module-packager.service.js","sourceRoot":"","sources":["../../../../src/services/module-packager.service.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `ModulePackagerService` — Phase 2 staging logic for module\n * publishing.\n *\n * Per KD-8 / `module-registry-analysis.md`:\n * - Published artifacts are zip archives staged under\n * `<saasRoot>/.wildo-saas/generated/modules-artifacts/<module.id>/`.\n * - The staged folder is the source of the artifact. It must\n * include the `wildo.module.json` manifest, copied source\n * files, and dependency declarations.\n *\n * Phase 2 ships the staging-folder primitives (path resolution,\n * stage-fresh, copy-source-tree, write-manifest, compute-digest).\n * Phase 6 wires `wildo registry publish` to call these and the zip\n * step. Phase 5 install consumes the same digest.\n *\n * Hard contracts:\n * - Staging is always a fresh tree per call (`stageFresh`\n * removes the destination first). The artifact's content\n * digest is over-the-wire deterministic — leftover bytes\n * from a previous failed stage MUST NOT leak into the digest.\n * - The staged tree is a CHILD of `<saasRoot>/.wildo-saas/`,\n * never reaches outside `saasRoot`.\n * - Boundary enforcement (KD-12) lives in this service: the\n * staging primitive rejects file paths that escape the\n * declared `boundaries.<layer>` allow-list, so a publish\n * can never claim app surface it doesn't own.\n */\n\nimport { createHash } from 'node:crypto';\nimport { promises as fs } from 'node:fs';\nimport * as path from 'node:path';\nimport {\n ModuleManifestSchema,\n RegistryBoundaryViolationError,\n type ModuleManifest,\n type ModuleManifestBoundaries,\n} from '@wildo-ai/platform-config-lib';\n\nexport const MODULES_ARTIFACTS_RELATIVE_DIR = '.wildo-saas/generated/modules-artifacts';\nconst MANIFEST_FILE_NAME = 'wildo.module.json';\n\nexport interface StagingPaths {\n /** Absolute path to the per-module staging root. */\n stagingRoot: string;\n /** Absolute path to the manifest file inside the staging root. */\n manifestPath: string;\n}\n\n/**\n * Resolve the per-module staging paths. Pure path arithmetic — no\n * fs side effects.\n */\nexport function getModuleStagingPaths(saasRoot: string, moduleId: string): StagingPaths {\n if (!moduleId.includes('/')) {\n // C-L3 (4th-pass review): `TypeError` is Node's standard for\n // argument-shape violations. The typed `RegistryError` subclasses\n // model registry-runtime conditions (auth, integrity, version\n // not found); a malformed `moduleId` argument is a developer\n // contract violation that downstream `isRegistryError(err)`\n // handlers should NOT catch. `TypeError` keeps the error visible\n // and stops the typed-error vocabulary from absorbing pure\n // argument-validation concerns.\n throw new TypeError(\n `Module id \"${moduleId}\" must be in <domain>/<name> form (e.g. \"acme/billing\").`,\n );\n }\n const stagingRoot = path.resolve(saasRoot, MODULES_ARTIFACTS_RELATIVE_DIR, moduleId);\n return {\n stagingRoot,\n manifestPath: path.join(stagingRoot, MANIFEST_FILE_NAME),\n };\n}\n\n/**\n * Returns `true` iff `child` is the same path as `parent` or\n * a descendant of it. Both arguments are expected to be absolute,\n * already resolved (no `..` segments). Tail-`/` arithmetic prevents\n * the classic `/foo/barx` → `/foo/bar` false-positive.\n *\n * Extracted as a helper (5th-pass C-L2) so the boundary check below\n * and the future zip-slip / symlink-escape guards in this package\n * share one reading of the containment rule.\n *\n * 6th-pass C-L1 — root edge case: when `parent` is the filesystem\n * root (`/` on POSIX, `C:\\` on Windows), appending `path.sep` would\n * produce `//` or `C:\\\\` and `startsWith` would reject every child.\n * Treat the root as a special case: any absolute child path is\n * \"inside\" the root by definition. Vanishingly unlikely in practice\n * (containerized SaaS roots are not literally `/`) but the helper\n * is also reusable for zip-slip guards where the assumption is\n * weaker.\n */\nfunction isPathInside(child: string, parent: string): boolean {\n // Special-case the filesystem root: any absolute child is inside it.\n if (parent === path.sep || /^[a-zA-Z]:[\\\\/]?$/.test(parent)) {\n return path.isAbsolute(child);\n }\n const childWithSep = child + path.sep;\n const parentWithSep = parent + path.sep;\n return childWithSep.startsWith(parentWithSep);\n}\n\n/**\n * Verify a candidate file path is contained inside one of the\n * manifest's declared `boundaries` (KD-12). Returns the resolved\n * absolute path on success; throws `RegistryBoundaryViolationError`\n * otherwise.\n */\nexport function assertWithinBoundaries(\n saasRoot: string,\n moduleId: string,\n appRelativePath: string,\n boundaries: ModuleManifestBoundaries | undefined,\n): string {\n const absolute = path.resolve(saasRoot, appRelativePath);\n // Path must stay inside saasRoot (no `..` escapes).\n if (!isPathInside(absolute, path.resolve(saasRoot))) {\n throw new RegistryBoundaryViolationError(moduleId, appRelativePath);\n }\n if (!boundaries) return absolute;\n const allowed: readonly string[] = [\n ...boundaries.specifications,\n ...boundaries.shared,\n ...boundaries.frontend,\n ...boundaries.backend,\n ...boundaries.minions,\n ...boundaries.workers,\n ];\n if (allowed.length === 0) return absolute;\n // The candidate must be inside at least one declared boundary.\n const isInside = allowed.some((rel) => isPathInside(absolute, path.resolve(saasRoot, rel)));\n if (!isInside) {\n throw new RegistryBoundaryViolationError(moduleId, appRelativePath);\n }\n return absolute;\n}\n\nexport const ModulePackagerService = {\n /**\n * Compute the per-module staging paths.\n */\n paths(saasRoot: string, moduleId: string): StagingPaths {\n return getModuleStagingPaths(saasRoot, moduleId);\n },\n\n /**\n * Remove and recreate the staging root. The freshness guarantee\n * is critical for digest determinism — `stageFresh` is the only\n * way to enter the per-module staging root.\n */\n async stageFresh(saasRoot: string, moduleId: string): Promise<StagingPaths> {\n const paths = getModuleStagingPaths(saasRoot, moduleId);\n await fs.rm(paths.stagingRoot, { recursive: true, force: true });\n await fs.mkdir(paths.stagingRoot, { recursive: true });\n return paths;\n },\n\n /**\n * Copy a single file from the app source into the staging tree.\n * Path is verified against the manifest's declared boundaries\n * (KD-12) — files outside `boundaries.*` are rejected with\n * `RegistryBoundaryViolationError`. The relative path INSIDE the\n * staging root mirrors the relative path inside the saas root,\n * so callers don't need to translate paths between the two\n * trees.\n */\n async copyFile(args: {\n saasRoot: string;\n moduleId: string;\n appRelativePath: string;\n manifest: ModuleManifest;\n }): Promise<{ stagedAbsolute: string; appRelativePath: string }> {\n const { saasRoot, moduleId, appRelativePath, manifest } = args;\n const sourceAbsolute = assertWithinBoundaries(\n saasRoot,\n moduleId,\n appRelativePath,\n manifest.boundaries,\n );\n const { stagingRoot } = getModuleStagingPaths(saasRoot, moduleId);\n const destAbsolute = path.join(stagingRoot, appRelativePath);\n await fs.mkdir(path.dirname(destAbsolute), { recursive: true });\n await fs.copyFile(sourceAbsolute, destAbsolute);\n return { stagedAbsolute: destAbsolute, appRelativePath };\n },\n\n /**\n * Validate the manifest, then write `wildo.module.json` into the\n * staging root. Manifest authors who pass a typo-laden object\n * get a Zod parse error here, before any zip work.\n */\n async writeManifest(args: {\n saasRoot: string;\n manifest: ModuleManifest;\n }): Promise<{ manifestPath: string; manifest: ModuleManifest }> {\n const validated = ModuleManifestSchema.parse(args.manifest);\n const { manifestPath, stagingRoot } = getModuleStagingPaths(args.saasRoot, validated.id);\n await fs.mkdir(stagingRoot, { recursive: true });\n await fs.writeFile(manifestPath, JSON.stringify(validated, null, 2) + '\\n', 'utf8');\n return { manifestPath, manifest: validated };\n },\n\n /**\n * Compute the sha256 digest of every file in the staging tree, in\n * stable lexicographic path order. Returns the canonical\n * `sha256:<64-hex>` form.\n *\n * The digest is over-the-wire deterministic for the same set of\n * staged files because:\n * 1. File order is sorted lexicographically (no fs traversal\n * randomness leaks in).\n * 2. The path prefix is the relative-from-staging-root path\n * (no absolute-path noise).\n * 3. We append the file content bytes verbatim.\n */\n async computeStagingDigest(saasRoot: string, moduleId: string): Promise<string> {\n const { stagingRoot } = getModuleStagingPaths(saasRoot, moduleId);\n const files = await collectFilesRelative(stagingRoot, '');\n // Normalize separators to POSIX before sorting + hashing — the\n // digest must reproduce across platforms for the same staged\n // tree, so the bytes fed into the hash cannot depend on the\n // publisher's OS path-separator convention.\n const normalized = files.map((f) => f.split(path.sep).join(path.posix.sep));\n normalized.sort();\n const hash = createHash('sha256');\n for (const rel of normalized) {\n const abs = path.join(stagingRoot, rel.split(path.posix.sep).join(path.sep));\n hash.update(rel);\n hash.update('\\0');\n hash.update(await fs.readFile(abs));\n hash.update('\\0');\n }\n return `sha256:${hash.digest('hex')}`;\n },\n} as const;\n\nasync function collectFilesRelative(rootAbs: string, prefix: string): Promise<string[]> {\n const here = path.join(rootAbs, prefix);\n const entries = await fs.readdir(here, { withFileTypes: true });\n const out: string[] = [];\n for (const entry of entries) {\n const rel = path.join(prefix, entry.name);\n if (entry.isDirectory()) {\n out.push(...(await collectFilesRelative(rootAbs, rel)));\n } else if (entry.isFile()) {\n out.push(rel);\n }\n }\n return out;\n}\n"]}
1
+ {"version":3,"file":"module-packager.service.js","sourceRoot":"","sources":["../../../../src/services/module-packager.service.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `ModulePackagerService` — Phase 2 staging logic for module\n * publishing.\n *\n * Per KD-8 / `module-registry-analysis.md`:\n * - Published artifacts are zip archives staged under\n * `<saasRoot>/.wildo-saas/generated/modules-artifacts/<module.id>/`.\n * - The staged folder is the source of the artifact. It must\n * include the `wildo.module.json` manifest, copied source\n * files, and dependency declarations.\n *\n * Phase 2 ships the staging-folder primitives (path resolution,\n * stage-fresh, copy-source-tree, write-manifest, compute-digest).\n * Phase 6 wires `wildo registry publish` to call these and the zip\n * step. Phase 5 install consumes the same digest.\n *\n * Hard contracts:\n * - Staging is always a fresh tree per call (`stageFresh`\n * removes the destination first). The artifact's content\n * digest is over-the-wire deterministic — leftover bytes\n * from a previous failed stage MUST NOT leak into the digest.\n * - The staged tree is a CHILD of `<saasRoot>/.wildo-saas/`,\n * never reaches outside `saasRoot`.\n * - Boundary enforcement (KD-12) lives in this service: the\n * staging primitive rejects file paths that escape the\n * declared `boundaries.<layer>` allow-list, so a publish\n * can never claim app surface it doesn't own.\n */\n\nimport { createHash } from 'node:crypto';\nimport { promises as fs } from 'node:fs';\nimport * as path from 'node:path';\nimport {\n flattenModuleBoundaries,\n isModuleArtifactPathAllowed,\n isModuleId,\n ModuleManifestSchema,\n RegistryBoundaryViolationError,\n type ModuleManifest,\n type ModuleManifestBoundaries,\n MODULE_MANIFEST_FILE_NAME as MANIFEST_FILE_NAME,\n} from '@wildo-ai/platform-config-lib';\n\nexport const MODULES_ARTIFACTS_RELATIVE_DIR = '.wildo-saas/generated/modules-artifacts';\n\nexport interface StagingPaths {\n /** Absolute path to the per-module staging root. */\n stagingRoot: string;\n /** Absolute path to the manifest file inside the staging root. */\n manifestPath: string;\n}\n\n/**\n * Resolve the per-module staging paths. Pure path arithmetic — no\n * fs side effects.\n */\nexport function getModuleStagingPaths(saasRoot: string, moduleId: string): StagingPaths {\n // The full `<scope>/<name>` shape, not merely \"contains a `/`\": `stageFresh` runs a recursive delete\n // on the path built here, and `a/../../../..` contained a slash.\n if (!isModuleId(moduleId)) {\n // C-L3 (4th-pass review): `TypeError` is Node's standard for\n // argument-shape violations. The typed `RegistryError` subclasses\n // model registry-runtime conditions (auth, integrity, version\n // not found); a malformed `moduleId` argument is a developer\n // contract violation that downstream `isRegistryError(err)`\n // handlers should NOT catch. `TypeError` keeps the error visible\n // and stops the typed-error vocabulary from absorbing pure\n // argument-validation concerns.\n throw new TypeError(\n `Module id \"${moduleId}\" must be in <scope>/<name> form (e.g. \"acme/billing\"), each half lowercase letters, digits, \".\", \"_\" or \"-\".`,\n );\n }\n const artifactsRoot = path.resolve(saasRoot, MODULES_ARTIFACTS_RELATIVE_DIR);\n const stagingRoot = path.resolve(artifactsRoot, moduleId);\n if (!isPathInside(stagingRoot, artifactsRoot) || stagingRoot === artifactsRoot) {\n throw new TypeError(`Module id \"${moduleId}\" resolves outside ${MODULES_ARTIFACTS_RELATIVE_DIR}.`);\n }\n return {\n stagingRoot,\n manifestPath: path.join(stagingRoot, MANIFEST_FILE_NAME),\n };\n}\n\n/**\n * Returns `true` iff `child` is the same path as `parent` or\n * a descendant of it. Both arguments are expected to be absolute,\n * already resolved (no `..` segments). Tail-`/` arithmetic prevents\n * the classic `/foo/barx` → `/foo/bar` false-positive.\n *\n * Extracted as a helper (5th-pass C-L2) so the boundary check below\n * and the future zip-slip / symlink-escape guards in this package\n * share one reading of the containment rule.\n *\n * 6th-pass C-L1 — root edge case: when `parent` is the filesystem\n * root (`/` on POSIX, `C:\\` on Windows), appending `path.sep` would\n * produce `//` or `C:\\\\` and `startsWith` would reject every child.\n * Treat the root as a special case: any absolute child path is\n * \"inside\" the root by definition. Vanishingly unlikely in practice\n * (containerized SaaS roots are not literally `/`) but the helper\n * is also reusable for zip-slip guards where the assumption is\n * weaker.\n */\nfunction isPathInside(child: string, parent: string): boolean {\n // Special-case the filesystem root: any absolute child is inside it.\n if (parent === path.sep || /^[a-zA-Z]:[\\\\/]?$/.test(parent)) {\n return path.isAbsolute(child);\n }\n const childWithSep = child + path.sep;\n const parentWithSep = parent + path.sep;\n return childWithSep.startsWith(parentWithSep);\n}\n\n/**\n * Verify a candidate file path is contained inside one of the\n * manifest's declared `boundaries` (KD-12). Returns the resolved\n * absolute path on success; throws `RegistryBoundaryViolationError`\n * otherwise.\n */\nexport function assertWithinBoundaries(\n saasRoot: string,\n moduleId: string,\n appRelativePath: string,\n boundaries: ModuleManifestBoundaries | undefined,\n): string {\n const absolute = path.resolve(saasRoot, appRelativePath);\n // Path must stay inside saasRoot (no `..` escapes), and be a path a module artifact may carry at\n // all — no dot-segment (`.env`, `.git`), no `node_modules` — whatever the boundaries say.\n if (!isPathInside(absolute, path.resolve(saasRoot)) || !isModuleArtifactPathAllowed(appRelativePath.split(path.sep).join('/'))) {\n throw new RegistryBoundaryViolationError(moduleId, appRelativePath);\n }\n // No declared boundary means the module may write NOWHERE. This used to return early and admit\n // every path in the application — the fail-open reading of an absent declaration.\n const allowed: readonly string[] = flattenModuleBoundaries(boundaries);\n // The candidate must be inside at least one declared boundary.\n const isInside = allowed.some((rel) => isPathInside(absolute, path.resolve(saasRoot, rel)));\n if (!isInside) {\n throw new RegistryBoundaryViolationError(moduleId, appRelativePath);\n }\n return absolute;\n}\n\nexport const ModulePackagerService = {\n /**\n * Compute the per-module staging paths.\n */\n paths(saasRoot: string, moduleId: string): StagingPaths {\n return getModuleStagingPaths(saasRoot, moduleId);\n },\n\n /**\n * Remove and recreate the staging root. The freshness guarantee\n * is critical for digest determinism — `stageFresh` is the only\n * way to enter the per-module staging root.\n */\n async stageFresh(saasRoot: string, moduleId: string): Promise<StagingPaths> {\n const paths = getModuleStagingPaths(saasRoot, moduleId);\n await fs.rm(paths.stagingRoot, { recursive: true, force: true });\n await fs.mkdir(paths.stagingRoot, { recursive: true });\n return paths;\n },\n\n /**\n * Copy a single file from the app source into the staging tree.\n * Path is verified against the manifest's declared boundaries\n * (KD-12) — files outside `boundaries.*` are rejected with\n * `RegistryBoundaryViolationError`. The relative path INSIDE the\n * staging root mirrors the relative path inside the saas root,\n * so callers don't need to translate paths between the two\n * trees.\n */\n async copyFile(args: {\n saasRoot: string;\n moduleId: string;\n appRelativePath: string;\n manifest: ModuleManifest;\n }): Promise<{ stagedAbsolute: string; appRelativePath: string }> {\n const { saasRoot, moduleId, appRelativePath, manifest } = args;\n const sourceAbsolute = assertWithinBoundaries(\n saasRoot,\n moduleId,\n appRelativePath,\n manifest.boundaries,\n );\n const { stagingRoot } = getModuleStagingPaths(saasRoot, moduleId);\n const destAbsolute = path.join(stagingRoot, appRelativePath);\n await fs.mkdir(path.dirname(destAbsolute), { recursive: true });\n await fs.copyFile(sourceAbsolute, destAbsolute);\n return { stagedAbsolute: destAbsolute, appRelativePath };\n },\n\n /**\n * Validate the manifest, then write `wildo.module.json` into the\n * staging root. Manifest authors who pass a typo-laden object\n * get a Zod parse error here, before any zip work.\n */\n async writeManifest(args: {\n saasRoot: string;\n manifest: ModuleManifest;\n }): Promise<{ manifestPath: string; manifest: ModuleManifest }> {\n const validated = ModuleManifestSchema.parse(args.manifest);\n const { manifestPath, stagingRoot } = getModuleStagingPaths(args.saasRoot, validated.id);\n await fs.mkdir(stagingRoot, { recursive: true });\n await fs.writeFile(manifestPath, JSON.stringify(validated, null, 2) + '\\n', 'utf8');\n return { manifestPath, manifest: validated };\n },\n\n /**\n * Compute the sha256 digest of every file in the staging tree, in\n * stable lexicographic path order. Returns the canonical\n * `sha256:<64-hex>` form.\n *\n * The digest is over-the-wire deterministic for the same set of\n * staged files because:\n * 1. File order is sorted lexicographically (no fs traversal\n * randomness leaks in).\n * 2. The path prefix is the relative-from-staging-root path\n * (no absolute-path noise).\n * 3. We append the file content bytes verbatim.\n */\n async computeStagingDigest(saasRoot: string, moduleId: string): Promise<string> {\n const { stagingRoot } = getModuleStagingPaths(saasRoot, moduleId);\n const files = await collectFilesRelative(stagingRoot, '');\n // Normalize separators to POSIX before sorting + hashing — the\n // digest must reproduce across platforms for the same staged\n // tree, so the bytes fed into the hash cannot depend on the\n // publisher's OS path-separator convention.\n const normalized = files.map((f) => f.split(path.sep).join(path.posix.sep));\n normalized.sort();\n const hash = createHash('sha256');\n for (const rel of normalized) {\n const abs = path.join(stagingRoot, rel.split(path.posix.sep).join(path.sep));\n hash.update(rel);\n hash.update('\\0');\n hash.update(await fs.readFile(abs));\n hash.update('\\0');\n }\n return `sha256:${hash.digest('hex')}`;\n },\n} as const;\n\nasync function collectFilesRelative(rootAbs: string, prefix: string): Promise<string[]> {\n const here = path.join(rootAbs, prefix);\n const entries = await fs.readdir(here, { withFileTypes: true });\n const out: string[] = [];\n for (const entry of entries) {\n const rel = path.join(prefix, entry.name);\n if (entry.isDirectory()) {\n out.push(...(await collectFilesRelative(rootAbs, rel)));\n } else if (entry.isFile()) {\n out.push(rel);\n }\n }\n return out;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"provider-materialization-hook.d.ts","sourceRoot":"","sources":["../../../src/services/provider-materialization-hook.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAE3E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAEpD,MAAM,WAAW,gCAAgC;IAC/C,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,GAAG,OAAO,CAAC;IAC5D,GAAG,CAAC,EAAE,oBAAoB,CAAC;CAC5B;AAED,MAAM,WAAW,iCAAiC;IAChD,OAAO,EAAE,IAAI,CAAC;IACd,IAAI,EAAE,SAAS,GAAG,WAAW,CAAC;IAC9B,gBAAgB,EAAE,KAAK,CAAC;IACxB,yBAAyB,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7C,0BAA0B,EAAE,MAAM,CAAC;IACnC,uBAAuB,EAAE,OAAO,0BAA0B,CAAC;IAC3D,MAAM,EAAE,gCAAgC,CAAC,QAAQ,CAAC,CAAC;CACpD;AAED,wBAAsB,8BAA8B,CAClD,KAAK,EAAE,gCAAgC,GACtC,OAAO,CAAC,iCAAiC,CAAC,CAgB5C"}
1
+ {"version":3,"file":"provider-materialization-hook.d.ts","sourceRoot":"","sources":["../../../../src/services/provider-materialization-hook.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAE3E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAEpD,MAAM,WAAW,gCAAgC;IAC/C,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,GAAG,OAAO,CAAC;IAC5D,GAAG,CAAC,EAAE,oBAAoB,CAAC;CAC5B;AAED,MAAM,WAAW,iCAAiC;IAChD,OAAO,EAAE,IAAI,CAAC;IACd,IAAI,EAAE,SAAS,GAAG,WAAW,CAAC;IAC9B,gBAAgB,EAAE,KAAK,CAAC;IACxB,yBAAyB,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7C,0BAA0B,EAAE,MAAM,CAAC;IACnC,uBAAuB,EAAE,OAAO,0BAA0B,CAAC;IAC3D,MAAM,EAAE,gCAAgC,CAAC,QAAQ,CAAC,CAAC;CACpD;AAED,wBAAsB,8BAA8B,CAClD,KAAK,EAAE,gCAAgC,GACtC,OAAO,CAAC,iCAAiC,CAAC,CAgB5C"}
@@ -45,9 +45,8 @@ export interface ProviderSyncRunnerInput {
45
45
  */
46
46
  wildoBinPath?: string;
47
47
  /**
48
- * Maximum wall-time before SIGTERM. Default 120s — provider sync
49
- * can be heavy on first run (artifact generation + cleanup),
50
- * but every additional second is operator-visible.
48
+ * Maximum wall-time before SIGTERM. Default {@link DEFAULT_TIMEOUT_MS} (five minutes): it only ever
49
+ * elapses on a wedged sync, and a healthy one under load has been measured past two minutes.
51
50
  */
52
51
  timeoutMs?: number;
53
52
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"provider-sync-runner.d.ts","sourceRoot":"","sources":["../../../src/services/provider-sync-runner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAKH,OAAO,EACL,0BAA0B,EAI3B,MAAM,+BAA+B,CAAC;AACvC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAIpD,MAAM,WAAW,uBAAuB;IACtC,kDAAkD;IAClD,QAAQ,EAAE,MAAM,CAAC;IACjB,oDAAoD;IACpD,MAAM,EAAE,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,GAAG,OAAO,CAAC;IAC5D,0CAA0C;IAC1C,GAAG,CAAC,EAAE,oBAAoB,CAAC;IAC3B;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,IAAI,CAAC;IACd;;;OAGG;IACH,IAAI,EAAE,WAAW,GAAG,SAAS,CAAC;IAC9B,6DAA6D;IAC7D,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,gCAAgC;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,iCAAiC;IACjC,MAAM,EAAE,uBAAuB,CAAC,QAAQ,CAAC,CAAC;IAC1C,8DAA8D;IAC9D,yBAAyB,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7C,+CAA+C;IAC/C,0BAA0B,EAAE,MAAM,CAAC;IACnC,8DAA8D;IAC9D,uBAAuB,EAAE,OAAO,0BAA0B,CAAC;CAC5D;AA+ID;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,KAAK,EAAE,uBAAuB,GAC7B,OAAO,CAAC,wBAAwB,CAAC,CAuDnC"}
1
+ {"version":3,"file":"provider-sync-runner.d.ts","sourceRoot":"","sources":["../../../../src/services/provider-sync-runner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAKH,OAAO,EACL,0BAA0B,EAI3B,MAAM,+BAA+B,CAAC;AACvC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAapD,MAAM,WAAW,uBAAuB;IACtC,kDAAkD;IAClD,QAAQ,EAAE,MAAM,CAAC;IACjB,oDAAoD;IACpD,MAAM,EAAE,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,GAAG,OAAO,CAAC;IAC5D,0CAA0C;IAC1C,GAAG,CAAC,EAAE,oBAAoB,CAAC;IAC3B;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,IAAI,CAAC;IACd;;;OAGG;IACH,IAAI,EAAE,WAAW,GAAG,SAAS,CAAC;IAC9B,6DAA6D;IAC7D,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,gCAAgC;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,iCAAiC;IACjC,MAAM,EAAE,uBAAuB,CAAC,QAAQ,CAAC,CAAC;IAC1C,8DAA8D;IAC9D,yBAAyB,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7C,+CAA+C;IAC/C,0BAA0B,EAAE,MAAM,CAAC;IACnC,8DAA8D;IAC9D,uBAAuB,EAAE,OAAO,0BAA0B,CAAC;CAC5D;AAkJD;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,KAAK,EAAE,uBAAuB,GAC7B,OAAO,CAAC,wBAAwB,CAAC,CAuDnC"}
@@ -33,7 +33,16 @@ import { spawn } from 'node:child_process';
33
33
  import { promises as fs } from 'node:fs';
34
34
  import * as path from 'node:path';
35
35
  import { PROVIDER_LOCK_SECTION_PATH, PROVIDER_RUNTIME_ARTIFACT_RELATIVE_PATHS, PROVIDER_RUNTIME_GENERATED_MODULE_RELATIVE_PATH, RegistryProviderSyncFailedError, } from '@wildo-ai/platform-config-lib';
36
- const DEFAULT_TIMEOUT_MS = 120_000;
36
+ /**
37
+ * How long the post-mutation `wildo config sync` may run before it is treated as wedged and killed.
38
+ *
39
+ * Five minutes, not two: the budget exists to stop a WEDGED sync, and a wall clock cannot tell wedged
40
+ * from slow. Measured 2026-09-27 on a scaffolded application (#1765): the same sync took 14.6 s on a
41
+ * quiet machine, 91 s while the framework's watchers were recompiling, and exceeded the old 120 s during
42
+ * an install under that load — so a healthy sync was killed part-way and the install reported a provider
43
+ * sync failure it did not have. The kill stays: a genuinely stuck sync still ends, five minutes on.
44
+ */
45
+ const DEFAULT_TIMEOUT_MS = 300_000;
37
46
  /**
38
47
  * Provider artifact paths sourced from the canonical
39
48
  * `PROVIDER_RUNTIME_ARTIFACT_RELATIVE_PATHS` constant in
@@ -103,7 +112,10 @@ function runWildoConfigSync(binPath, cwd, timeoutMs) {
103
112
  const startedAt = Date.now();
104
113
  const child = spawn(binPath, ['config', 'sync', '--domain', 'config'], {
105
114
  cwd,
106
- stdio: ['ignore', 'pipe', 'pipe'],
115
+ // stdout is IGNORED, not piped: nothing here reads it, and an unread pipe fills at the OS buffer
116
+ // (~64 KB) and blocks the child mid-write — a finished sync then looked like a wedged one and
117
+ // was killed at the timeout. stderr stays piped because the diagnostic reads it (bounded below).
118
+ stdio: ['ignore', 'ignore', 'pipe'],
107
119
  });
108
120
  let stderr = '';
109
121
  let signaled = false;
@@ -1 +1 @@
1
- {"version":3,"file":"provider-sync-runner.js","sourceRoot":"","sources":["../../../../src/services/provider-sync-runner.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `runProviderSync` — Phase 2 real shell-out to\n * `wildo config sync --domain config`.\n *\n * Module-registry install/update/remove/adopt flows mutate app\n * source. When that source changes provider implementations,\n * provider companion exports, or provider scopes, the\n * provider-runtime delivery layer must regenerate its outputs\n * (KD-18). The canonical command is owned by the provider\n * workstream — module-registry MUST delegate, not fork.\n *\n * Phase 0.5 / 1 shipped a no-op `runProviderMaterializationHook`\n * that recorded intent. Phase 2 replaces it with a real\n * `child_process.spawn`-based runner that:\n * - resolves the `wildo` CLI binary from the saas root's\n * `node_modules/.bin/wildo` (the workspace's own version);\n * - shells `wildo config sync --domain config` in the saas root;\n * - times out at the operator-configurable budget;\n * - classifies stderr-tail patterns into the\n * `RegistryProviderSyncFailedError.failureKind` discriminator\n * (`companion-discovery` / `runtime-import-reachability` /\n * `invalid-scope` / `unknown`) so callers report repairable\n * failures with first-class UX rather than generic install\n * noise (`module-registry-analysis.md` / Provider Migration\n * WIP Seams §1).\n *\n * Hard contract (KD-18): the runner NEVER writes provider runtime\n * artifacts itself. It only invokes the provider workstream's\n * canonical command. Tests assert no provider artifact is touched\n * by this code path.\n */\n\nimport { spawn } from 'node:child_process';\nimport { promises as fs } from 'node:fs';\nimport * as path from 'node:path';\nimport {\n PROVIDER_LOCK_SECTION_PATH,\n PROVIDER_RUNTIME_ARTIFACT_RELATIVE_PATHS,\n PROVIDER_RUNTIME_GENERATED_MODULE_RELATIVE_PATH,\n RegistryProviderSyncFailedError,\n} from '@wildo-ai/platform-config-lib';\nimport type { RegistryClientLogger } from './types';\n\nconst DEFAULT_TIMEOUT_MS = 120_000;\n\nexport interface ProviderSyncRunnerInput {\n /** Absolute path to the SaaS application root. */\n saasRoot: string;\n /** Reason for the call — drives diagnostic copy. */\n reason: 'install' | 'update' | 'remove' | 'adopt' | 'smoke';\n /** Optional logger. Defaults to no-op. */\n log?: RegistryClientLogger;\n /**\n * Override the wildo CLI binary path. Defaults to\n * `<saasRoot>/node_modules/.bin/wildo`. Phase 2 tests override\n * this to point at a stub script.\n */\n wildoBinPath?: string;\n /**\n * Maximum wall-time before SIGTERM. Default 120s — provider sync\n * can be heavy on first run (artifact generation + cleanup),\n * but every additional second is operator-visible.\n */\n timeoutMs?: number;\n /**\n * If true, do NOT actually shell out — log the intent and\n * resolve as `mode: 'dry-run'`. Used by the Phase 0.5 smoke\n * command which intentionally cannot rely on a working\n * `wildo` binary in the smoke sandbox.\n */\n dryRun?: boolean;\n}\n\nexport interface ProviderSyncRunnerResult {\n invoked: true;\n /**\n * `'shell-out'` when the runner spawned `wildo config sync`.\n * `'dry-run'` when `dryRun: true` was passed (smoke flow).\n */\n mode: 'shell-out' | 'dry-run';\n /** Exit code reported by the wildo binary (0 on success). */\n exitCode: number | null;\n /** Wall-clock duration (ms). */\n durationMs: number;\n /** Reason that was passed in. */\n reason: ProviderSyncRunnerInput['reason'];\n /** Provider-output paths the canonical sync would refresh. */\n providerJsonRelativePaths: readonly string[];\n /** Source module the Node-runtime consumes. */\n providerSourceRelativePath: string;\n /** Provider lock section the materializer owns end-to-end. */\n providerLockSectionPath: typeof PROVIDER_LOCK_SECTION_PATH;\n}\n\n/**\n * Provider artifact paths sourced from the canonical\n * `PROVIDER_RUNTIME_ARTIFACT_RELATIVE_PATHS` constant in\n * `platform-config-lib`. Refactored on 2026-05-06 to import the\n * single source of truth instead of duplicating the literal paths\n * here — the artifacts moved from `.wildo-saas/providers/` to\n * `.wildo-saas/generated/providers/` and any duplication would\n * silently drift.\n */\nconst PROVIDER_JSON_RELATIVE_PATHS = Object.values(\n PROVIDER_RUNTIME_ARTIFACT_RELATIVE_PATHS,\n) as readonly string[];\n\nconst PROVIDER_SOURCE_RELATIVE_PATH = PROVIDER_RUNTIME_GENERATED_MODULE_RELATIVE_PATH;\n\n/**\n * Classify a `wildo config sync` failure based on its stderr tail.\n * The matchers mirror the failure modes the provider workstream\n * surfaces today; new modes get a new branch + a new\n * discriminator value (out of `RegistryProviderSyncFailedError`).\n */\nfunction classifyStderr(\n stderr: string,\n): 'companion-discovery' | 'runtime-import-reachability' | 'invalid-scope' | 'unknown' {\n const tail = stderr.slice(-2000);\n // Companion-export discovery failure — the provider sync walks\n // each owner package's `./companion` export to discover provider\n // contributions. A package whose export contract is broken\n // surfaces a \"companion export\" / \"DiscoveredProviderCompanionContribution\"\n // error.\n if (\n /companion[- ]export|DiscoveredProviderCompanionContribution|companion-discovery/i.test(tail)\n ) {\n return 'companion-discovery';\n }\n // Runtime-import reachability — the materializer can't resolve a\n // contributed `runtimeImport` against the owning runtime package\n // graph. Typical messages mention `runtimeImport`, `Cannot find\n // module`, or `MODULE_NOT_FOUND`.\n if (\n /runtimeImport|MODULE_NOT_FOUND|Cannot find module|cannot resolve/i.test(tail)\n ) {\n return 'runtime-import-reachability';\n }\n // Scope validation — `defineSaaSProviders({ scopes })` rejects\n // malformed scope inputs ahead of materialization.\n if (\n /defineSaaSProviders|invalid[- ]scope|providers\\.scopes/i.test(tail)\n ) {\n return 'invalid-scope';\n }\n return 'unknown';\n}\n\nasync function resolveWildoBin(input: ProviderSyncRunnerInput): Promise<string> {\n if (input.wildoBinPath) return input.wildoBinPath;\n const candidate = path.join(input.saasRoot, 'node_modules', '.bin', 'wildo');\n try {\n await fs.access(candidate);\n return candidate;\n } catch {\n // Fall back to PATH-resolved `wildo`. spawn will raise ENOENT\n // if it's not on PATH either, surfaced as\n // `RegistryProviderSyncFailedError(failureKind: 'unknown')`.\n return 'wildo';\n }\n}\n\n/**\n * Grace window between SIGTERM and SIGKILL. A well-behaved\n * `wildo config sync` should react to SIGTERM in well under a\n * second — the grace exists for tools that need to flush a buffer\n * or release a file lock. After the window we escalate to SIGKILL\n * so a hung child cannot pin the parent forever.\n */\nconst SIGKILL_GRACE_MS = 5_000;\n\nfunction runWildoConfigSync(\n binPath: string,\n cwd: string,\n timeoutMs: number,\n): Promise<{ exitCode: number | null; stderr: string; durationMs: number; signaled: boolean }> {\n return new Promise((resolve) => {\n const startedAt = Date.now();\n const child = spawn(binPath, ['config', 'sync', '--domain', 'config'], {\n cwd,\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n let stderr = '';\n let signaled = false;\n let killTimer: NodeJS.Timeout | undefined;\n child.stderr?.on('data', (b: Buffer) => {\n // Cap captured stderr so a runaway sync can't OOM the parent.\n stderr += b.toString();\n if (stderr.length > 16_384) stderr = stderr.slice(-16_384);\n });\n const termTimer = setTimeout(() => {\n // Soft-kill first; record so the caller can synthesize a\n // diagnostic message that distinguishes a TERM-induced exit\n // from a normal one.\n signaled = true;\n stderr += `\\n[provider-sync] timeout ${timeoutMs}ms exceeded — sent SIGTERM.`;\n child.kill('SIGTERM');\n // Escalate to SIGKILL if the child doesn't react within the\n // grace window. Without this a child stuck in a syscall (or\n // deliberately ignoring SIGTERM) would leave the parent\n // blocked on the spawn promise forever.\n killTimer = setTimeout(() => {\n if (child.exitCode === null && child.signalCode === null) {\n stderr += `\\n[provider-sync] SIGTERM grace ${SIGKILL_GRACE_MS}ms exceeded — escalating to SIGKILL.`;\n try {\n child.kill('SIGKILL');\n } catch {\n /* swallow — best-effort escalation */\n }\n }\n }, SIGKILL_GRACE_MS);\n }, timeoutMs);\n child.on('exit', (code) => {\n clearTimeout(termTimer);\n if (killTimer) clearTimeout(killTimer);\n resolve({\n exitCode: code,\n stderr,\n durationMs: Date.now() - startedAt,\n signaled,\n });\n });\n child.on('error', (err) => {\n clearTimeout(termTimer);\n if (killTimer) clearTimeout(killTimer);\n resolve({\n exitCode: null,\n stderr: stderr + `\\n[spawn error] ${err.message}`,\n durationMs: Date.now() - startedAt,\n signaled,\n });\n });\n });\n}\n\n/**\n * Run the canonical provider-sync command. Throws\n * `RegistryProviderSyncFailedError` (with classified `failureKind`)\n * when the spawned process exits non-zero or fails to start.\n */\nexport async function runProviderSync(\n input: ProviderSyncRunnerInput,\n): Promise<ProviderSyncRunnerResult> {\n const log = input.log;\n const timeoutMs = input.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const baseResult = {\n invoked: true as const,\n reason: input.reason,\n providerJsonRelativePaths: PROVIDER_JSON_RELATIVE_PATHS,\n providerSourceRelativePath: PROVIDER_SOURCE_RELATIVE_PATH,\n providerLockSectionPath: PROVIDER_LOCK_SECTION_PATH,\n };\n\n if (input.dryRun) {\n log?.info?.(\n `[registry] post-${input.reason} provider sync (dry-run): would shell ` +\n `\\`wildo config sync --domain config\\` in ${input.saasRoot}.`,\n );\n return { ...baseResult, mode: 'dry-run', exitCode: 0, durationMs: 0 };\n }\n\n const binPath = await resolveWildoBin(input);\n log?.info?.(\n `[registry] post-${input.reason} provider sync: spawning ` +\n `${binPath} config sync --domain config (cwd=${input.saasRoot}, timeout=${timeoutMs}ms)`,\n );\n const { exitCode, stderr, durationMs, signaled } = await runWildoConfigSync(\n binPath,\n input.saasRoot,\n timeoutMs,\n );\n\n if (exitCode === 0) {\n log?.success?.(`[registry] provider sync succeeded in ${durationMs}ms.`);\n return { ...baseResult, mode: 'shell-out', exitCode, durationMs };\n }\n\n // H5: distinguish a timeout-signaled exit from a generic non-zero\n // exit. `classifyStderr` runs regex against the appended\n // `[provider-sync] timeout ... SIGTERM/SIGKILL` lines but those\n // tails match no failure-mode regex — so prior to this branch\n // operators saw `failureKind: 'unknown'` for a hard timeout. The\n // typed error now carries the discriminator + a hint to either\n // raise the timeout or investigate the wedge.\n const failureKind = signaled ? ('timeout' as const) : classifyStderr(stderr);\n log?.error?.(\n `[registry] provider sync failed (${failureKind}, exit=${exitCode}, ${durationMs}ms).`,\n );\n const messageSuffix = failureKind === 'timeout'\n ? `Wall-clock exceeded ${timeoutMs}ms; child was sent SIGTERM (then SIGKILL after grace). ` +\n 'Either raise `timeoutMs` for legitimately heavy syncs or investigate why the sync wedged.'\n : 'Run `wildo config sync --domain config` manually to inspect.';\n throw new RegistryProviderSyncFailedError(\n `Post-${input.reason} provider sync failed (${failureKind}). ${messageSuffix}`,\n failureKind,\n stderr.slice(-1024),\n );\n}\n"]}
1
+ {"version":3,"file":"provider-sync-runner.js","sourceRoot":"","sources":["../../../../src/services/provider-sync-runner.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `runProviderSync` — Phase 2 real shell-out to\n * `wildo config sync --domain config`.\n *\n * Module-registry install/update/remove/adopt flows mutate app\n * source. When that source changes provider implementations,\n * provider companion exports, or provider scopes, the\n * provider-runtime delivery layer must regenerate its outputs\n * (KD-18). The canonical command is owned by the provider\n * workstream — module-registry MUST delegate, not fork.\n *\n * Phase 0.5 / 1 shipped a no-op `runProviderMaterializationHook`\n * that recorded intent. Phase 2 replaces it with a real\n * `child_process.spawn`-based runner that:\n * - resolves the `wildo` CLI binary from the saas root's\n * `node_modules/.bin/wildo` (the workspace's own version);\n * - shells `wildo config sync --domain config` in the saas root;\n * - times out at the operator-configurable budget;\n * - classifies stderr-tail patterns into the\n * `RegistryProviderSyncFailedError.failureKind` discriminator\n * (`companion-discovery` / `runtime-import-reachability` /\n * `invalid-scope` / `unknown`) so callers report repairable\n * failures with first-class UX rather than generic install\n * noise (`module-registry-analysis.md` / Provider Migration\n * WIP Seams §1).\n *\n * Hard contract (KD-18): the runner NEVER writes provider runtime\n * artifacts itself. It only invokes the provider workstream's\n * canonical command. Tests assert no provider artifact is touched\n * by this code path.\n */\n\nimport { spawn } from 'node:child_process';\nimport { promises as fs } from 'node:fs';\nimport * as path from 'node:path';\nimport {\n PROVIDER_LOCK_SECTION_PATH,\n PROVIDER_RUNTIME_ARTIFACT_RELATIVE_PATHS,\n PROVIDER_RUNTIME_GENERATED_MODULE_RELATIVE_PATH,\n RegistryProviderSyncFailedError,\n} from '@wildo-ai/platform-config-lib';\nimport type { RegistryClientLogger } from './types';\n\n/**\n * How long the post-mutation `wildo config sync` may run before it is treated as wedged and killed.\n *\n * Five minutes, not two: the budget exists to stop a WEDGED sync, and a wall clock cannot tell wedged\n * from slow. Measured 2026-09-27 on a scaffolded application (#1765): the same sync took 14.6 s on a\n * quiet machine, 91 s while the framework's watchers were recompiling, and exceeded the old 120 s during\n * an install under that load — so a healthy sync was killed part-way and the install reported a provider\n * sync failure it did not have. The kill stays: a genuinely stuck sync still ends, five minutes on.\n */\nconst DEFAULT_TIMEOUT_MS = 300_000;\n\nexport interface ProviderSyncRunnerInput {\n /** Absolute path to the SaaS application root. */\n saasRoot: string;\n /** Reason for the call — drives diagnostic copy. */\n reason: 'install' | 'update' | 'remove' | 'adopt' | 'smoke';\n /** Optional logger. Defaults to no-op. */\n log?: RegistryClientLogger;\n /**\n * Override the wildo CLI binary path. Defaults to\n * `<saasRoot>/node_modules/.bin/wildo`. Phase 2 tests override\n * this to point at a stub script.\n */\n wildoBinPath?: string;\n /**\n * Maximum wall-time before SIGTERM. Default {@link DEFAULT_TIMEOUT_MS} (five minutes): it only ever\n * elapses on a wedged sync, and a healthy one under load has been measured past two minutes.\n */\n timeoutMs?: number;\n /**\n * If true, do NOT actually shell out — log the intent and\n * resolve as `mode: 'dry-run'`. Used by the Phase 0.5 smoke\n * command which intentionally cannot rely on a working\n * `wildo` binary in the smoke sandbox.\n */\n dryRun?: boolean;\n}\n\nexport interface ProviderSyncRunnerResult {\n invoked: true;\n /**\n * `'shell-out'` when the runner spawned `wildo config sync`.\n * `'dry-run'` when `dryRun: true` was passed (smoke flow).\n */\n mode: 'shell-out' | 'dry-run';\n /** Exit code reported by the wildo binary (0 on success). */\n exitCode: number | null;\n /** Wall-clock duration (ms). */\n durationMs: number;\n /** Reason that was passed in. */\n reason: ProviderSyncRunnerInput['reason'];\n /** Provider-output paths the canonical sync would refresh. */\n providerJsonRelativePaths: readonly string[];\n /** Source module the Node-runtime consumes. */\n providerSourceRelativePath: string;\n /** Provider lock section the materializer owns end-to-end. */\n providerLockSectionPath: typeof PROVIDER_LOCK_SECTION_PATH;\n}\n\n/**\n * Provider artifact paths sourced from the canonical\n * `PROVIDER_RUNTIME_ARTIFACT_RELATIVE_PATHS` constant in\n * `platform-config-lib`. Refactored on 2026-05-06 to import the\n * single source of truth instead of duplicating the literal paths\n * here — the artifacts moved from `.wildo-saas/providers/` to\n * `.wildo-saas/generated/providers/` and any duplication would\n * silently drift.\n */\nconst PROVIDER_JSON_RELATIVE_PATHS = Object.values(\n PROVIDER_RUNTIME_ARTIFACT_RELATIVE_PATHS,\n) as readonly string[];\n\nconst PROVIDER_SOURCE_RELATIVE_PATH = PROVIDER_RUNTIME_GENERATED_MODULE_RELATIVE_PATH;\n\n/**\n * Classify a `wildo config sync` failure based on its stderr tail.\n * The matchers mirror the failure modes the provider workstream\n * surfaces today; new modes get a new branch + a new\n * discriminator value (out of `RegistryProviderSyncFailedError`).\n */\nfunction classifyStderr(\n stderr: string,\n): 'companion-discovery' | 'runtime-import-reachability' | 'invalid-scope' | 'unknown' {\n const tail = stderr.slice(-2000);\n // Companion-export discovery failure — the provider sync walks\n // each owner package's `./companion` export to discover provider\n // contributions. A package whose export contract is broken\n // surfaces a \"companion export\" / \"DiscoveredProviderCompanionContribution\"\n // error.\n if (\n /companion[- ]export|DiscoveredProviderCompanionContribution|companion-discovery/i.test(tail)\n ) {\n return 'companion-discovery';\n }\n // Runtime-import reachability — the materializer can't resolve a\n // contributed `runtimeImport` against the owning runtime package\n // graph. Typical messages mention `runtimeImport`, `Cannot find\n // module`, or `MODULE_NOT_FOUND`.\n if (\n /runtimeImport|MODULE_NOT_FOUND|Cannot find module|cannot resolve/i.test(tail)\n ) {\n return 'runtime-import-reachability';\n }\n // Scope validation — `defineSaaSProviders({ scopes })` rejects\n // malformed scope inputs ahead of materialization.\n if (\n /defineSaaSProviders|invalid[- ]scope|providers\\.scopes/i.test(tail)\n ) {\n return 'invalid-scope';\n }\n return 'unknown';\n}\n\nasync function resolveWildoBin(input: ProviderSyncRunnerInput): Promise<string> {\n if (input.wildoBinPath) return input.wildoBinPath;\n const candidate = path.join(input.saasRoot, 'node_modules', '.bin', 'wildo');\n try {\n await fs.access(candidate);\n return candidate;\n } catch {\n // Fall back to PATH-resolved `wildo`. spawn will raise ENOENT\n // if it's not on PATH either, surfaced as\n // `RegistryProviderSyncFailedError(failureKind: 'unknown')`.\n return 'wildo';\n }\n}\n\n/**\n * Grace window between SIGTERM and SIGKILL. A well-behaved\n * `wildo config sync` should react to SIGTERM in well under a\n * second — the grace exists for tools that need to flush a buffer\n * or release a file lock. After the window we escalate to SIGKILL\n * so a hung child cannot pin the parent forever.\n */\nconst SIGKILL_GRACE_MS = 5_000;\n\nfunction runWildoConfigSync(\n binPath: string,\n cwd: string,\n timeoutMs: number,\n): Promise<{ exitCode: number | null; stderr: string; durationMs: number; signaled: boolean }> {\n return new Promise((resolve) => {\n const startedAt = Date.now();\n const child = spawn(binPath, ['config', 'sync', '--domain', 'config'], {\n cwd,\n // stdout is IGNORED, not piped: nothing here reads it, and an unread pipe fills at the OS buffer\n // (~64 KB) and blocks the child mid-write — a finished sync then looked like a wedged one and\n // was killed at the timeout. stderr stays piped because the diagnostic reads it (bounded below).\n stdio: ['ignore', 'ignore', 'pipe'],\n });\n let stderr = '';\n let signaled = false;\n let killTimer: NodeJS.Timeout | undefined;\n child.stderr?.on('data', (b: Buffer) => {\n // Cap captured stderr so a runaway sync can't OOM the parent.\n stderr += b.toString();\n if (stderr.length > 16_384) stderr = stderr.slice(-16_384);\n });\n const termTimer = setTimeout(() => {\n // Soft-kill first; record so the caller can synthesize a\n // diagnostic message that distinguishes a TERM-induced exit\n // from a normal one.\n signaled = true;\n stderr += `\\n[provider-sync] timeout ${timeoutMs}ms exceeded — sent SIGTERM.`;\n child.kill('SIGTERM');\n // Escalate to SIGKILL if the child doesn't react within the\n // grace window. Without this a child stuck in a syscall (or\n // deliberately ignoring SIGTERM) would leave the parent\n // blocked on the spawn promise forever.\n killTimer = setTimeout(() => {\n if (child.exitCode === null && child.signalCode === null) {\n stderr += `\\n[provider-sync] SIGTERM grace ${SIGKILL_GRACE_MS}ms exceeded — escalating to SIGKILL.`;\n try {\n child.kill('SIGKILL');\n } catch {\n /* swallow — best-effort escalation */\n }\n }\n }, SIGKILL_GRACE_MS);\n }, timeoutMs);\n child.on('exit', (code) => {\n clearTimeout(termTimer);\n if (killTimer) clearTimeout(killTimer);\n resolve({\n exitCode: code,\n stderr,\n durationMs: Date.now() - startedAt,\n signaled,\n });\n });\n child.on('error', (err) => {\n clearTimeout(termTimer);\n if (killTimer) clearTimeout(killTimer);\n resolve({\n exitCode: null,\n stderr: stderr + `\\n[spawn error] ${err.message}`,\n durationMs: Date.now() - startedAt,\n signaled,\n });\n });\n });\n}\n\n/**\n * Run the canonical provider-sync command. Throws\n * `RegistryProviderSyncFailedError` (with classified `failureKind`)\n * when the spawned process exits non-zero or fails to start.\n */\nexport async function runProviderSync(\n input: ProviderSyncRunnerInput,\n): Promise<ProviderSyncRunnerResult> {\n const log = input.log;\n const timeoutMs = input.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const baseResult = {\n invoked: true as const,\n reason: input.reason,\n providerJsonRelativePaths: PROVIDER_JSON_RELATIVE_PATHS,\n providerSourceRelativePath: PROVIDER_SOURCE_RELATIVE_PATH,\n providerLockSectionPath: PROVIDER_LOCK_SECTION_PATH,\n };\n\n if (input.dryRun) {\n log?.info?.(\n `[registry] post-${input.reason} provider sync (dry-run): would shell ` +\n `\\`wildo config sync --domain config\\` in ${input.saasRoot}.`,\n );\n return { ...baseResult, mode: 'dry-run', exitCode: 0, durationMs: 0 };\n }\n\n const binPath = await resolveWildoBin(input);\n log?.info?.(\n `[registry] post-${input.reason} provider sync: spawning ` +\n `${binPath} config sync --domain config (cwd=${input.saasRoot}, timeout=${timeoutMs}ms)`,\n );\n const { exitCode, stderr, durationMs, signaled } = await runWildoConfigSync(\n binPath,\n input.saasRoot,\n timeoutMs,\n );\n\n if (exitCode === 0) {\n log?.success?.(`[registry] provider sync succeeded in ${durationMs}ms.`);\n return { ...baseResult, mode: 'shell-out', exitCode, durationMs };\n }\n\n // H5: distinguish a timeout-signaled exit from a generic non-zero\n // exit. `classifyStderr` runs regex against the appended\n // `[provider-sync] timeout ... SIGTERM/SIGKILL` lines but those\n // tails match no failure-mode regex — so prior to this branch\n // operators saw `failureKind: 'unknown'` for a hard timeout. The\n // typed error now carries the discriminator + a hint to either\n // raise the timeout or investigate the wedge.\n const failureKind = signaled ? ('timeout' as const) : classifyStderr(stderr);\n log?.error?.(\n `[registry] provider sync failed (${failureKind}, exit=${exitCode}, ${durationMs}ms).`,\n );\n const messageSuffix = failureKind === 'timeout'\n ? `Wall-clock exceeded ${timeoutMs}ms; child was sent SIGTERM (then SIGKILL after grace). ` +\n 'Either raise `timeoutMs` for legitimately heavy syncs or investigate why the sync wedged.'\n : 'Run `wildo config sync --domain config` manually to inspect.';\n throw new RegistryProviderSyncFailedError(\n `Post-${input.reason} provider sync failed (${failureKind}). ${messageSuffix}`,\n failureKind,\n stderr.slice(-1024),\n );\n}\n"]}
@@ -1,20 +1,15 @@
1
1
  /**
2
- * `RegistryCompanionClient` — HTTP client for the local dev
3
- * companion's registry routes.
2
+ * `RegistryCompanionClient` — liveness probe for the local dev companion, used by
3
+ * `wildo registry ping` beside the registry-server probe.
4
4
  *
5
- * Distinct from `RegistryServerClient` (which talks to the central
6
- * `platform-module-registry` server). The companion client targets
7
- * `/api/companion/registry/*` on `http://localhost:<companion-port>`
8
- * for AST/source mutations the CLI delegates to the companion
9
- * (KD-6).
10
- *
11
- * Phase 2 ships the four mutation routes as method calls; the
12
- * companion controller (under
13
- * `platform/factory/wildo-dev-companion/src/companion-api/controllers/`)
14
- * implements them with the actual ts-morph + filesystem work in
15
- * Phase 5+.
5
+ * That is all it is. It used to carry four mutation methods (install / update / remove / publish)
6
+ * against `/api/companion/registry/*` routes that only ever answered a "Phase 5/6 deliverable"
7
+ * not-implemented envelope and had no caller. Installation shipped CLI-direct instead
8
+ * (`wildo registry add` / `update` / `remove` / `publish` do the work themselves), so the routes,
9
+ * these methods and their wire schemas were deleted on 2026-09-25 rather than left describing a
10
+ * design that was never built. A future companion-owned mutation lane starts from the CLI commands,
11
+ * not from this file.
16
12
  */
17
- import { type RegistryInstallRequest, type RegistryPublishRequest, type RegistryRemoveRequest, type RegistryRouteResult, type RegistryUpdateRequest } from '@wildo-ai/platform-config-lib';
18
13
  export interface RegistryCompanionClientOptions {
19
14
  baseUrl: string;
20
15
  timeoutMs?: number;
@@ -24,16 +19,6 @@ export declare const RegistryCompanionClient: {
24
19
  * Liveness probe against `/health` (the same endpoint the
25
20
  * dev-supervisor monitors). Used by `wildo registry ping`.
26
21
  */
27
- readonly ping: (options: RegistryCompanionClientOptions) => Promise<boolean>;
28
- /**
29
- * Ask the companion to install a module: download bytes, copy
30
- * source, mutate `wildo.saas.config.ts > moduleRegistries` +
31
- * the three `modules-registry.*.ts` files, refresh
32
- * `sections.modules`, run provider sync.
33
- */
34
- readonly install: (options: RegistryCompanionClientOptions, request: RegistryInstallRequest) => Promise<RegistryRouteResult>;
35
- readonly update: (options: RegistryCompanionClientOptions, request: RegistryUpdateRequest) => Promise<RegistryRouteResult>;
36
- readonly remove: (options: RegistryCompanionClientOptions, request: RegistryRemoveRequest) => Promise<RegistryRouteResult>;
37
- readonly publish: (options: RegistryCompanionClientOptions, request: RegistryPublishRequest) => Promise<RegistryRouteResult>;
22
+ ping(options: RegistryCompanionClientOptions): Promise<boolean>;
38
23
  };
39
24
  //# sourceMappingURL=registry-companion-client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"registry-companion-client.d.ts","sourceRoot":"","sources":["../../../src/services/registry-companion-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAKL,KAAK,sBAAsB,EAC3B,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,EAC3B,MAAM,+BAA+B,CAAC;AAMvC,MAAM,WAAW,8BAA8B;IAC7C,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA2CD,eAAO,MAAM,uBAAuB;IAClC;;;OAGG;6BACiB,8BAA8B,KAAG,OAAO,CAAC,OAAO,CAAC;IAcrE;;;;;OAKG;gCAEQ,8BAA8B,WAC9B,sBAAsB,KAC9B,OAAO,CAAC,mBAAmB,CAAC;+BAMpB,8BAA8B,WAC9B,qBAAqB,KAC7B,OAAO,CAAC,mBAAmB,CAAC;+BAMpB,8BAA8B,WAC9B,qBAAqB,KAC7B,OAAO,CAAC,mBAAmB,CAAC;gCAMpB,8BAA8B,WAC9B,sBAAsB,KAC9B,OAAO,CAAC,mBAAmB,CAAC;CAIvB,CAAC"}
1
+ {"version":3,"file":"registry-companion-client.d.ts","sourceRoot":"","sources":["../../../../src/services/registry-companion-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAMH,MAAM,WAAW,8BAA8B;IAC7C,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,uBAAuB;IAClC;;;OAGG;kBACiB,8BAA8B,GAAG,OAAO,CAAC,OAAO,CAAC;CAatE,CAAC"}
@@ -1,51 +1,17 @@
1
1
  /**
2
- * `RegistryCompanionClient` — HTTP client for the local dev
3
- * companion's registry routes.
2
+ * `RegistryCompanionClient` — liveness probe for the local dev companion, used by
3
+ * `wildo registry ping` beside the registry-server probe.
4
4
  *
5
- * Distinct from `RegistryServerClient` (which talks to the central
6
- * `platform-module-registry` server). The companion client targets
7
- * `/api/companion/registry/*` on `http://localhost:<companion-port>`
8
- * for AST/source mutations the CLI delegates to the companion
9
- * (KD-6).
10
- *
11
- * Phase 2 ships the four mutation routes as method calls; the
12
- * companion controller (under
13
- * `platform/factory/wildo-dev-companion/src/companion-api/controllers/`)
14
- * implements them with the actual ts-morph + filesystem work in
15
- * Phase 5+.
5
+ * That is all it is. It used to carry four mutation methods (install / update / remove / publish)
6
+ * against `/api/companion/registry/*` routes that only ever answered a "Phase 5/6 deliverable"
7
+ * not-implemented envelope and had no caller. Installation shipped CLI-direct instead
8
+ * (`wildo registry add` / `update` / `remove` / `publish` do the work themselves), so the routes,
9
+ * these methods and their wire schemas were deleted on 2026-09-25 rather than left describing a
10
+ * design that was never built. A future companion-owned mutation lane starts from the CLI commands,
11
+ * not from this file.
16
12
  */
17
- import { REGISTRY_COMPANION_ROUTES, RegistryMalformedPayloadError, RegistryRouteResultSchema, RegistryUnexpectedStatusError, } from '@wildo-ai/platform-config-lib';
18
13
  import { fetchWithTimeout, joinUrl } from './_http-client.js';
19
- import { formatZodIssuesAsSingleLine } from './_zod-issue-formatting.js';
20
14
  const DIAGNOSTIC_LABEL = 'Companion registry';
21
- async function postJson(options, routePath, body) {
22
- const url = joinUrl(options.baseUrl, routePath);
23
- const res = await fetchWithTimeout(url, {
24
- method: 'POST',
25
- headers: { 'content-type': 'application/json' },
26
- body: JSON.stringify(body),
27
- timeoutMs: options.timeoutMs,
28
- diagnosticLabel: DIAGNOSTIC_LABEL,
29
- });
30
- if (!res.ok) {
31
- // 3rd-pass M5: typed `RegistryUnexpectedStatusError` instead of
32
- // bare `new Error(...)` so callers can `isRegistryError(err)` and
33
- // discriminate by status code without re-parsing message text.
34
- // The companion deliberately reuses the same error vocabulary
35
- // as `RegistryServerClient` since the wire shape (status + URL +
36
- // action) is identical even though the lane is different.
37
- throw new RegistryUnexpectedStatusError(options.baseUrl, res.status, res.statusText, `companion ${routePath}`);
38
- }
39
- // 6th-pass M3: typed JSON-parse — a misconfigured proxy returning
40
- // HTML at 2xx would otherwise throw a bare `SyntaxError` that
41
- // bypasses the typed-error contract.
42
- try {
43
- return (await res.json());
44
- }
45
- catch (err) {
46
- throw new RegistryMalformedPayloadError(options.baseUrl, `companion ${routePath}`, `response is not valid JSON: ${err instanceof Error ? err.message : String(err)}`);
47
- }
48
- }
49
15
  export const RegistryCompanionClient = {
50
16
  /**
51
17
  * Liveness probe against `/health` (the same endpoint the
@@ -65,45 +31,5 @@ export const RegistryCompanionClient = {
65
31
  return false;
66
32
  }
67
33
  },
68
- /**
69
- * Ask the companion to install a module: download bytes, copy
70
- * source, mutate `wildo.saas.config.ts > moduleRegistries` +
71
- * the three `modules-registry.*.ts` files, refresh
72
- * `sections.modules`, run provider sync.
73
- */
74
- async install(options, request) {
75
- const raw = await postJson(options, REGISTRY_COMPANION_ROUTES.install, request);
76
- return parseCompanionResult(options.baseUrl, REGISTRY_COMPANION_ROUTES.install, raw);
77
- },
78
- async update(options, request) {
79
- const raw = await postJson(options, REGISTRY_COMPANION_ROUTES.update, request);
80
- return parseCompanionResult(options.baseUrl, REGISTRY_COMPANION_ROUTES.update, raw);
81
- },
82
- async remove(options, request) {
83
- const raw = await postJson(options, REGISTRY_COMPANION_ROUTES.remove, request);
84
- return parseCompanionResult(options.baseUrl, REGISTRY_COMPANION_ROUTES.remove, raw);
85
- },
86
- async publish(options, request) {
87
- const raw = await postJson(options, REGISTRY_COMPANION_ROUTES.publish, request);
88
- return parseCompanionResult(options.baseUrl, REGISTRY_COMPANION_ROUTES.publish, raw);
89
- },
90
34
  };
91
- /**
92
- * M5: thin wrapper around `RegistryRouteResultSchema.parse(raw)` that
93
- * converts a Zod parse failure into the typed
94
- * `RegistryMalformedPayloadError` instead of letting a bare `ZodError`
95
- * bubble up. Keeps the typed-handler contract aligned with
96
- * `RegistryServerClient`.
97
- */
98
- function parseCompanionResult(baseUrl, routePath, raw) {
99
- const parsed = RegistryRouteResultSchema.safeParse(raw);
100
- if (!parsed.success) {
101
- // 7th-pass C-L2: format the issues array for operator
102
- // readability instead of using Zod 4's JSON-stringified
103
- // `error.message`.
104
- const detail = formatZodIssuesAsSingleLine(parsed.error);
105
- throw new RegistryMalformedPayloadError(baseUrl, `companion ${routePath}`, detail);
106
- }
107
- return parsed.data;
108
- }
109
35
  //# sourceMappingURL=registry-companion-client.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"registry-companion-client.js","sourceRoot":"","sources":["../../../../src/services/registry-companion-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EACL,yBAAyB,EACzB,6BAA6B,EAC7B,yBAAyB,EACzB,6BAA6B,GAM9B,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAC3D,OAAO,EAAE,2BAA2B,EAAE,MAAM,yBAAyB,CAAC;AAEtE,MAAM,gBAAgB,GAAG,oBAAoB,CAAC;AAO9C,KAAK,UAAU,QAAQ,CACrB,OAAuC,EACvC,SAAiB,EACjB,IAAa;IAEb,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IAChD,MAAM,GAAG,GAAG,MAAM,gBAAgB,CAAC,GAAG,EAAE;QACtC,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;QAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;QAC1B,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,eAAe,EAAE,gBAAgB;KAClC,CAAC,CAAC;IACH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,gEAAgE;QAChE,kEAAkE;QAClE,+DAA+D;QAC/D,8DAA8D;QAC9D,iEAAiE;QACjE,0DAA0D;QAC1D,MAAM,IAAI,6BAA6B,CACrC,OAAO,CAAC,OAAO,EACf,GAAG,CAAC,MAAM,EACV,GAAG,CAAC,UAAU,EACd,aAAa,SAAS,EAAE,CACzB,CAAC;IACJ,CAAC;IACD,kEAAkE;IAClE,8DAA8D;IAC9D,qCAAqC;IACrC,IAAI,CAAC;QACH,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAM,CAAC;IACjC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,6BAA6B,CACrC,OAAO,CAAC,OAAO,EACf,aAAa,SAAS,EAAE,EACxB,+BAA+B,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAClF,CAAC;IACJ,CAAC;AACH,CAAC;AAED,MAAM,CAAC,MAAM,uBAAuB,GAAG;IACrC;;;OAGG;IACH,KAAK,CAAC,IAAI,CAAC,OAAuC;QAChD,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;QAChD,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,gBAAgB,CAAC,GAAG,EAAE;gBACtC,MAAM,EAAE,KAAK;gBACb,SAAS,EAAE,OAAO,CAAC,SAAS;gBAC5B,eAAe,EAAE,gBAAgB;aAClC,CAAC,CAAC;YACH,OAAO,GAAG,CAAC,EAAE,CAAC;QAChB,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,OAAO,CACX,OAAuC,EACvC,OAA+B;QAE/B,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAU,OAAO,EAAE,yBAAyB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACzF,OAAO,oBAAoB,CAAC,OAAO,CAAC,OAAO,EAAE,yBAAyB,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IACvF,CAAC;IAED,KAAK,CAAC,MAAM,CACV,OAAuC,EACvC,OAA8B;QAE9B,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAU,OAAO,EAAE,yBAAyB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACxF,OAAO,oBAAoB,CAAC,OAAO,CAAC,OAAO,EAAE,yBAAyB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACtF,CAAC;IAED,KAAK,CAAC,MAAM,CACV,OAAuC,EACvC,OAA8B;QAE9B,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAU,OAAO,EAAE,yBAAyB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACxF,OAAO,oBAAoB,CAAC,OAAO,CAAC,OAAO,EAAE,yBAAyB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACtF,CAAC;IAED,KAAK,CAAC,OAAO,CACX,OAAuC,EACvC,OAA+B;QAE/B,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAU,OAAO,EAAE,yBAAyB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACzF,OAAO,oBAAoB,CAAC,OAAO,CAAC,OAAO,EAAE,yBAAyB,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IACvF,CAAC;CACO,CAAC;AAEX;;;;;;GAMG;AACH,SAAS,oBAAoB,CAC3B,OAAe,EACf,SAAiB,EACjB,GAAY;IAEZ,MAAM,MAAM,GAAG,yBAAyB,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IACxD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,sDAAsD;QACtD,wDAAwD;QACxD,mBAAmB;QACnB,MAAM,MAAM,GAAG,2BAA2B,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACzD,MAAM,IAAI,6BAA6B,CACrC,OAAO,EACP,aAAa,SAAS,EAAE,EACxB,MAAM,CACP,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAC;AACrB,CAAC","sourcesContent":["/**\n * `RegistryCompanionClient` — HTTP client for the local dev\n * companion's registry routes.\n *\n * Distinct from `RegistryServerClient` (which talks to the central\n * `platform-module-registry` server). The companion client targets\n * `/api/companion/registry/*` on `http://localhost:<companion-port>`\n * for AST/source mutations the CLI delegates to the companion\n * (KD-6).\n *\n * Phase 2 ships the four mutation routes as method calls; the\n * companion controller (under\n * `platform/factory/wildo-dev-companion/src/companion-api/controllers/`)\n * implements them with the actual ts-morph + filesystem work in\n * Phase 5+.\n */\n\nimport {\n REGISTRY_COMPANION_ROUTES,\n RegistryMalformedPayloadError,\n RegistryRouteResultSchema,\n RegistryUnexpectedStatusError,\n type RegistryInstallRequest,\n type RegistryPublishRequest,\n type RegistryRemoveRequest,\n type RegistryRouteResult,\n type RegistryUpdateRequest,\n} from '@wildo-ai/platform-config-lib';\nimport { fetchWithTimeout, joinUrl } from './_http-client';\nimport { formatZodIssuesAsSingleLine } from './_zod-issue-formatting';\n\nconst DIAGNOSTIC_LABEL = 'Companion registry';\n\nexport interface RegistryCompanionClientOptions {\n baseUrl: string;\n timeoutMs?: number;\n}\n\nasync function postJson<R>(\n options: RegistryCompanionClientOptions,\n routePath: string,\n body: unknown,\n): Promise<R> {\n const url = joinUrl(options.baseUrl, routePath);\n const res = await fetchWithTimeout(url, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n timeoutMs: options.timeoutMs,\n diagnosticLabel: DIAGNOSTIC_LABEL,\n });\n if (!res.ok) {\n // 3rd-pass M5: typed `RegistryUnexpectedStatusError` instead of\n // bare `new Error(...)` so callers can `isRegistryError(err)` and\n // discriminate by status code without re-parsing message text.\n // The companion deliberately reuses the same error vocabulary\n // as `RegistryServerClient` since the wire shape (status + URL +\n // action) is identical even though the lane is different.\n throw new RegistryUnexpectedStatusError(\n options.baseUrl,\n res.status,\n res.statusText,\n `companion ${routePath}`,\n );\n }\n // 6th-pass M3: typed JSON-parse — a misconfigured proxy returning\n // HTML at 2xx would otherwise throw a bare `SyntaxError` that\n // bypasses the typed-error contract.\n try {\n return (await res.json()) as R;\n } catch (err) {\n throw new RegistryMalformedPayloadError(\n options.baseUrl,\n `companion ${routePath}`,\n `response is not valid JSON: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n}\n\nexport const RegistryCompanionClient = {\n /**\n * Liveness probe against `/health` (the same endpoint the\n * dev-supervisor monitors). Used by `wildo registry ping`.\n */\n async ping(options: RegistryCompanionClientOptions): Promise<boolean> {\n const url = joinUrl(options.baseUrl, '/health');\n try {\n const res = await fetchWithTimeout(url, {\n method: 'GET',\n timeoutMs: options.timeoutMs,\n diagnosticLabel: DIAGNOSTIC_LABEL,\n });\n return res.ok;\n } catch {\n return false;\n }\n },\n\n /**\n * Ask the companion to install a module: download bytes, copy\n * source, mutate `wildo.saas.config.ts > moduleRegistries` +\n * the three `modules-registry.*.ts` files, refresh\n * `sections.modules`, run provider sync.\n */\n async install(\n options: RegistryCompanionClientOptions,\n request: RegistryInstallRequest,\n ): Promise<RegistryRouteResult> {\n const raw = await postJson<unknown>(options, REGISTRY_COMPANION_ROUTES.install, request);\n return parseCompanionResult(options.baseUrl, REGISTRY_COMPANION_ROUTES.install, raw);\n },\n\n async update(\n options: RegistryCompanionClientOptions,\n request: RegistryUpdateRequest,\n ): Promise<RegistryRouteResult> {\n const raw = await postJson<unknown>(options, REGISTRY_COMPANION_ROUTES.update, request);\n return parseCompanionResult(options.baseUrl, REGISTRY_COMPANION_ROUTES.update, raw);\n },\n\n async remove(\n options: RegistryCompanionClientOptions,\n request: RegistryRemoveRequest,\n ): Promise<RegistryRouteResult> {\n const raw = await postJson<unknown>(options, REGISTRY_COMPANION_ROUTES.remove, request);\n return parseCompanionResult(options.baseUrl, REGISTRY_COMPANION_ROUTES.remove, raw);\n },\n\n async publish(\n options: RegistryCompanionClientOptions,\n request: RegistryPublishRequest,\n ): Promise<RegistryRouteResult> {\n const raw = await postJson<unknown>(options, REGISTRY_COMPANION_ROUTES.publish, request);\n return parseCompanionResult(options.baseUrl, REGISTRY_COMPANION_ROUTES.publish, raw);\n },\n} as const;\n\n/**\n * M5: thin wrapper around `RegistryRouteResultSchema.parse(raw)` that\n * converts a Zod parse failure into the typed\n * `RegistryMalformedPayloadError` instead of letting a bare `ZodError`\n * bubble up. Keeps the typed-handler contract aligned with\n * `RegistryServerClient`.\n */\nfunction parseCompanionResult(\n baseUrl: string,\n routePath: string,\n raw: unknown,\n): RegistryRouteResult {\n const parsed = RegistryRouteResultSchema.safeParse(raw);\n if (!parsed.success) {\n // 7th-pass C-L2: format the issues array for operator\n // readability instead of using Zod 4's JSON-stringified\n // `error.message`.\n const detail = formatZodIssuesAsSingleLine(parsed.error);\n throw new RegistryMalformedPayloadError(\n baseUrl,\n `companion ${routePath}`,\n detail,\n );\n }\n return parsed.data;\n}\n"]}
1
+ {"version":3,"file":"registry-companion-client.js","sourceRoot":"","sources":["../../../../src/services/registry-companion-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAE3D,MAAM,gBAAgB,GAAG,oBAAoB,CAAC;AAO9C,MAAM,CAAC,MAAM,uBAAuB,GAAG;IACrC;;;OAGG;IACH,KAAK,CAAC,IAAI,CAAC,OAAuC;QAChD,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;QAChD,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,gBAAgB,CAAC,GAAG,EAAE;gBACtC,MAAM,EAAE,KAAK;gBACb,SAAS,EAAE,OAAO,CAAC,SAAS;gBAC5B,eAAe,EAAE,gBAAgB;aAClC,CAAC,CAAC;YACH,OAAO,GAAG,CAAC,EAAE,CAAC;QAChB,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;CACF,CAAC","sourcesContent":["/**\n * `RegistryCompanionClient` — liveness probe for the local dev companion, used by\n * `wildo registry ping` beside the registry-server probe.\n *\n * That is all it is. It used to carry four mutation methods (install / update / remove / publish)\n * against `/api/companion/registry/*` routes that only ever answered a \"Phase 5/6 deliverable\"\n * not-implemented envelope and had no caller. Installation shipped CLI-direct instead\n * (`wildo registry add` / `update` / `remove` / `publish` do the work themselves), so the routes,\n * these methods and their wire schemas were deleted on 2026-09-25 rather than left describing a\n * design that was never built. A future companion-owned mutation lane starts from the CLI commands,\n * not from this file.\n */\n\nimport { fetchWithTimeout, joinUrl } from './_http-client';\n\nconst DIAGNOSTIC_LABEL = 'Companion registry';\n\nexport interface RegistryCompanionClientOptions {\n baseUrl: string;\n timeoutMs?: number;\n}\n\nexport const RegistryCompanionClient = {\n /**\n * Liveness probe against `/health` (the same endpoint the\n * dev-supervisor monitors). Used by `wildo registry ping`.\n */\n async ping(options: RegistryCompanionClientOptions): Promise<boolean> {\n const url = joinUrl(options.baseUrl, '/health');\n try {\n const res = await fetchWithTimeout(url, {\n method: 'GET',\n timeoutMs: options.timeoutMs,\n diagnosticLabel: DIAGNOSTIC_LABEL,\n });\n return res.ok;\n } catch {\n return false;\n }\n },\n};\n"]}
@@ -19,7 +19,7 @@
19
19
  * those are produced by the provider materialization workflow.
20
20
  * - Never mutates `sections.providers.runtimeScopes`.
21
21
  */
22
- import { type RegistryModulesLockSection, type RegistryModulesLockEntry } from '@wildo-ai/platform-config-lib';
22
+ import { type RegistryModulesLockSection, type RegistryModulesLockEntryInput } from '@wildo-ai/platform-config-lib';
23
23
  export declare const RegistryLockfileService: {
24
24
  /**
25
25
  * Read the current `sections.modules` payload (or an empty section
@@ -35,8 +35,12 @@ export declare const RegistryLockfileService: {
35
35
  * before commit.
36
36
  */
37
37
  readonly mutate: (saasRoot: string, mutator: (current: RegistryModulesLockSection) => RegistryModulesLockSection | void | Promise<RegistryModulesLockSection | void>) => Promise<RegistryModulesLockSection>;
38
- /** Convenience helper used by Phase 5+ install flow. */
39
- readonly upsertEntry: (saasRoot: string, entry: RegistryModulesLockEntry) => Promise<void>;
38
+ /**
39
+ * Insert or replace one module's entry. The entry is parsed here, so its defaults (`distribution`,
40
+ * `files`) apply in one place and a malformed entry is refused naming the module, before the lockfile
41
+ * is touched (`mutate` then re-validates the whole section).
42
+ */
43
+ readonly upsertEntry: (saasRoot: string, input: RegistryModulesLockEntryInput) => Promise<void>;
40
44
  /**
41
45
  * Find every other lockfile entry whose `files[]` claims any of the
42
46
  * given paths. Used by `update`/`remove` to skip deletes that would
@@ -1 +1 @@
1
- {"version":3,"file":"registry-lockfile.service.d.ts","sourceRoot":"","sources":["../../../src/services/registry-lockfile.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAIL,KAAK,0BAA0B,EAC/B,KAAK,wBAAwB,EAC9B,MAAM,+BAA+B,CAAC;AAevC,eAAO,MAAM,uBAAuB;IAClC;;;;OAIG;8BACkB,MAAM,KAAG,OAAO,CAAC,0BAA0B,CAAC;IAOjE;;;;;;OAMG;gCAES,MAAM,WACP,CACP,OAAO,EAAE,0BAA0B,KAEjC,0BAA0B,GAC1B,IAAI,GACJ,OAAO,CAAC,0BAA0B,GAAG,IAAI,CAAC,KAC7C,OAAO,CAAC,0BAA0B,CAAC;IAYtC,wDAAwD;qCAC5B,MAAM,SAAS,wBAAwB,KAAG,OAAO,CAAC,IAAI,CAAC;IAOnF;;;;;;;;;OASG;oCACwB;QACzB,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;QACzB,eAAe,EAAE,MAAM,CAAC;KACzB,KAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;CAgB1B,CAAC"}
1
+ {"version":3,"file":"registry-lockfile.service.d.ts","sourceRoot":"","sources":["../../../../src/services/registry-lockfile.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAIL,KAAK,0BAA0B,EAE/B,KAAK,6BAA6B,EACnC,MAAM,+BAA+B,CAAC;AAevC,eAAO,MAAM,uBAAuB;IAClC;;;;OAIG;8BACkB,MAAM,KAAG,OAAO,CAAC,0BAA0B,CAAC;IAOjE;;;;;;OAMG;gCAES,MAAM,WACP,CACP,OAAO,EAAE,0BAA0B,KAEjC,0BAA0B,GAC1B,IAAI,GACJ,OAAO,CAAC,0BAA0B,GAAG,IAAI,CAAC,KAC7C,OAAO,CAAC,0BAA0B,CAAC;IAYtC;;;;OAIG;qCACyB,MAAM,SAAS,6BAA6B,KAAG,OAAO,CAAC,IAAI,CAAC;IAQxF;;;;;;;;;OASG;oCACwB;QACzB,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;QACzB,eAAe,EAAE,MAAM,CAAC;KACzB,KAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;CAgB1B,CAAC"}
@@ -19,7 +19,7 @@
19
19
  * those are produced by the provider materialization workflow.
20
20
  * - Never mutates `sections.providers.runtimeScopes`.
21
21
  */
22
- import { RegistryModulesLockSectionSchema, withLockfileSectionMutate, readWildoSaasLock, } from '@wildo-ai/platform-config-lib';
22
+ import { RegistryModulesLockSectionSchema, withLockfileSectionMutate, readWildoSaasLock, RegistryModulesLockEntrySchema, } from '@wildo-ai/platform-config-lib';
23
23
  // L6 (third-pass review): the package's barrel-discipline rule says
24
24
  // "Never re-export (except in the root `index.ts` of each package)."
25
25
  // The previous `export { WILDO_SAAS_LOCK_RELATIVE_PATH }` here AND in
@@ -59,8 +59,13 @@ export const RegistryLockfileService = {
59
59
  initialSection: emptySection,
60
60
  }, mutator);
61
61
  },
62
- /** Convenience helper used by Phase 5+ install flow. */
63
- async upsertEntry(saasRoot, entry) {
62
+ /**
63
+ * Insert or replace one module's entry. The entry is parsed here, so its defaults (`distribution`,
64
+ * `files`) apply in one place and a malformed entry is refused naming the module, before the lockfile
65
+ * is touched (`mutate` then re-validates the whole section).
66
+ */
67
+ async upsertEntry(saasRoot, input) {
68
+ const entry = RegistryModulesLockEntrySchema.parse(input);
64
69
  await this.mutate(saasRoot, (current) => ({
65
70
  ...current,
66
71
  installed: { ...current.installed, [entry.moduleId]: entry },
@@ -1 +1 @@
1
- {"version":3,"file":"registry-lockfile.service.js","sourceRoot":"","sources":["../../../../src/services/registry-lockfile.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EACL,gCAAgC,EAChC,yBAAyB,EACzB,iBAAiB,GAGlB,MAAM,+BAA+B,CAAC;AACvC,oEAAoE;AACpE,qEAAqE;AACrE,sEAAsE;AACtE,8DAA8D;AAC9D,kEAAkE;AAClE,qEAAqE;AACrE,mCAAmC;AAEnC,MAAM,WAAW,GAAG,SAAkB,CAAC;AAEvC,SAAS,YAAY;IACnB,OAAO,gCAAgC,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;AACpD,CAAC;AAED,MAAM,CAAC,MAAM,uBAAuB,GAAG;IACrC;;;;OAIG;IACH,KAAK,CAAC,IAAI,CAAC,QAAgB;QACzB,MAAM,QAAQ,GAAG,MAAM,iBAAiB,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC;QACvD,MAAM,GAAG,GAAG,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC;QACtC,IAAI,GAAG,KAAK,SAAS;YAAE,OAAO,YAAY,EAAE,CAAC;QAC7C,OAAO,gCAAgC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACrD,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,MAAM,CACV,QAAgB,EAChB,OAK8C;QAE9C,OAAO,yBAAyB,CAC9B;YACE,QAAQ;YACR,UAAU,EAAE,WAAW;YACvB,aAAa,EAAE,gCAAgC;YAC/C,cAAc,EAAE,YAAY;SAC7B,EACD,OAAO,CACR,CAAC;IACJ,CAAC;IAED,wDAAwD;IACxD,KAAK,CAAC,WAAW,CAAC,QAAgB,EAAE,KAA+B;QACjE,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;YACxC,GAAG,OAAO;YACV,SAAS,EAAE,EAAE,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,KAAK,EAAE;SAC7D,CAAC,CAAC,CAAC;IACN,CAAC;IAED;;;;;;;;;OASG;IACH,KAAK,CAAC,cAAc,CAAC,IAIpB;QACC,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC/C,MAAM,SAAS,GAAG,IAAI,GAAG,EAAoB,CAAC;QAC9C,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,KAAK;YAAE,SAAS,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACjD,KAAK,MAAM,aAAa,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;YAC3D,IAAI,aAAa,KAAK,IAAI,CAAC,eAAe;gBAAE,SAAS;YACrD,MAAM,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC;YACpD,IAAI,CAAC,UAAU;gBAAE,SAAS;YAC1B,KAAK,MAAM,SAAS,IAAI,UAAU,CAAC,KAAK,EAAE,CAAC;gBACzC,IAAI,SAAS,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;oBAC7B,SAAS,CAAC,GAAG,CAAC,SAAS,CAAE,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;gBAChD,CAAC;YACH,CAAC;QACH,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;CACO,CAAC","sourcesContent":["/**\n * `RegistryLockfileService` — section-scoped reader/writer for the\n * `sections.modules` slice of `wildo-saas.lock.json` (KD-4).\n *\n * Phase 1 graduates the locking + atomic-write primitives into\n * `@wildo-ai/platform-config-lib` (validated by the Phase 0.B\n * contention spike). This service is a thin wrapper around\n * `withLockfileSectionMutate` that pre-binds the `modules` section\n * key + schema, so module-registry callers cannot accidentally\n * touch sibling sections.\n *\n * Hard contracts (KD-4 / KD-15 / KD-18):\n * - Mutates ONLY `sections.modules`. The shared library preserves\n * `sections.providers`, `sections.i18n`, and every unknown\n * `sections.*` payload verbatim — no defensive logic needed\n * here.\n * - Never writes `.wildo-saas/generated/providers/*.runtime-providers.generated.json`\n * or `src/generated/wildo-provider-runtime.generated.ts` —\n * those are produced by the provider materialization workflow.\n * - Never mutates `sections.providers.runtimeScopes`.\n */\n\nimport {\n RegistryModulesLockSectionSchema,\n withLockfileSectionMutate,\n readWildoSaasLock,\n type RegistryModulesLockSection,\n type RegistryModulesLockEntry,\n} from '@wildo-ai/platform-config-lib';\n// L6 (third-pass review): the package's barrel-discipline rule says\n// \"Never re-export (except in the root `index.ts` of each package).\"\n// The previous `export { WILDO_SAAS_LOCK_RELATIVE_PATH }` here AND in\n// `lockfile-repair.service.ts` surfaced the symbol twice from\n// `wildo-module-client`'s root. Consumers that need it can import\n// directly from `@wildo-ai/platform-config-lib`, OR rely on the root\n// `index.ts` to re-export it once.\n\nconst SECTION_KEY = 'modules' as const;\n\nfunction emptySection(): RegistryModulesLockSection {\n return RegistryModulesLockSectionSchema.parse({});\n}\n\nexport const RegistryLockfileService = {\n /**\n * Read the current `sections.modules` payload (or an empty section\n * when missing). Unsynchronized — callers needing read-after-write\n * coherence should read INSIDE `mutate`.\n */\n async read(saasRoot: string): Promise<RegistryModulesLockSection> {\n const envelope = await readWildoSaasLock({ saasRoot });\n const raw = envelope.sections.modules;\n if (raw === undefined) return emptySection();\n return RegistryModulesLockSectionSchema.parse(raw);\n },\n\n /**\n * Run `mutator` against the current `sections.modules` payload\n * under an OS-level lock + atomic-write commit. The mutator can\n * either mutate-in-place or return a new value; either way the\n * result is re-validated against `RegistryModulesLockSectionSchema`\n * before commit.\n */\n async mutate(\n saasRoot: string,\n mutator: (\n current: RegistryModulesLockSection,\n ) =>\n | RegistryModulesLockSection\n | void\n | Promise<RegistryModulesLockSection | void>,\n ): Promise<RegistryModulesLockSection> {\n return withLockfileSectionMutate(\n {\n saasRoot,\n sectionKey: SECTION_KEY,\n sectionSchema: RegistryModulesLockSectionSchema,\n initialSection: emptySection,\n },\n mutator,\n );\n },\n\n /** Convenience helper used by Phase 5+ install flow. */\n async upsertEntry(saasRoot: string, entry: RegistryModulesLockEntry): Promise<void> {\n await this.mutate(saasRoot, (current) => ({\n ...current,\n installed: { ...current.installed, [entry.moduleId]: entry },\n }));\n },\n\n /**\n * Find every other lockfile entry whose `files[]` claims any of the\n * given paths. Used by `update`/`remove` to skip deletes that would\n * silently pull files out from under modules that own them too\n * (cross-module file overlap).\n *\n * Returns a `Map<relPath, owners[]>` where `owners` excludes\n * `excludeModuleId`. Empty array → uniquely owned by the excluded\n * module, safe to delete.\n */\n async findFileOwners(args: {\n saasRoot: string;\n paths: readonly string[];\n excludeModuleId: string;\n }): Promise<Map<string, string[]>> {\n const section = await this.read(args.saasRoot);\n const claimedBy = new Map<string, string[]>();\n for (const p of args.paths) claimedBy.set(p, []);\n for (const otherModuleId of Object.keys(section.installed)) {\n if (otherModuleId === args.excludeModuleId) continue;\n const otherEntry = section.installed[otherModuleId];\n if (!otherEntry) continue;\n for (const otherFile of otherEntry.files) {\n if (claimedBy.has(otherFile)) {\n claimedBy.get(otherFile)!.push(otherModuleId);\n }\n }\n }\n return claimedBy;\n },\n} as const;\n"]}
1
+ {"version":3,"file":"registry-lockfile.service.js","sourceRoot":"","sources":["../../../../src/services/registry-lockfile.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EACL,gCAAgC,EAChC,yBAAyB,EACzB,iBAAiB,EAEjB,8BAA8B,GAE/B,MAAM,+BAA+B,CAAC;AACvC,oEAAoE;AACpE,qEAAqE;AACrE,sEAAsE;AACtE,8DAA8D;AAC9D,kEAAkE;AAClE,qEAAqE;AACrE,mCAAmC;AAEnC,MAAM,WAAW,GAAG,SAAkB,CAAC;AAEvC,SAAS,YAAY;IACnB,OAAO,gCAAgC,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;AACpD,CAAC;AAED,MAAM,CAAC,MAAM,uBAAuB,GAAG;IACrC;;;;OAIG;IACH,KAAK,CAAC,IAAI,CAAC,QAAgB;QACzB,MAAM,QAAQ,GAAG,MAAM,iBAAiB,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC;QACvD,MAAM,GAAG,GAAG,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC;QACtC,IAAI,GAAG,KAAK,SAAS;YAAE,OAAO,YAAY,EAAE,CAAC;QAC7C,OAAO,gCAAgC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACrD,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,MAAM,CACV,QAAgB,EAChB,OAK8C;QAE9C,OAAO,yBAAyB,CAC9B;YACE,QAAQ;YACR,UAAU,EAAE,WAAW;YACvB,aAAa,EAAE,gCAAgC;YAC/C,cAAc,EAAE,YAAY;SAC7B,EACD,OAAO,CACR,CAAC;IACJ,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,WAAW,CAAC,QAAgB,EAAE,KAAoC;QACtE,MAAM,KAAK,GAAG,8BAA8B,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC1D,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;YACxC,GAAG,OAAO;YACV,SAAS,EAAE,EAAE,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,KAAK,EAAE;SAC7D,CAAC,CAAC,CAAC;IACN,CAAC;IAED;;;;;;;;;OASG;IACH,KAAK,CAAC,cAAc,CAAC,IAIpB;QACC,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC/C,MAAM,SAAS,GAAG,IAAI,GAAG,EAAoB,CAAC;QAC9C,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,KAAK;YAAE,SAAS,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACjD,KAAK,MAAM,aAAa,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;YAC3D,IAAI,aAAa,KAAK,IAAI,CAAC,eAAe;gBAAE,SAAS;YACrD,MAAM,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC;YACpD,IAAI,CAAC,UAAU;gBAAE,SAAS;YAC1B,KAAK,MAAM,SAAS,IAAI,UAAU,CAAC,KAAK,EAAE,CAAC;gBACzC,IAAI,SAAS,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;oBAC7B,SAAS,CAAC,GAAG,CAAC,SAAS,CAAE,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;gBAChD,CAAC;YACH,CAAC;QACH,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;CACO,CAAC","sourcesContent":["/**\n * `RegistryLockfileService` — section-scoped reader/writer for the\n * `sections.modules` slice of `wildo-saas.lock.json` (KD-4).\n *\n * Phase 1 graduates the locking + atomic-write primitives into\n * `@wildo-ai/platform-config-lib` (validated by the Phase 0.B\n * contention spike). This service is a thin wrapper around\n * `withLockfileSectionMutate` that pre-binds the `modules` section\n * key + schema, so module-registry callers cannot accidentally\n * touch sibling sections.\n *\n * Hard contracts (KD-4 / KD-15 / KD-18):\n * - Mutates ONLY `sections.modules`. The shared library preserves\n * `sections.providers`, `sections.i18n`, and every unknown\n * `sections.*` payload verbatim — no defensive logic needed\n * here.\n * - Never writes `.wildo-saas/generated/providers/*.runtime-providers.generated.json`\n * or `src/generated/wildo-provider-runtime.generated.ts` —\n * those are produced by the provider materialization workflow.\n * - Never mutates `sections.providers.runtimeScopes`.\n */\n\nimport {\n RegistryModulesLockSectionSchema,\n withLockfileSectionMutate,\n readWildoSaasLock,\n type RegistryModulesLockSection,\n RegistryModulesLockEntrySchema,\n type RegistryModulesLockEntryInput,\n} from '@wildo-ai/platform-config-lib';\n// L6 (third-pass review): the package's barrel-discipline rule says\n// \"Never re-export (except in the root `index.ts` of each package).\"\n// The previous `export { WILDO_SAAS_LOCK_RELATIVE_PATH }` here AND in\n// `lockfile-repair.service.ts` surfaced the symbol twice from\n// `wildo-module-client`'s root. Consumers that need it can import\n// directly from `@wildo-ai/platform-config-lib`, OR rely on the root\n// `index.ts` to re-export it once.\n\nconst SECTION_KEY = 'modules' as const;\n\nfunction emptySection(): RegistryModulesLockSection {\n return RegistryModulesLockSectionSchema.parse({});\n}\n\nexport const RegistryLockfileService = {\n /**\n * Read the current `sections.modules` payload (or an empty section\n * when missing). Unsynchronized — callers needing read-after-write\n * coherence should read INSIDE `mutate`.\n */\n async read(saasRoot: string): Promise<RegistryModulesLockSection> {\n const envelope = await readWildoSaasLock({ saasRoot });\n const raw = envelope.sections.modules;\n if (raw === undefined) return emptySection();\n return RegistryModulesLockSectionSchema.parse(raw);\n },\n\n /**\n * Run `mutator` against the current `sections.modules` payload\n * under an OS-level lock + atomic-write commit. The mutator can\n * either mutate-in-place or return a new value; either way the\n * result is re-validated against `RegistryModulesLockSectionSchema`\n * before commit.\n */\n async mutate(\n saasRoot: string,\n mutator: (\n current: RegistryModulesLockSection,\n ) =>\n | RegistryModulesLockSection\n | void\n | Promise<RegistryModulesLockSection | void>,\n ): Promise<RegistryModulesLockSection> {\n return withLockfileSectionMutate(\n {\n saasRoot,\n sectionKey: SECTION_KEY,\n sectionSchema: RegistryModulesLockSectionSchema,\n initialSection: emptySection,\n },\n mutator,\n );\n },\n\n /**\n * Insert or replace one module's entry. The entry is parsed here, so its defaults (`distribution`,\n * `files`) apply in one place and a malformed entry is refused naming the module, before the lockfile\n * is touched (`mutate` then re-validates the whole section).\n */\n async upsertEntry(saasRoot: string, input: RegistryModulesLockEntryInput): Promise<void> {\n const entry = RegistryModulesLockEntrySchema.parse(input);\n await this.mutate(saasRoot, (current) => ({\n ...current,\n installed: { ...current.installed, [entry.moduleId]: entry },\n }));\n },\n\n /**\n * Find every other lockfile entry whose `files[]` claims any of the\n * given paths. Used by `update`/`remove` to skip deletes that would\n * silently pull files out from under modules that own them too\n * (cross-module file overlap).\n *\n * Returns a `Map<relPath, owners[]>` where `owners` excludes\n * `excludeModuleId`. Empty array → uniquely owned by the excluded\n * module, safe to delete.\n */\n async findFileOwners(args: {\n saasRoot: string;\n paths: readonly string[];\n excludeModuleId: string;\n }): Promise<Map<string, string[]>> {\n const section = await this.read(args.saasRoot);\n const claimedBy = new Map<string, string[]>();\n for (const p of args.paths) claimedBy.set(p, []);\n for (const otherModuleId of Object.keys(section.installed)) {\n if (otherModuleId === args.excludeModuleId) continue;\n const otherEntry = section.installed[otherModuleId];\n if (!otherEntry) continue;\n for (const otherFile of otherEntry.files) {\n if (claimedBy.has(otherFile)) {\n claimedBy.get(otherFile)!.push(otherModuleId);\n }\n }\n }\n return claimedBy;\n },\n} as const;\n"]}
@@ -12,9 +12,10 @@
12
12
  * Auth (KD-11): if `authToken` is supplied, every request adds
13
13
  * `Authorization: Bearer <token>`. Public read paths work without
14
14
  * a token; restricted paths return 401/403, surfaced as
15
- * `RegistryAuthRequiredError` / `RegistryAuthForbiddenError`.
15
+ * `RegistryAuthRequiredError` (no key sent), `RegistryCredentialRejectedError` (a key sent and refused)
16
+ * or `RegistryAuthForbiddenError`; a 429 is `RegistryRateLimitedError`.
16
17
  */
17
- import { type ModuleManifest, type ModuleVersionRecord } from '@wildo-ai/platform-config-lib';
18
+ import { type ModuleManifest, type ModuleSearchFilter, type ModuleSearchHit, type RegistryYankNotice, type ModuleVersionRecord } from '@wildo-ai/platform-config-lib';
18
19
  export interface RegistryServerClientOptions {
19
20
  /** Registry origin (e.g. `https://registry.wildo.ai`). */
20
21
  baseUrl: string;
@@ -27,6 +28,11 @@ export interface RegistryResolveResult {
27
28
  /** Resolved exact version (matches `Sha256DigestSchema` for digest). */
28
29
  version: string;
29
30
  artifactDigest: string;
31
+ /**
32
+ * Present when the resolved release is yanked. Only an EXACT-version request can resolve to one
33
+ * (#1758) — ranges and tags never do — so the caller is pinned to it and should warn, not refuse.
34
+ */
35
+ yanked?: RegistryYankNotice;
30
36
  }
31
37
  export interface RegistryInfoResult {
32
38
  moduleId: string;
@@ -35,11 +41,8 @@ export interface RegistryInfoResult {
35
41
  /** dist-tag → version map (e.g. `latest → 1.2.3`). */
36
42
  tags: Readonly<Record<string, string>>;
37
43
  }
38
- export interface RegistrySearchHit {
39
- moduleId: string;
40
- description?: string;
41
- latestVersion: string;
42
- }
44
+ /** One search hit: the module, its latest version, and (#2014) its kind and provider facts. */
45
+ export type RegistrySearchHit = ModuleSearchHit;
43
46
  export declare const RegistryServerClient: {
44
47
  /**
45
48
  * Liveness probe against `/health`. Returns boolean — used by
@@ -68,12 +71,13 @@ export declare const RegistryServerClient: {
68
71
  moduleId: string;
69
72
  }) => Promise<RegistryInfoResult>;
70
73
  /**
71
- * Search the registry's metadata index. The shape is
72
- * intentionally narrow until Phase 4 designs the search UX.
74
+ * Search the registry's metadata index: a module-id substring, and (#2014) the manifest filters —
75
+ * kind, engine capability and provider maturity. The server refuses an unknown filter value.
73
76
  */
74
77
  readonly search: (options: RegistryServerClientOptions, args: {
75
78
  domain: string;
76
79
  query: string;
80
+ filter?: ModuleSearchFilter;
77
81
  }) => Promise<readonly RegistrySearchHit[]>;
78
82
  /**
79
83
  * Stream-download a published artifact's bytes and verify the
@@ -89,6 +93,28 @@ export declare const RegistryServerClient: {
89
93
  }) => Promise<{
90
94
  bytes: Uint8Array;
91
95
  integrity: string;
96
+ yanked?: RegistryYankNotice;
97
+ }>;
98
+ /**
99
+ * Yank a release (#1758): ranges and `latest` stop selecting it, and `latest` moves to the highest
100
+ * live release; an exact pin still installs it, with a warning. Needs a key holding the version UPDATE
101
+ * role in the domain's owning organization. Returns where `latest` points afterwards.
102
+ */
103
+ readonly yank: (options: RegistryServerClientOptions, args: {
104
+ domain: string;
105
+ moduleId: string;
106
+ version: string;
107
+ reason?: string;
108
+ }) => Promise<{
109
+ latest: string | null;
110
+ }>;
111
+ /** Restore a yanked release (#1758); `latest` returns to it when it outranks the current target. */
112
+ readonly unyank: (options: RegistryServerClientOptions, args: {
113
+ domain: string;
114
+ moduleId: string;
115
+ version: string;
116
+ }) => Promise<{
117
+ latest: string | null;
92
118
  }>;
93
119
  };
94
120
  //# sourceMappingURL=registry-server-client.d.ts.map