@wildo-ai/wildo-module-client 1.1.5 → 1.1.7

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 (69) 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.map +1 -1
  9. package/dist/esm/services/module-adopter.service.d.ts +1 -1
  10. package/dist/esm/services/module-adopter.service.d.ts.map +1 -1
  11. package/dist/esm/services/module-adopter.service.js +7 -8
  12. package/dist/esm/services/module-adopter.service.js.map +1 -1
  13. package/dist/esm/services/module-packager.service.d.ts.map +1 -1
  14. package/dist/esm/services/module-packager.service.js +16 -19
  15. package/dist/esm/services/module-packager.service.js.map +1 -1
  16. package/dist/esm/services/provider-materialization-hook.d.ts.map +1 -1
  17. package/dist/esm/services/provider-sync-runner.d.ts +2 -3
  18. package/dist/esm/services/provider-sync-runner.d.ts.map +1 -1
  19. package/dist/esm/services/provider-sync-runner.js +14 -2
  20. package/dist/esm/services/provider-sync-runner.js.map +1 -1
  21. package/dist/esm/services/registry-companion-client.d.ts +10 -47
  22. package/dist/esm/services/registry-companion-client.d.ts.map +1 -1
  23. package/dist/esm/services/registry-companion-client.js +9 -97
  24. package/dist/esm/services/registry-companion-client.js.map +1 -1
  25. package/dist/esm/services/registry-lockfile.service.d.ts +7 -3
  26. package/dist/esm/services/registry-lockfile.service.d.ts.map +1 -1
  27. package/dist/esm/services/registry-lockfile.service.js +8 -3
  28. package/dist/esm/services/registry-lockfile.service.js.map +1 -1
  29. package/dist/esm/services/registry-server-client.d.ts +35 -9
  30. package/dist/esm/services/registry-server-client.d.ts.map +1 -1
  31. package/dist/esm/services/registry-server-client.js +115 -58
  32. package/dist/esm/services/registry-server-client.js.map +1 -1
  33. package/dist/tsconfig.build.tsbuildinfo +1 -1
  34. package/dist/tsconfig.tsbuildinfo +1 -1
  35. package/package.json +2 -2
  36. package/src/__tests__/module-adopter.service.test.ts +6 -4
  37. package/src/__tests__/module-packager.service.test.ts +36 -34
  38. package/src/__tests__/provider-sync-runner.test.ts +12 -0
  39. package/src/__tests__/registry-lockfile.service.test.ts +3 -3
  40. package/src/__tests__/registry-server-client.test.ts +129 -0
  41. package/src/__tests__/registry-transfer-timeouts.test.ts +94 -0
  42. package/src/services/_http-client.ts +100 -6
  43. package/src/services/module-adopter.service.ts +6 -9
  44. package/src/services/module-packager.service.ts +19 -16
  45. package/src/services/provider-sync-runner.ts +16 -5
  46. package/src/services/registry-companion-client.ts +10 -166
  47. package/src/services/registry-lockfile.service.ts +9 -3
  48. package/src/services/registry-server-client.ts +147 -57
  49. package/dist/esm/__tests__/lockfile-repair.service.test.d.ts +0 -0
  50. package/dist/esm/__tests__/lockfile-repair.service.test.d.ts.map +0 -0
  51. package/dist/esm/__tests__/lockfile-repair.service.test.js.map +0 -0
  52. package/dist/esm/__tests__/module-adopter.service.test.d.ts +0 -0
  53. package/dist/esm/__tests__/module-adopter.service.test.d.ts.map +0 -0
  54. package/dist/esm/__tests__/module-adopter.service.test.js.map +0 -0
  55. package/dist/esm/__tests__/module-packager.service.test.d.ts +0 -0
  56. package/dist/esm/__tests__/module-packager.service.test.d.ts.map +0 -0
  57. package/dist/esm/__tests__/module-packager.service.test.js.map +0 -0
  58. package/dist/esm/__tests__/provider-materialization-hook.test.d.ts +0 -0
  59. package/dist/esm/__tests__/provider-materialization-hook.test.d.ts.map +0 -0
  60. package/dist/esm/__tests__/provider-materialization-hook.test.js.map +0 -0
  61. package/dist/esm/__tests__/provider-sync-runner.test.d.ts +0 -0
  62. package/dist/esm/__tests__/provider-sync-runner.test.d.ts.map +0 -0
  63. package/dist/esm/__tests__/provider-sync-runner.test.js.map +0 -0
  64. package/dist/esm/__tests__/registry-lockfile.service.test.d.ts +0 -0
  65. package/dist/esm/__tests__/registry-lockfile.service.test.d.ts.map +0 -0
  66. package/dist/esm/__tests__/registry-lockfile.service.test.js.map +0 -0
  67. package/dist/esm/__tests__/registry-server-client.test.d.ts +0 -0
  68. package/dist/esm/__tests__/registry-server-client.test.d.ts.map +0 -0
  69. package/dist/esm/__tests__/registry-server-client.test.js.map +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,cAAc,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,cAAc,YAAY,CAAC"}
@@ -5,9 +5,12 @@
5
5
  * Both clients want the same primitives:
6
6
  * - `joinUrl(base, path)` — single-source-of-truth path joiner
7
7
  * that tolerates leading/trailing slashes;
8
- * - `fetchWithTimeout(url, init, timeoutMs)` — abort-on-timeout
9
- * wrapper that converts `AbortError`/`TimeoutError` into a
10
- * uniform diagnostic message;
8
+ * - `fetchWithTimeout(url, init)` — one budget for a metadata
9
+ * exchange; an abort becomes a typed `RegistryRequestTimeoutError`;
10
+ * - `readJsonBody(...)` — reads that exchange's JSON body, typing a
11
+ * body cut short by the same budget as a `READING_BODY` timeout;
12
+ * - `fetchTransfer(url, init)` — an artifact download: a response
13
+ * budget plus an idle budget between body chunks (#1763);
11
14
  * - `fetchJsonOk(...)` — convenience for liveness probes that
12
15
  * want a boolean answer.
13
16
  *
@@ -16,6 +19,12 @@
16
19
  * services that need fancier HTTP (multipart upload, streaming
17
20
  * download) extend this file rather than copy the primitives.
18
21
  */
22
+ /**
23
+ * How long a DOWNLOAD may go without receiving a single byte (#1763). An idle budget, not a cap on the
24
+ * whole body: a large artifact over a slow but moving link finishes, a stalled one fails. 30 s is the
25
+ * order npm and cargo tolerate before calling a transfer stuck.
26
+ */
27
+ export declare const REGISTRY_TRANSFER_IDLE_TIMEOUT_MS = 30000;
19
28
  export declare function joinUrl(base: string, path: string): string;
20
29
  export interface FetchWithTimeoutInit extends RequestInit {
21
30
  timeoutMs?: number;
@@ -23,6 +32,26 @@ export interface FetchWithTimeoutInit extends RequestInit {
23
32
  diagnosticLabel?: string;
24
33
  }
25
34
  export declare function fetchWithTimeout(url: string, init?: FetchWithTimeoutInit): Promise<Response>;
35
+ /**
36
+ * Read a small JSON body fetched through {@link fetchWithTimeout}. A read cut short by that request's
37
+ * timeout is a TIMEOUT, never a malformed payload (it used to be reported as one).
38
+ */
39
+ export declare function readJsonBody(res: Response, url: string, label: string, timeoutMs: number | undefined): Promise<unknown>;
40
+ export interface TransferInit extends FetchWithTimeoutInit {
41
+ /** No-bytes budget for the body (default {@link REGISTRY_TRANSFER_IDLE_TIMEOUT_MS}). */
42
+ idleTimeoutMs?: number;
43
+ }
44
+ /**
45
+ * Fetch for a TRANSFER — an artifact download (#1763). Two budgets, because a metadata timeout cannot
46
+ * be sized for a body of unknown length:
47
+ * - `timeoutMs` bounds the wait for the RESPONSE (status + headers);
48
+ * - `idleTimeoutMs` bounds the silence between body chunks, reset on every chunk.
49
+ * Both aborts surface as `RegistryRequestTimeoutError`, with the phase that fired.
50
+ */
51
+ export declare function fetchTransfer(url: string, init?: TransferInit): Promise<{
52
+ response: Response;
53
+ readAll: () => Promise<Uint8Array>;
54
+ }>;
26
55
  /**
27
56
  * Throws on non-2xx; returns the Response otherwise.
28
57
  *
@@ -1 +1 @@
1
- {"version":3,"file":"_http-client.d.ts","sourceRoot":"","sources":["../../../../src/services/_http-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAG1D;AAED,MAAM,WAAW,oBAAqB,SAAQ,WAAW;IACvD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sFAAsF;IACtF,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,oBAAyB,GAC9B,OAAO,CAAC,QAAQ,CAAC,CAyBnB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,WAAW,CAC/B,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,oBAAyB,GAC9B,OAAO,CAAC,QAAQ,CAAC,CAQnB"}
1
+ {"version":3,"file":"_http-client.d.ts","sourceRoot":"","sources":["../../../src/services/_http-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAMH;;;;GAIG;AACH,eAAO,MAAM,iCAAiC,QAAS,CAAC;AAExD,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAG1D;AAED,MAAM,WAAW,oBAAqB,SAAQ,WAAW;IACvD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sFAAsF;IACtF,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,oBAAyB,GAC9B,OAAO,CAAC,QAAQ,CAAC,CA0BnB;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAAC,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,CAQ7H;AAED,MAAM,WAAW,YAAa,SAAQ,oBAAoB;IACxD,wFAAwF;IACxF,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;GAMG;AACH,wBAAsB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,GAAE,YAAiB,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,OAAO,EAAE,MAAM,OAAO,CAAC,UAAU,CAAC,CAAA;CAAE,CAAC,CAqD7I;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,WAAW,CAC/B,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,oBAAyB,GAC9B,OAAO,CAAC,QAAQ,CAAC,CAQnB"}
@@ -5,9 +5,12 @@
5
5
  * Both clients want the same primitives:
6
6
  * - `joinUrl(base, path)` — single-source-of-truth path joiner
7
7
  * that tolerates leading/trailing slashes;
8
- * - `fetchWithTimeout(url, init, timeoutMs)` — abort-on-timeout
9
- * wrapper that converts `AbortError`/`TimeoutError` into a
10
- * uniform diagnostic message;
8
+ * - `fetchWithTimeout(url, init)` — one budget for a metadata
9
+ * exchange; an abort becomes a typed `RegistryRequestTimeoutError`;
10
+ * - `readJsonBody(...)` — reads that exchange's JSON body, typing a
11
+ * body cut short by the same budget as a `READING_BODY` timeout;
12
+ * - `fetchTransfer(url, init)` — an artifact download: a response
13
+ * budget plus an idle budget between body chunks (#1763);
11
14
  * - `fetchJsonOk(...)` — convenience for liveness probes that
12
15
  * want a boolean answer.
13
16
  *
@@ -16,7 +19,14 @@
16
19
  * services that need fancier HTTP (multipart upload, streaming
17
20
  * download) extend this file rather than copy the primitives.
18
21
  */
22
+ import { isAbortLikeError, RegistryRequestTimeoutError, RegistryTimeoutPhase } from '@wildo-ai/platform-config-lib';
19
23
  const DEFAULT_TIMEOUT_MS = 10_000;
24
+ /**
25
+ * How long a DOWNLOAD may go without receiving a single byte (#1763). An idle budget, not a cap on the
26
+ * whole body: a large artifact over a slow but moving link finishes, a stalled one fails. 30 s is the
27
+ * order npm and cargo tolerate before calling a transfer stuck.
28
+ */
29
+ export const REGISTRY_TRANSFER_IDLE_TIMEOUT_MS = 30_000;
20
30
  export function joinUrl(base, path) {
21
31
  const normalized = path.startsWith('/') ? path : `/${path}`;
22
32
  return `${base.replace(/\/$/, '')}${normalized}`;
@@ -42,12 +52,96 @@ export async function fetchWithTimeout(url, init = {}) {
42
52
  return await fetch(url, { ...rest, signal });
43
53
  }
44
54
  catch (err) {
45
- if (err instanceof Error && (err.name === 'AbortError' || err.name === 'TimeoutError')) {
46
- throw new Error(`${label} request timed out after ${timeoutMs}ms (${url})`);
47
- }
55
+ // Typed, so an `isRegistryError(err)` handler sees it (#1763). The budget spans the whole exchange,
56
+ // which is right for a small metadata response; a body read the same signal aborts is translated by
57
+ // the reader (see `readJsonBody`).
58
+ if (isAbortLikeError(err))
59
+ throw new RegistryRequestTimeoutError(url, label, RegistryTimeoutPhase.AWAITING_RESPONSE, timeoutMs);
48
60
  throw err;
49
61
  }
50
62
  }
63
+ /**
64
+ * Read a small JSON body fetched through {@link fetchWithTimeout}. A read cut short by that request's
65
+ * timeout is a TIMEOUT, never a malformed payload (it used to be reported as one).
66
+ */
67
+ export async function readJsonBody(res, url, label, timeoutMs) {
68
+ try {
69
+ return await res.json();
70
+ }
71
+ catch (err) {
72
+ // The headers arrived; the body is what ran out of time (#1763 review).
73
+ if (isAbortLikeError(err))
74
+ throw new RegistryRequestTimeoutError(url, label, RegistryTimeoutPhase.READING_BODY, timeoutMs ?? DEFAULT_TIMEOUT_MS);
75
+ throw err;
76
+ }
77
+ }
78
+ /**
79
+ * Fetch for a TRANSFER — an artifact download (#1763). Two budgets, because a metadata timeout cannot
80
+ * be sized for a body of unknown length:
81
+ * - `timeoutMs` bounds the wait for the RESPONSE (status + headers);
82
+ * - `idleTimeoutMs` bounds the silence between body chunks, reset on every chunk.
83
+ * Both aborts surface as `RegistryRequestTimeoutError`, with the phase that fired.
84
+ */
85
+ export async function fetchTransfer(url, init = {}) {
86
+ const timeoutMs = init.timeoutMs ?? DEFAULT_TIMEOUT_MS;
87
+ const idleTimeoutMs = init.idleTimeoutMs ?? REGISTRY_TRANSFER_IDLE_TIMEOUT_MS;
88
+ const label = init.diagnosticLabel ?? 'HTTP';
89
+ const { timeoutMs: _t, idleTimeoutMs: _i, diagnosticLabel: _d, ...rest } = init;
90
+ const controller = new AbortController();
91
+ let phase = RegistryTimeoutPhase.AWAITING_RESPONSE;
92
+ let timer = setTimeout(() => controller.abort(), timeoutMs);
93
+ const timeoutError = () => new RegistryRequestTimeoutError(url, label, phase, phase === RegistryTimeoutPhase.AWAITING_RESPONSE ? timeoutMs : idleTimeoutMs);
94
+ let response;
95
+ try {
96
+ response = await fetch(url, { ...rest, signal: controller.signal });
97
+ }
98
+ catch (err) {
99
+ clearTimeout(timer);
100
+ if (isAbortLikeError(err))
101
+ throw timeoutError();
102
+ throw err;
103
+ }
104
+ clearTimeout(timer);
105
+ const readAll = async () => {
106
+ phase = RegistryTimeoutPhase.TRANSFER_STALLED;
107
+ const chunks = [];
108
+ let total = 0;
109
+ const reader = response.body?.getReader();
110
+ if (!reader)
111
+ return new Uint8Array(0);
112
+ const arm = () => {
113
+ clearTimeout(timer);
114
+ timer = setTimeout(() => controller.abort(), idleTimeoutMs);
115
+ };
116
+ arm();
117
+ try {
118
+ for (;;) {
119
+ const { done, value } = await reader.read();
120
+ if (done)
121
+ break;
122
+ chunks.push(value);
123
+ total += value.byteLength;
124
+ arm();
125
+ }
126
+ }
127
+ catch (err) {
128
+ if (isAbortLikeError(err) || controller.signal.aborted)
129
+ throw timeoutError();
130
+ throw err;
131
+ }
132
+ finally {
133
+ clearTimeout(timer);
134
+ }
135
+ const bytes = new Uint8Array(total);
136
+ let offset = 0;
137
+ for (const chunk of chunks) {
138
+ bytes.set(chunk, offset);
139
+ offset += chunk.byteLength;
140
+ }
141
+ return bytes;
142
+ };
143
+ return { response, readAll };
144
+ }
51
145
  /**
52
146
  * Throws on non-2xx; returns the Response otherwise.
53
147
  *
@@ -1 +1 @@
1
- {"version":3,"file":"_http-client.js","sourceRoot":"","sources":["../../../../src/services/_http-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,kBAAkB,GAAG,MAAM,CAAC;AAElC,MAAM,UAAU,OAAO,CAAC,IAAY,EAAE,IAAY;IAChD,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC;IAC5D,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,UAAU,EAAE,CAAC;AACnD,CAAC;AAQD,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,GAAW,EACX,OAA6B,EAAE;IAE/B,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,kBAAkB,CAAC;IACvD,MAAM,KAAK,GAAG,IAAI,CAAC,eAAe,IAAI,MAAM,CAAC;IAC7C,MAAM,MAAM,GAAG,WAAW,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAC9C,kEAAkE;IAClE,qDAAqD;IACrD,EAAE;IACF,+DAA+D;IAC/D,qEAAqE;IACrE,mEAAmE;IACnE,iEAAiE;IACjE,oEAAoE;IACpE,mEAAmE;IACnE,iEAAiE;IACjE,kEAAkE;IAClE,sCAAsC;IACtC,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,eAAe,EAAE,gBAAgB,EAAE,GAAG,IAAI,EAAE,GAAG,IAAI,CAAC;IACnF,IAAI,CAAC;QACH,OAAO,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;IAC/C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,KAAK,IAAI,CAAC,GAAG,CAAC,IAAI,KAAK,YAAY,IAAI,GAAG,CAAC,IAAI,KAAK,cAAc,CAAC,EAAE,CAAC;YACvF,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,4BAA4B,SAAS,OAAO,GAAG,GAAG,CAAC,CAAC;QAC9E,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,GAAW,EACX,OAA6B,EAAE;IAE/B,MAAM,GAAG,GAAG,MAAM,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IAC9C,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CACb,GAAG,IAAI,CAAC,eAAe,IAAI,MAAM,sBAAsB,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,UAAU,QAAQ,GAAG,EAAE,CACjG,CAAC;IACJ,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC","sourcesContent":["/**\n * Internal HTTP client utilities shared by `RegistryServerClient`\n * and `RegistryCompanionClient`.\n *\n * Both clients want the same primitives:\n * - `joinUrl(base, path)` — single-source-of-truth path joiner\n * that tolerates leading/trailing slashes;\n * - `fetchWithTimeout(url, init, timeoutMs)` — abort-on-timeout\n * wrapper that converts `AbortError`/`TimeoutError` into a\n * uniform diagnostic message;\n * - `fetchJsonOk(...)` — convenience for liveness probes that\n * want a boolean answer.\n *\n * The leading underscore signals \"package-internal\" — these are\n * intentionally NOT re-exported from `services/index.ts`. Phase 2\n * services that need fancier HTTP (multipart upload, streaming\n * download) extend this file rather than copy the primitives.\n */\n\nconst DEFAULT_TIMEOUT_MS = 10_000;\n\nexport function joinUrl(base: string, path: string): string {\n const normalized = path.startsWith('/') ? path : `/${path}`;\n return `${base.replace(/\\/$/, '')}${normalized}`;\n}\n\nexport interface FetchWithTimeoutInit extends RequestInit {\n timeoutMs?: number;\n /** Used in the diagnostic timeout message (e.g. \"Registry\", \"Companion registry\"). */\n diagnosticLabel?: string;\n}\n\nexport async function fetchWithTimeout(\n url: string,\n init: FetchWithTimeoutInit = {},\n): Promise<Response> {\n const timeoutMs = init.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const label = init.diagnosticLabel ?? 'HTTP';\n const signal = AbortSignal.timeout(timeoutMs);\n // Strip the wrapper-only fields before forwarding to `fetch`. The\n // `_` names are conventional unused-binding markers.\n //\n // 6th-pass M1 — tsconfig premise (corrected): the wider repo's\n // `engine/tsconfig.common.json` currently sets both `noUnusedLocals`\n // and `noUnusedParameters` to `false`, so any unused binding (with\n // or without `_` prefix) compiles clean today. If either flag is\n // ever flipped to `true`, TypeScript's underscore-prefix convention\n // applies to parameters reliably and to destructured `let`/`const`\n // bindings depending on TS version + setting — so the `_` prefix\n // here is a defensive signal of \"intentional discard\" should that\n // happen. Today it's purely cosmetic.\n const { timeoutMs: _timeoutMs, diagnosticLabel: _diagnosticLabel, ...rest } = init;\n try {\n return await fetch(url, { ...rest, signal });\n } catch (err) {\n if (err instanceof Error && (err.name === 'AbortError' || err.name === 'TimeoutError')) {\n throw new Error(`${label} request timed out after ${timeoutMs}ms (${url})`);\n }\n throw err;\n }\n}\n\n/**\n * Throws on non-2xx; returns the Response otherwise.\n *\n * C-L2 (4th-pass review) waiver: this helper deliberately throws\n * bare `Error` and is NOT part of the typed `RegistryError`\n * vocabulary. It exists for liveness probes (`ping()`) that wrap\n * the call in their own try/catch and convert any thrown exception\n * to `false`. Future callers MUST do the same — if a caller wants\n * to surface a typed error to the user, they should not use\n * `fetchJsonOk` and should call `fetchWithTimeout` directly +\n * inspect `res.ok` themselves to throw the right\n * `RegistryUnexpectedStatusError` / `RegistryAuthRequiredError` /\n * etc. variant.\n *\n * @internal — internal lib helper, not re-exported from the\n * package root.\n */\nexport async function fetchJsonOk(\n url: string,\n init: FetchWithTimeoutInit = {},\n): Promise<Response> {\n const res = await fetchWithTimeout(url, init);\n if (!res.ok) {\n throw new Error(\n `${init.diagnosticLabel ?? 'HTTP'} request rejected (${res.status} ${res.statusText}) at ${url}`,\n );\n }\n return res;\n}\n"]}
1
+ {"version":3,"file":"_http-client.js","sourceRoot":"","sources":["../../../src/services/_http-client.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Internal HTTP client utilities shared by `RegistryServerClient`\n * and `RegistryCompanionClient`.\n *\n * Both clients want the same primitives:\n * - `joinUrl(base, path)` — single-source-of-truth path joiner\n * that tolerates leading/trailing slashes;\n * - `fetchWithTimeout(url, init)` — one budget for a metadata\n * exchange; an abort becomes a typed `RegistryRequestTimeoutError`;\n * - `readJsonBody(...)` — reads that exchange's JSON body, typing a\n * body cut short by the same budget as a `READING_BODY` timeout;\n * - `fetchTransfer(url, init)` — an artifact download: a response\n * budget plus an idle budget between body chunks (#1763);\n * - `fetchJsonOk(...)` — convenience for liveness probes that\n * want a boolean answer.\n *\n * The leading underscore signals \"package-internal\" — these are\n * intentionally NOT re-exported from `services/index.ts`. Phase 2\n * services that need fancier HTTP (multipart upload, streaming\n * download) extend this file rather than copy the primitives.\n */\n\nimport { isAbortLikeError, RegistryRequestTimeoutError, RegistryTimeoutPhase } from '@wildo-ai/platform-config-lib';\n\nconst DEFAULT_TIMEOUT_MS = 10_000;\n\n/**\n * How long a DOWNLOAD may go without receiving a single byte (#1763). An idle budget, not a cap on the\n * whole body: a large artifact over a slow but moving link finishes, a stalled one fails. 30 s is the\n * order npm and cargo tolerate before calling a transfer stuck.\n */\nexport const REGISTRY_TRANSFER_IDLE_TIMEOUT_MS = 30_000;\n\nexport function joinUrl(base: string, path: string): string {\n const normalized = path.startsWith('/') ? path : `/${path}`;\n return `${base.replace(/\\/$/, '')}${normalized}`;\n}\n\nexport interface FetchWithTimeoutInit extends RequestInit {\n timeoutMs?: number;\n /** Used in the diagnostic timeout message (e.g. \"Registry\", \"Companion registry\"). */\n diagnosticLabel?: string;\n}\n\nexport async function fetchWithTimeout(\n url: string,\n init: FetchWithTimeoutInit = {},\n): Promise<Response> {\n const timeoutMs = init.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const label = init.diagnosticLabel ?? 'HTTP';\n const signal = AbortSignal.timeout(timeoutMs);\n // Strip the wrapper-only fields before forwarding to `fetch`. The\n // `_` names are conventional unused-binding markers.\n //\n // 6th-pass M1 — tsconfig premise (corrected): the wider repo's\n // `engine/tsconfig.common.json` currently sets both `noUnusedLocals`\n // and `noUnusedParameters` to `false`, so any unused binding (with\n // or without `_` prefix) compiles clean today. If either flag is\n // ever flipped to `true`, TypeScript's underscore-prefix convention\n // applies to parameters reliably and to destructured `let`/`const`\n // bindings depending on TS version + setting — so the `_` prefix\n // here is a defensive signal of \"intentional discard\" should that\n // happen. Today it's purely cosmetic.\n const { timeoutMs: _timeoutMs, diagnosticLabel: _diagnosticLabel, ...rest } = init;\n try {\n return await fetch(url, { ...rest, signal });\n } catch (err) {\n // Typed, so an `isRegistryError(err)` handler sees it (#1763). The budget spans the whole exchange,\n // which is right for a small metadata response; a body read the same signal aborts is translated by\n // the reader (see `readJsonBody`).\n if (isAbortLikeError(err)) throw new RegistryRequestTimeoutError(url, label, RegistryTimeoutPhase.AWAITING_RESPONSE, timeoutMs);\n throw err;\n }\n}\n\n/**\n * Read a small JSON body fetched through {@link fetchWithTimeout}. A read cut short by that request's\n * timeout is a TIMEOUT, never a malformed payload (it used to be reported as one).\n */\nexport async function readJsonBody(res: Response, url: string, label: string, timeoutMs: number | undefined): Promise<unknown> {\n try {\n return await res.json();\n } catch (err) {\n // The headers arrived; the body is what ran out of time (#1763 review).\n if (isAbortLikeError(err)) throw new RegistryRequestTimeoutError(url, label, RegistryTimeoutPhase.READING_BODY, timeoutMs ?? DEFAULT_TIMEOUT_MS);\n throw err;\n }\n}\n\nexport interface TransferInit extends FetchWithTimeoutInit {\n /** No-bytes budget for the body (default {@link REGISTRY_TRANSFER_IDLE_TIMEOUT_MS}). */\n idleTimeoutMs?: number;\n}\n\n/**\n * Fetch for a TRANSFER — an artifact download (#1763). Two budgets, because a metadata timeout cannot\n * be sized for a body of unknown length:\n * - `timeoutMs` bounds the wait for the RESPONSE (status + headers);\n * - `idleTimeoutMs` bounds the silence between body chunks, reset on every chunk.\n * Both aborts surface as `RegistryRequestTimeoutError`, with the phase that fired.\n */\nexport async function fetchTransfer(url: string, init: TransferInit = {}): Promise<{ response: Response; readAll: () => Promise<Uint8Array> }> {\n const timeoutMs = init.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const idleTimeoutMs = init.idleTimeoutMs ?? REGISTRY_TRANSFER_IDLE_TIMEOUT_MS;\n const label = init.diagnosticLabel ?? 'HTTP';\n const { timeoutMs: _t, idleTimeoutMs: _i, diagnosticLabel: _d, ...rest } = init;\n const controller = new AbortController();\n let phase = RegistryTimeoutPhase.AWAITING_RESPONSE;\n let timer = setTimeout(() => controller.abort(), timeoutMs);\n const timeoutError = () =>\n new RegistryRequestTimeoutError(url, label, phase, phase === RegistryTimeoutPhase.AWAITING_RESPONSE ? timeoutMs : idleTimeoutMs);\n let response: Response;\n try {\n response = await fetch(url, { ...rest, signal: controller.signal });\n } catch (err) {\n clearTimeout(timer);\n if (isAbortLikeError(err)) throw timeoutError();\n throw err;\n }\n clearTimeout(timer);\n const readAll = async (): Promise<Uint8Array> => {\n phase = RegistryTimeoutPhase.TRANSFER_STALLED;\n const chunks: Uint8Array[] = [];\n let total = 0;\n const reader = response.body?.getReader();\n if (!reader) return new Uint8Array(0);\n const arm = () => {\n clearTimeout(timer);\n timer = setTimeout(() => controller.abort(), idleTimeoutMs);\n };\n arm();\n try {\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n chunks.push(value);\n total += value.byteLength;\n arm();\n }\n } catch (err) {\n if (isAbortLikeError(err) || controller.signal.aborted) throw timeoutError();\n throw err;\n } finally {\n clearTimeout(timer);\n }\n const bytes = new Uint8Array(total);\n let offset = 0;\n for (const chunk of chunks) {\n bytes.set(chunk, offset);\n offset += chunk.byteLength;\n }\n return bytes;\n };\n return { response, readAll };\n}\n\n/**\n * Throws on non-2xx; returns the Response otherwise.\n *\n * C-L2 (4th-pass review) waiver: this helper deliberately throws\n * bare `Error` and is NOT part of the typed `RegistryError`\n * vocabulary. It exists for liveness probes (`ping()`) that wrap\n * the call in their own try/catch and convert any thrown exception\n * to `false`. Future callers MUST do the same — if a caller wants\n * to surface a typed error to the user, they should not use\n * `fetchJsonOk` and should call `fetchWithTimeout` directly +\n * inspect `res.ok` themselves to throw the right\n * `RegistryUnexpectedStatusError` / `RegistryAuthRequiredError` /\n * etc. variant.\n *\n * @internal — internal lib helper, not re-exported from the\n * package root.\n */\nexport async function fetchJsonOk(\n url: string,\n init: FetchWithTimeoutInit = {},\n): Promise<Response> {\n const res = await fetchWithTimeout(url, init);\n if (!res.ok) {\n throw new Error(\n `${init.diagnosticLabel ?? 'HTTP'} request rejected (${res.status} ${res.statusText}) at ${url}`,\n );\n }\n return res;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"_zod-issue-formatting.d.ts","sourceRoot":"","sources":["../../../../src/services/_zod-issue-formatting.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,KAAK,CAAC;AAiCpC;;;;;;;;GAQG;AACH,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,QAAQ,GAAG,MAAM,CAOnE"}
1
+ {"version":3,"file":"_zod-issue-formatting.d.ts","sourceRoot":"","sources":["../../../src/services/_zod-issue-formatting.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,KAAK,CAAC;AAiCpC;;;;;;;;GAQG;AACH,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,QAAQ,GAAG,MAAM,CAOnE"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/services/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,0BAA0B,CAAC;AACzC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,wBAAwB,CAAC;AACvC,cAAc,iCAAiC,CAAC;AAChD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,0BAA0B,CAAC;AACzC,cAAc,2BAA2B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/services/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,0BAA0B,CAAC;AACzC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,wBAAwB,CAAC;AACvC,cAAc,iCAAiC,CAAC;AAChD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,0BAA0B,CAAC;AACzC,cAAc,2BAA2B,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"lockfile-repair.service.d.ts","sourceRoot":"","sources":["../../../../src/services/lockfile-repair.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAMH,OAAO,EAGL,KAAK,wBAAwB,EAE9B,MAAM,+BAA+B,CAAC;AAcvC,MAAM,MAAM,qBAAqB,GAC7B;IACE,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,oBAAoB,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GACD;IACE,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,2BAA2B,CAAC;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GACD;IACE,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,qBAAqB,CAAC;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEN,MAAM,WAAW,oBAAoB;IACnC,uCAAuC;IACvC,YAAY,EAAE,MAAM,CAAC;IACrB,4DAA4D;IAC5D,EAAE,EAAE,OAAO,CAAC;IACZ,QAAQ,EAAE,SAAS,qBAAqB,EAAE,CAAC;CAC5C;AAWD,eAAO,MAAM,qBAAqB;IAChC,iDAAiD;iCAC/B,MAAM,KAAG,MAAM;IAIjC;;OAEG;iCACqB,MAAM,KAAG,OAAO,CAAC,oBAAoB,CAAC;IA8E9D;;OAEG;8CAES,MAAM,YACN,MAAM,SACT,wBAAwB,YACrB,qBAAqB,EAAE,KAChC,OAAO,CAAC,IAAI,CAAC;CAaR,CAAC"}
1
+ {"version":3,"file":"lockfile-repair.service.d.ts","sourceRoot":"","sources":["../../../src/services/lockfile-repair.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAMH,OAAO,EAGL,KAAK,wBAAwB,EAE9B,MAAM,+BAA+B,CAAC;AAcvC,MAAM,MAAM,qBAAqB,GAC7B;IACE,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,oBAAoB,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GACD;IACE,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,2BAA2B,CAAC;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GACD;IACE,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,qBAAqB,CAAC;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEN,MAAM,WAAW,oBAAoB;IACnC,uCAAuC;IACvC,YAAY,EAAE,MAAM,CAAC;IACrB,4DAA4D;IAC5D,EAAE,EAAE,OAAO,CAAC;IACZ,QAAQ,EAAE,SAAS,qBAAqB,EAAE,CAAC;CAC5C;AAWD,eAAO,MAAM,qBAAqB;IAChC,iDAAiD;iCAC/B,MAAM,KAAG,MAAM;IAIjC;;OAEG;iCACqB,MAAM,KAAG,OAAO,CAAC,oBAAoB,CAAC;IA8E9D;;OAEG;8CAES,MAAM,YACN,MAAM,SACT,wBAAwB,YACrB,qBAAqB,EAAE,KAChC,OAAO,CAAC,IAAI,CAAC;CAaR,CAAC"}
@@ -19,7 +19,7 @@
19
19
  * the staging digest. Phase 6 plugs this into the publish
20
20
  * flow.
21
21
  */
22
- import type { ModuleManifest } from '@wildo-ai/platform-config-lib';
22
+ import { type ModuleManifest } from '@wildo-ai/platform-config-lib';
23
23
  import { type StagingPaths } from './module-packager.service';
24
24
  export interface AdoptionPlan {
25
25
  /** Per-layer file list, relative to saas root. */
@@ -1 +1 @@
1
- {"version":3,"file":"module-adopter.service.d.ts","sourceRoot":"","sources":["../../../../src/services/module-adopter.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AACpE,OAAO,EAAyB,KAAK,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAErF,MAAM,WAAW,YAAY;IAC3B,kDAAkD;IAClD,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,wEAAwE;IACxE,eAAe,EAAE,SAAS,MAAM,EAAE,CAAC;CACpC;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,cAAc,CAAC;IACzB,OAAO,EAAE,YAAY,CAAC;IACtB,oDAAoD;IACpD,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1B,uEAAuE;IACvE,MAAM,EAAE,MAAM,CAAC;CAChB;AAoCD,eAAO,MAAM,oBAAoB;IAC/B;;;OAGG;6CAES,MAAM,YACN,cAAc,KACvB,OAAO,CAAC,YAAY,CAAC;IA+BxB;;;OAGG;wCAES,MAAM,YACN,cAAc,KACvB,OAAO,CAAC,cAAc,CAAC;IAO1B;;;;OAIG;yCAES,MAAM,YACN,cAAc,QAClB,YAAY,KACjB,OAAO,CAAC;QAAE,OAAO,EAAE,YAAY,CAAC;QAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;KAAE,CAAC;CAezD,CAAC"}
1
+ {"version":3,"file":"module-adopter.service.d.ts","sourceRoot":"","sources":["../../../src/services/module-adopter.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAIH,OAAO,EAAuD,KAAK,cAAc,EAAE,MAAM,+BAA+B,CAAC;AACzH,OAAO,EAAyB,KAAK,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAErF,MAAM,WAAW,YAAY;IAC3B,kDAAkD;IAClD,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,wEAAwE;IACxE,eAAe,EAAE,SAAS,MAAM,EAAE,CAAC;CACpC;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,cAAc,CAAC;IACzB,OAAO,EAAE,YAAY,CAAC;IACtB,oDAAoD;IACpD,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1B,uEAAuE;IACvE,MAAM,EAAE,MAAM,CAAC;CAChB;AAuCD,eAAO,MAAM,oBAAoB;IAC/B;;;OAGG;6CAES,MAAM,YACN,cAAc,KACvB,OAAO,CAAC,YAAY,CAAC;IAyBxB;;;OAGG;wCAES,MAAM,YACN,cAAc,KACvB,OAAO,CAAC,cAAc,CAAC;IAO1B;;;;OAIG;yCAES,MAAM,YACN,cAAc,QAClB,YAAY,KACjB,OAAO,CAAC;QAAE,OAAO,EAAE,YAAY,CAAC;QAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;KAAE,CAAC;CAezD,CAAC"}
@@ -21,6 +21,7 @@
21
21
  */
22
22
  import { promises as fs } from 'node:fs';
23
23
  import * as path from 'node:path';
24
+ import { isModuleArtifactPathAllowed, MODULE_BOUNDARY_LAYERS } from '@wildo-ai/platform-config-lib';
24
25
  import { ModulePackagerService } from './module-packager.service.js';
25
26
  async function fileExists(absPath) {
26
27
  try {
@@ -45,6 +46,10 @@ async function walkBoundary(saasRoot, appRelative) {
45
46
  const entries = await fs.readdir(path.resolve(saasRoot, cur), { withFileTypes: true });
46
47
  for (const entry of entries) {
47
48
  const rel = path.join(cur, entry.name);
49
+ // Skip what a module never ships, exactly as `wildo registry publish` does — otherwise the
50
+ // packager (which refuses those paths) throws on a module's own `.env` or `node_modules`.
51
+ if (!isModuleArtifactPathAllowed(rel.split(path.sep).join('/')))
52
+ continue;
48
53
  if (entry.isDirectory()) {
49
54
  queue.push(rel);
50
55
  }
@@ -65,14 +70,8 @@ export const ModuleAdopterService = {
65
70
  if (!boundaries) {
66
71
  return { files: [], emptyBoundaries: [] };
67
72
  }
68
- const layers = [
69
- 'specifications',
70
- 'shared',
71
- 'frontend',
72
- 'backend',
73
- 'minions',
74
- 'workers',
75
- ];
73
+ // Every layer the registry knows, not a hand-kept copy: a copy here missed `providers` (#2014).
74
+ const layers = MODULE_BOUNDARY_LAYERS;
76
75
  const aggregate = new Set();
77
76
  const empty = [];
78
77
  for (const layer of layers) {
@@ -1 +1 @@
1
- {"version":3,"file":"module-adopter.service.js","sourceRoot":"","sources":["../../../../src/services/module-adopter.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,OAAO,EAAE,qBAAqB,EAAqB,MAAM,2BAA2B,CAAC;AAkBrF,KAAK,UAAU,UAAU,CAAC,OAAe;IACvC,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACpC,OAAO,IAAI,CAAC,MAAM,EAAE,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;IAC7C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,KAAK,UAAU,YAAY,CACzB,QAAgB,EAChB,WAAmB;IAEnB,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;IAChD,IAAI,CAAC,CAAC,MAAM,UAAU,CAAC,GAAG,CAAC,CAAC;QAAE,OAAO,EAAE,CAAC;IACxC,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAChC,IAAI,IAAI,CAAC,MAAM,EAAE;QAAE,OAAO,CAAC,WAAW,CAAC,CAAC;IACxC,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,MAAM,KAAK,GAAa,CAAC,WAAW,CAAC,CAAC;IACtC,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,MAAM,GAAG,GAAG,KAAK,CAAC,KAAK,EAAG,CAAC;QAC3B,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QACvF,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YACvC,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;gBACxB,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAClB,CAAC;iBAAM,IAAI,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;gBAC1B,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAChB,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC;;;OAGG;IACH,KAAK,CAAC,mBAAmB,CACvB,QAAgB,EAChB,QAAwB;QAExB,MAAM,UAAU,GAAG,QAAQ,CAAC,UAAU,CAAC;QACvC,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,eAAe,EAAE,EAAE,EAAE,CAAC;QAC5C,CAAC;QACD,MAAM,MAAM,GAA2C;YACrD,gBAAgB;YAChB,QAAQ;YACR,UAAU;YACV,SAAS;YACT,SAAS;YACT,SAAS;SACV,CAAC;QACF,MAAM,SAAS,GAAG,IAAI,GAAG,EAAU,CAAC;QACpC,MAAM,KAAK,GAAa,EAAE,CAAC;QAC3B,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,MAAM,MAAM,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;YACjC,IAAI,UAAU,GAAG,CAAC,CAAC;YACnB,KAAK,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;gBACzB,MAAM,SAAS,GAAG,MAAM,YAAY,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;gBACpD,KAAK,MAAM,IAAI,IAAI,SAAS;oBAAE,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;gBAClD,UAAU,IAAI,SAAS,CAAC,MAAM,CAAC;YACjC,CAAC;YACD,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,UAAU,KAAK,CAAC;gBAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC/D,CAAC;QACD,OAAO;YACL,KAAK,EAAE,CAAC,GAAG,SAAS,CAAC,CAAC,IAAI,EAAE;YAC5B,eAAe,EAAE,KAAK;SACvB,CAAC;IACJ,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,cAAc,CAClB,QAAgB,EAChB,QAAwB;QAExB,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAChE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC;QAClF,MAAM,MAAM,GAAG,MAAM,qBAAqB,CAAC,oBAAoB,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC;QACvF,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IACxD,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,eAAe,CACnB,QAAgB,EAChB,QAAwB,EACxB,IAAkB;QAElB,MAAM,OAAO,GAAG,MAAM,qBAAqB,CAAC,UAAU,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC;QAC9E,MAAM,qBAAqB,CAAC,aAAa,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAC;QAClE,MAAM,OAAO,GAAa,EAAE,CAAC;QAC7B,KAAK,MAAM,eAAe,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACzC,MAAM,MAAM,GAAG,MAAM,qBAAqB,CAAC,QAAQ,CAAC;gBAClD,QAAQ;gBACR,QAAQ,EAAE,QAAQ,CAAC,EAAE;gBACrB,eAAe;gBACf,QAAQ;aACT,CAAC,CAAC;YACH,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC;QACvC,CAAC;QACD,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;IAC9B,CAAC;CACO,CAAC","sourcesContent":["/**\n * `ModuleAdopterService` — Phase 2 CLI-direct adoption flow.\n *\n * `wildo registry adopt` takes existing app code that fits the\n * Wildo module-artifact shape and prepares it for publication\n * (KD-14). Adoption is intentionally CLI-DIRECT (Review-pass-3 /\n * MEDIUM-G) — it only reads source slices and stages files. AST\n * mutations on the host app's source are NOT part of adopt; if a\n * future flow needs them, they go through the companion HTTP\n * surface like every other source-mutating verb.\n *\n * Service surface:\n * - `discoverModuleFiles(saasRoot, manifest)` — walks each\n * declared `boundaries.*` allow-list and returns the set of\n * files that exist on disk and are within the boundary.\n * - `adoptToStaging(saasRoot, manifest)` — clears + populates\n * `<saasRoot>/.wildo-saas/generated/modules-artifacts/<id>/`\n * with the manifest + every discovered file, then computes\n * the staging digest. Phase 6 plugs this into the publish\n * flow.\n */\n\nimport { promises as fs } from 'node:fs';\nimport * as path from 'node:path';\nimport type { ModuleManifest } from '@wildo-ai/platform-config-lib';\nimport { ModulePackagerService, type StagingPaths } from './module-packager.service';\n\nexport interface AdoptionPlan {\n /** Per-layer file list, relative to saas root. */\n files: readonly string[];\n /** Empty when every declared boundary contributed at least one file. */\n emptyBoundaries: readonly string[];\n}\n\nexport interface AdoptionResult {\n manifest: ModuleManifest;\n staging: StagingPaths;\n /** Files that were copied into the staging root. */\n staged: readonly string[];\n /** sha256 digest of the staged tree (matches `Sha256DigestSchema`). */\n digest: string;\n}\n\nasync function fileExists(absPath: string): Promise<boolean> {\n try {\n const stat = await fs.stat(absPath);\n return stat.isFile() || stat.isDirectory();\n } catch {\n return false;\n }\n}\n\nasync function walkBoundary(\n saasRoot: string,\n appRelative: string,\n): Promise<string[]> {\n const abs = path.resolve(saasRoot, appRelative);\n if (!(await fileExists(abs))) return [];\n const stat = await fs.stat(abs);\n if (stat.isFile()) return [appRelative];\n const out: string[] = [];\n const queue: string[] = [appRelative];\n while (queue.length > 0) {\n const cur = queue.shift()!;\n const entries = await fs.readdir(path.resolve(saasRoot, cur), { withFileTypes: true });\n for (const entry of entries) {\n const rel = path.join(cur, entry.name);\n if (entry.isDirectory()) {\n queue.push(rel);\n } else if (entry.isFile()) {\n out.push(rel);\n }\n }\n }\n return out;\n}\n\nexport const ModuleAdopterService = {\n /**\n * Walk every declared `boundaries.*` allow-list and return the\n * files that actually exist on disk.\n */\n async discoverModuleFiles(\n saasRoot: string,\n manifest: ModuleManifest,\n ): Promise<AdoptionPlan> {\n const boundaries = manifest.boundaries;\n if (!boundaries) {\n return { files: [], emptyBoundaries: [] };\n }\n const layers: ReadonlyArray<keyof typeof boundaries> = [\n 'specifications',\n 'shared',\n 'frontend',\n 'backend',\n 'minions',\n 'workers',\n ];\n const aggregate = new Set<string>();\n const empty: string[] = [];\n for (const layer of layers) {\n const allows = boundaries[layer];\n let layerCount = 0;\n for (const rel of allows) {\n const collected = await walkBoundary(saasRoot, rel);\n for (const file of collected) aggregate.add(file);\n layerCount += collected.length;\n }\n if (allows.length > 0 && layerCount === 0) empty.push(layer);\n }\n return {\n files: [...aggregate].sort(),\n emptyBoundaries: empty,\n };\n },\n\n /**\n * Stage the module from existing app source. Idempotent — calling\n * twice with the same source produces the same digest.\n */\n async adoptToStaging(\n saasRoot: string,\n manifest: ModuleManifest,\n ): Promise<AdoptionResult> {\n const plan = await this.discoverModuleFiles(saasRoot, manifest);\n const { staging, written } = await this.runAdoptionPlan(saasRoot, manifest, plan);\n const digest = await ModulePackagerService.computeStagingDigest(saasRoot, manifest.id);\n return { manifest, staging, staged: written, digest };\n },\n\n /**\n * Re-usable apply step: writes manifest + copies every file in\n * the plan. Exposed so Phase 6 publish can reuse it after a\n * separate staging-prep step.\n */\n async runAdoptionPlan(\n saasRoot: string,\n manifest: ModuleManifest,\n plan: AdoptionPlan,\n ): Promise<{ staging: StagingPaths; written: readonly string[] }> {\n const staging = await ModulePackagerService.stageFresh(saasRoot, manifest.id);\n await ModulePackagerService.writeManifest({ saasRoot, manifest });\n const written: string[] = [];\n for (const appRelativePath of plan.files) {\n const result = await ModulePackagerService.copyFile({\n saasRoot,\n moduleId: manifest.id,\n appRelativePath,\n manifest,\n });\n written.push(result.appRelativePath);\n }\n return { staging, written };\n },\n} as const;\n"]}
1
+ {"version":3,"file":"module-adopter.service.js","sourceRoot":"","sources":["../../../src/services/module-adopter.service.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `ModuleAdopterService` — Phase 2 CLI-direct adoption flow.\n *\n * `wildo registry adopt` takes existing app code that fits the\n * Wildo module-artifact shape and prepares it for publication\n * (KD-14). Adoption is intentionally CLI-DIRECT (Review-pass-3 /\n * MEDIUM-G) — it only reads source slices and stages files. AST\n * mutations on the host app's source are NOT part of adopt; if a\n * future flow needs them, they go through the companion HTTP\n * surface like every other source-mutating verb.\n *\n * Service surface:\n * - `discoverModuleFiles(saasRoot, manifest)` — walks each\n * declared `boundaries.*` allow-list and returns the set of\n * files that exist on disk and are within the boundary.\n * - `adoptToStaging(saasRoot, manifest)` — clears + populates\n * `<saasRoot>/.wildo-saas/generated/modules-artifacts/<id>/`\n * with the manifest + every discovered file, then computes\n * the staging digest. Phase 6 plugs this into the publish\n * flow.\n */\n\nimport { promises as fs } from 'node:fs';\nimport * as path from 'node:path';\nimport { isModuleArtifactPathAllowed, MODULE_BOUNDARY_LAYERS, type ModuleManifest } from '@wildo-ai/platform-config-lib';\nimport { ModulePackagerService, type StagingPaths } from './module-packager.service';\n\nexport interface AdoptionPlan {\n /** Per-layer file list, relative to saas root. */\n files: readonly string[];\n /** Empty when every declared boundary contributed at least one file. */\n emptyBoundaries: readonly string[];\n}\n\nexport interface AdoptionResult {\n manifest: ModuleManifest;\n staging: StagingPaths;\n /** Files that were copied into the staging root. */\n staged: readonly string[];\n /** sha256 digest of the staged tree (matches `Sha256DigestSchema`). */\n digest: string;\n}\n\nasync function fileExists(absPath: string): Promise<boolean> {\n try {\n const stat = await fs.stat(absPath);\n return stat.isFile() || stat.isDirectory();\n } catch {\n return false;\n }\n}\n\nasync function walkBoundary(\n saasRoot: string,\n appRelative: string,\n): Promise<string[]> {\n const abs = path.resolve(saasRoot, appRelative);\n if (!(await fileExists(abs))) return [];\n const stat = await fs.stat(abs);\n if (stat.isFile()) return [appRelative];\n const out: string[] = [];\n const queue: string[] = [appRelative];\n while (queue.length > 0) {\n const cur = queue.shift()!;\n const entries = await fs.readdir(path.resolve(saasRoot, cur), { withFileTypes: true });\n for (const entry of entries) {\n const rel = path.join(cur, entry.name);\n // Skip what a module never ships, exactly as `wildo registry publish` does — otherwise the\n // packager (which refuses those paths) throws on a module's own `.env` or `node_modules`.\n if (!isModuleArtifactPathAllowed(rel.split(path.sep).join('/'))) continue;\n if (entry.isDirectory()) {\n queue.push(rel);\n } else if (entry.isFile()) {\n out.push(rel);\n }\n }\n }\n return out;\n}\n\nexport const ModuleAdopterService = {\n /**\n * Walk every declared `boundaries.*` allow-list and return the\n * files that actually exist on disk.\n */\n async discoverModuleFiles(\n saasRoot: string,\n manifest: ModuleManifest,\n ): Promise<AdoptionPlan> {\n const boundaries = manifest.boundaries;\n if (!boundaries) {\n return { files: [], emptyBoundaries: [] };\n }\n // Every layer the registry knows, not a hand-kept copy: a copy here missed `providers` (#2014).\n const layers = MODULE_BOUNDARY_LAYERS;\n const aggregate = new Set<string>();\n const empty: string[] = [];\n for (const layer of layers) {\n const allows = boundaries[layer];\n let layerCount = 0;\n for (const rel of allows) {\n const collected = await walkBoundary(saasRoot, rel);\n for (const file of collected) aggregate.add(file);\n layerCount += collected.length;\n }\n if (allows.length > 0 && layerCount === 0) empty.push(layer);\n }\n return {\n files: [...aggregate].sort(),\n emptyBoundaries: empty,\n };\n },\n\n /**\n * Stage the module from existing app source. Idempotent — calling\n * twice with the same source produces the same digest.\n */\n async adoptToStaging(\n saasRoot: string,\n manifest: ModuleManifest,\n ): Promise<AdoptionResult> {\n const plan = await this.discoverModuleFiles(saasRoot, manifest);\n const { staging, written } = await this.runAdoptionPlan(saasRoot, manifest, plan);\n const digest = await ModulePackagerService.computeStagingDigest(saasRoot, manifest.id);\n return { manifest, staging, staged: written, digest };\n },\n\n /**\n * Re-usable apply step: writes manifest + copies every file in\n * the plan. Exposed so Phase 6 publish can reuse it after a\n * separate staging-prep step.\n */\n async runAdoptionPlan(\n saasRoot: string,\n manifest: ModuleManifest,\n plan: AdoptionPlan,\n ): Promise<{ staging: StagingPaths; written: readonly string[] }> {\n const staging = await ModulePackagerService.stageFresh(saasRoot, manifest.id);\n await ModulePackagerService.writeManifest({ saasRoot, manifest });\n const written: string[] = [];\n for (const appRelativePath of plan.files) {\n const result = await ModulePackagerService.copyFile({\n saasRoot,\n moduleId: manifest.id,\n appRelativePath,\n manifest,\n });\n written.push(result.appRelativePath);\n }\n return { staging, written };\n },\n} as const;\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"module-packager.service.d.ts","sourceRoot":"","sources":["../../../../src/services/module-packager.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAKH,OAAO,EAGL,KAAK,cAAc,EACnB,KAAK,wBAAwB,EAC9B,MAAM,+BAA+B,CAAC;AAEvC,eAAO,MAAM,8BAA8B,4CAA4C,CAAC;AAGxF,MAAM,WAAW,YAAY;IAC3B,oDAAoD;IACpD,WAAW,EAAE,MAAM,CAAC;IACpB,kEAAkE;IAClE,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,YAAY,CAmBtF;AA+BD;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,eAAe,EAAE,MAAM,EACvB,UAAU,EAAE,wBAAwB,GAAG,SAAS,GAC/C,MAAM,CAsBR;AAED,eAAO,MAAM,qBAAqB;IAChC;;OAEG;+BACa,MAAM,YAAY,MAAM,KAAG,YAAY;IAIvD;;;;OAIG;oCACwB,MAAM,YAAY,MAAM,KAAG,OAAO,CAAC,YAAY,CAAC;IAO3E;;;;;;;;OAQG;8BACkB;QACnB,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,eAAe,EAAE,MAAM,CAAC;QACxB,QAAQ,EAAE,cAAc,CAAC;KAC1B,KAAG,OAAO,CAAC;QAAE,cAAc,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,CAAA;KAAE,CAAC;IAehE;;;;OAIG;mCACuB;QACxB,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,cAAc,CAAC;KAC1B,KAAG,OAAO,CAAC;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,cAAc,CAAA;KAAE,CAAC;IAQ/D;;;;;;;;;;;;OAYG;8CACkC,MAAM,YAAY,MAAM,KAAG,OAAO,CAAC,MAAM,CAAC;CAmBvE,CAAC"}
1
+ {"version":3,"file":"module-packager.service.d.ts","sourceRoot":"","sources":["../../../src/services/module-packager.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAKH,OAAO,EAML,KAAK,cAAc,EACnB,KAAK,wBAAwB,EAE9B,MAAM,+BAA+B,CAAC;AAEvC,eAAO,MAAM,8BAA8B,4CAA4C,CAAC;AAExF,MAAM,WAAW,YAAY;IAC3B,oDAAoD;IACpD,WAAW,EAAE,MAAM,CAAC;IACpB,kEAAkE;IAClE,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,YAAY,CAyBtF;AA+BD;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,eAAe,EAAE,MAAM,EACvB,UAAU,EAAE,wBAAwB,GAAG,SAAS,GAC/C,MAAM,CAgBR;AAED,eAAO,MAAM,qBAAqB;IAChC;;OAEG;+BACa,MAAM,YAAY,MAAM,KAAG,YAAY;IAIvD;;;;OAIG;oCACwB,MAAM,YAAY,MAAM,KAAG,OAAO,CAAC,YAAY,CAAC;IAO3E;;;;;;;;OAQG;8BACkB;QACnB,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,eAAe,EAAE,MAAM,CAAC;QACxB,QAAQ,EAAE,cAAc,CAAC;KAC1B,KAAG,OAAO,CAAC;QAAE,cAAc,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,CAAA;KAAE,CAAC;IAehE;;;;OAIG;mCACuB;QACxB,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,cAAc,CAAC;KAC1B,KAAG,OAAO,CAAC;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,cAAc,CAAA;KAAE,CAAC;IAQ/D;;;;;;;;;;;;OAYG;8CACkC,MAAM,YAAY,MAAM,KAAG,OAAO,CAAC,MAAM,CAAC;CAmBvE,CAAC"}
@@ -29,15 +29,16 @@
29
29
  import { createHash } from 'node:crypto';
30
30
  import { promises as fs } from 'node:fs';
31
31
  import * as path from 'node:path';
32
- import { ModuleManifestSchema, RegistryBoundaryViolationError, } from '@wildo-ai/platform-config-lib';
32
+ import { flattenModuleBoundaries, isModuleArtifactPathAllowed, isModuleId, ModuleManifestSchema, RegistryBoundaryViolationError, MODULE_MANIFEST_FILE_NAME as MANIFEST_FILE_NAME, } from '@wildo-ai/platform-config-lib';
33
33
  export const MODULES_ARTIFACTS_RELATIVE_DIR = '.wildo-saas/generated/modules-artifacts';
34
- const MANIFEST_FILE_NAME = 'wildo.module.json';
35
34
  /**
36
35
  * Resolve the per-module staging paths. Pure path arithmetic — no
37
36
  * fs side effects.
38
37
  */
39
38
  export function getModuleStagingPaths(saasRoot, moduleId) {
40
- if (!moduleId.includes('/')) {
39
+ // The full `<scope>/<name>` shape, not merely "contains a `/`": `stageFresh` runs a recursive delete
40
+ // on the path built here, and `a/../../../..` contained a slash.
41
+ if (!isModuleId(moduleId)) {
41
42
  // C-L3 (4th-pass review): `TypeError` is Node's standard for
42
43
  // argument-shape violations. The typed `RegistryError` subclasses
43
44
  // model registry-runtime conditions (auth, integrity, version
@@ -46,9 +47,13 @@ export function getModuleStagingPaths(saasRoot, moduleId) {
46
47
  // handlers should NOT catch. `TypeError` keeps the error visible
47
48
  // and stops the typed-error vocabulary from absorbing pure
48
49
  // argument-validation concerns.
49
- throw new TypeError(`Module id "${moduleId}" must be in <domain>/<name> form (e.g. "acme/billing").`);
50
+ throw new TypeError(`Module id "${moduleId}" must be in <scope>/<name> form (e.g. "acme/billing"), each half lowercase letters, digits, ".", "_" or "-".`);
51
+ }
52
+ const artifactsRoot = path.resolve(saasRoot, MODULES_ARTIFACTS_RELATIVE_DIR);
53
+ const stagingRoot = path.resolve(artifactsRoot, moduleId);
54
+ if (!isPathInside(stagingRoot, artifactsRoot) || stagingRoot === artifactsRoot) {
55
+ throw new TypeError(`Module id "${moduleId}" resolves outside ${MODULES_ARTIFACTS_RELATIVE_DIR}.`);
50
56
  }
51
- const stagingRoot = path.resolve(saasRoot, MODULES_ARTIFACTS_RELATIVE_DIR, moduleId);
52
57
  return {
53
58
  stagingRoot,
54
59
  manifestPath: path.join(stagingRoot, MANIFEST_FILE_NAME),
@@ -90,22 +95,14 @@ function isPathInside(child, parent) {
90
95
  */
91
96
  export function assertWithinBoundaries(saasRoot, moduleId, appRelativePath, boundaries) {
92
97
  const absolute = path.resolve(saasRoot, appRelativePath);
93
- // Path must stay inside saasRoot (no `..` escapes).
94
- if (!isPathInside(absolute, path.resolve(saasRoot))) {
98
+ // Path must stay inside saasRoot (no `..` escapes), and be a path a module artifact may carry at
99
+ // all — no dot-segment (`.env`, `.git`), no `node_modules` — whatever the boundaries say.
100
+ if (!isPathInside(absolute, path.resolve(saasRoot)) || !isModuleArtifactPathAllowed(appRelativePath.split(path.sep).join('/'))) {
95
101
  throw new RegistryBoundaryViolationError(moduleId, appRelativePath);
96
102
  }
97
- if (!boundaries)
98
- return absolute;
99
- const allowed = [
100
- ...boundaries.specifications,
101
- ...boundaries.shared,
102
- ...boundaries.frontend,
103
- ...boundaries.backend,
104
- ...boundaries.minions,
105
- ...boundaries.workers,
106
- ];
107
- if (allowed.length === 0)
108
- return absolute;
103
+ // No declared boundary means the module may write NOWHERE. This used to return early and admit
104
+ // every path in the application — the fail-open reading of an absent declaration.
105
+ const allowed = flattenModuleBoundaries(boundaries);
109
106
  // The candidate must be inside at least one declared boundary.
110
107
  const isInside = allowed.some((rel) => isPathInside(absolute, path.resolve(saasRoot, rel)));
111
108
  if (!isInside) {
@@ -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;