@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":"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,21 +1,12 @@
1
1
  /**
2
- * `LockfileRepairService` — Phase 2 read-only lockfile diagnostics.
2
+ * `LockfileRepairService` — read-only lockfile diagnostics.
3
3
  *
4
- * Backs `wildo registry repair-lockfile` (CLI command lands in a
5
- * later step). The service does NOT mutate the lockfile by
6
- * default — its job is to surface a structured diagnostic the
7
- * operator can act on:
8
- * - validate the canonical envelope shape;
9
- * - validate `sections.modules` against
10
- * `RegistryModulesLockSectionSchema`;
11
- * - report which `installed[*].files` are missing on disk
12
- * (drift between the lockfile and the source tree);
13
- * - report which `installed[*].artifactDigest` strings are not
14
- * `Sha256DigestSchema`-shaped (residue from pre-tightening
15
- * publishes).
16
- *
17
- * Mutating repair is a Phase 7 deliverable — surfacing the gaps is
18
- * what unblocks the operator now.
4
+ * Backs `wildo registry repair-lockfile`. Strict envelope and module-section
5
+ * validation runs before file inspection. Malformed records, including invalid
6
+ * artifact digests, require restoration from a trusted record; they are never
7
+ * admitted into the automatic repair path. Valid records can report missing
8
+ * source files, which the CLI can re-fetch at their recorded exact version.
9
+ * This inspector does not mutate files.
19
10
  *
20
11
  * `i18n` validation is intentionally NOT performed here (KD-15 +
21
12
  * Review-pass-2 / MEDIUM-5): repair is the wrong place to
@@ -39,11 +30,6 @@ export type LockfileRepairFinding = {
39
30
  kind: 'module-file-missing';
40
31
  moduleId: string;
41
32
  filePath: string;
42
- } | {
43
- severity: 'warn';
44
- kind: 'module-digest-not-sha256';
45
- moduleId: string;
46
- observedDigest: string;
47
33
  };
48
34
  export interface LockfileRepairReport {
49
35
  /** Absolute path the report covers. */
@@ -60,8 +46,7 @@ export declare const LockfileRepairService: {
60
46
  */
61
47
  readonly inspect: (saasRoot: string) => Promise<LockfileRepairReport>;
62
48
  /**
63
- * Per-entry diagnostic helper. Exposed so Phase 7 mutating-
64
- * repair flows can reuse the same predicates.
49
+ * Missing-file diagnostics for an entry already validated by the strict section schema.
65
50
  */
66
51
  readonly collectEntryFindings: (saasRoot: string, moduleId: string, entry: RegistryModulesLockEntry, findings: LockfileRepairFinding[]) => Promise<void>;
67
52
  };
@@ -1 +1 @@
1
- {"version":3,"file":"lockfile-repair.service.d.ts","sourceRoot":"","sources":["../../../src/services/lockfile-repair.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAMH,OAAO,EAIL,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,GACD;IACE,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,0BAA0B,CAAC;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;CACxB,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;;;OAGG;8CAES,MAAM,YACN,MAAM,SACT,wBAAwB,YACrB,qBAAqB,EAAE,KAChC,OAAO,CAAC,IAAI,CAAC;CAqBR,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"}
@@ -1,21 +1,12 @@
1
1
  /**
2
- * `LockfileRepairService` — Phase 2 read-only lockfile diagnostics.
2
+ * `LockfileRepairService` — read-only lockfile diagnostics.
3
3
  *
4
- * Backs `wildo registry repair-lockfile` (CLI command lands in a
5
- * later step). The service does NOT mutate the lockfile by
6
- * default — its job is to surface a structured diagnostic the
7
- * operator can act on:
8
- * - validate the canonical envelope shape;
9
- * - validate `sections.modules` against
10
- * `RegistryModulesLockSectionSchema`;
11
- * - report which `installed[*].files` are missing on disk
12
- * (drift between the lockfile and the source tree);
13
- * - report which `installed[*].artifactDigest` strings are not
14
- * `Sha256DigestSchema`-shaped (residue from pre-tightening
15
- * publishes).
16
- *
17
- * Mutating repair is a Phase 7 deliverable — surfacing the gaps is
18
- * what unblocks the operator now.
4
+ * Backs `wildo registry repair-lockfile`. Strict envelope and module-section
5
+ * validation runs before file inspection. Malformed records, including invalid
6
+ * artifact digests, require restoration from a trusted record; they are never
7
+ * admitted into the automatic repair path. Valid records can report missing
8
+ * source files, which the CLI can re-fetch at their recorded exact version.
9
+ * This inspector does not mutate files.
19
10
  *
20
11
  * `i18n` validation is intentionally NOT performed here (KD-15 +
21
12
  * Review-pass-2 / MEDIUM-5): repair is the wrong place to
@@ -27,7 +18,7 @@ import { promises as fs } from 'node:fs';
27
18
  import * as path from 'node:path';
28
19
  import { z } from 'zod';
29
20
  import { formatZodIssuesAsSingleLine } from './_zod-issue-formatting.js';
30
- import { RegistryModulesLockSectionSchema, Sha256DigestSchema, getWildoSaasLockPath, } from '@wildo-ai/platform-config-lib';
21
+ import { RegistryModulesLockSectionSchema, getWildoSaasLockPath, } from '@wildo-ai/platform-config-lib';
31
22
  /**
32
23
  * Permissive envelope schema used by repair only — it accepts any
33
24
  * `sections.*` payload without delegating to per-section schemas
@@ -116,7 +107,7 @@ export const LockfileRepairService = {
116
107
  findings.push({
117
108
  severity: 'error',
118
109
  kind: 'modules-section-malformed',
119
- message: '`sections.modules` failed schema validation.',
110
+ message: '`sections.modules` failed schema validation. Restore the affected record from a trusted lockfile revision; automatic repair does not infer missing or invalid metadata.',
120
111
  detail: formatZodIssuesAsSingleLine(parsed.error),
121
112
  });
122
113
  return { lockfilePath, ok: false, findings };
@@ -129,18 +120,9 @@ export const LockfileRepairService = {
129
120
  return { lockfilePath, ok, findings };
130
121
  },
131
122
  /**
132
- * Per-entry diagnostic helper. Exposed so Phase 7 mutating-
133
- * repair flows can reuse the same predicates.
123
+ * Missing-file diagnostics for an entry already validated by the strict section schema.
134
124
  */
135
125
  async collectEntryFindings(saasRoot, moduleId, entry, findings) {
136
- if (!Sha256DigestSchema.safeParse(entry.artifactDigest).success) {
137
- findings.push({
138
- severity: 'warn',
139
- kind: 'module-digest-not-sha256',
140
- moduleId,
141
- observedDigest: entry.artifactDigest,
142
- });
143
- }
144
126
  for (const filePath of entry.files) {
145
127
  const abs = path.resolve(saasRoot, filePath);
146
128
  if (!(await fileExists(abs))) {
@@ -1 +1 @@
1
- {"version":3,"file":"lockfile-repair.service.js","sourceRoot":"","sources":["../../../../src/services/lockfile-repair.service.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `LockfileRepairService` — Phase 2 read-only lockfile diagnostics.\n *\n * Backs `wildo registry repair-lockfile` (CLI command lands in a\n * later step). The service does NOT mutate the lockfile by\n * default — its job is to surface a structured diagnostic the\n * operator can act on:\n * - validate the canonical envelope shape;\n * - validate `sections.modules` against\n * `RegistryModulesLockSectionSchema`;\n * - report which `installed[*].files` are missing on disk\n * (drift between the lockfile and the source tree);\n * - report which `installed[*].artifactDigest` strings are not\n * `Sha256DigestSchema`-shaped (residue from pre-tightening\n * publishes).\n *\n * Mutating repair is a Phase 7 deliverable — surfacing the gaps is\n * what unblocks the operator now.\n *\n * `i18n` validation is intentionally NOT performed here (KD-15 +\n * Review-pass-2 / MEDIUM-5): repair is the wrong place to\n * second-guess companion-owned section semantics. The shared\n * lockfile library STILL validates i18n on the companion-owned\n * read path; this service only owns module diagnostics.\n */\n\nimport { promises as fs } from 'node:fs';\nimport * as path from 'node:path';\nimport { z } from 'zod';\nimport { formatZodIssuesAsSingleLine } from './_zod-issue-formatting';\nimport {\n RegistryModulesLockSectionSchema,\n Sha256DigestSchema,\n getWildoSaasLockPath,\n type RegistryModulesLockEntry,\n type RegistryModulesLockSection,\n} from '@wildo-ai/platform-config-lib';\n\n/**\n * Permissive envelope schema used by repair only — it accepts any\n * `sections.*` payload without delegating to per-section schemas\n * so the repair flow can isolate \"envelope is malformed\" from\n * \"envelope is fine but modules section is malformed\".\n */\nconst RelaxedEnvelopeSchema = z.strictObject({\n schemaVersion: z.literal(2),\n generatedAt: z.iso.datetime({ offset: true }),\n sections: z.record(z.string(), z.unknown()),\n});\n\nexport type LockfileRepairFinding =\n | {\n severity: 'error';\n kind: 'envelope-malformed';\n message: string;\n detail?: string;\n }\n | {\n severity: 'error';\n kind: 'modules-section-malformed';\n message: string;\n detail?: string;\n }\n | {\n severity: 'warn';\n kind: 'module-file-missing';\n moduleId: string;\n filePath: string;\n }\n | {\n severity: 'warn';\n kind: 'module-digest-not-sha256';\n moduleId: string;\n observedDigest: string;\n };\n\nexport interface LockfileRepairReport {\n /** Absolute path the report covers. */\n lockfilePath: string;\n /** True when no errors were produced (warnings allowed). */\n ok: boolean;\n findings: readonly LockfileRepairFinding[];\n}\n\nasync function fileExists(absPath: string): Promise<boolean> {\n try {\n await fs.access(absPath);\n return true;\n } catch {\n return false;\n }\n}\n\nexport const LockfileRepairService = {\n /** Compute the lockfile path for a saas root. */\n pathFor(saasRoot: string): string {\n return getWildoSaasLockPath(saasRoot);\n },\n\n /**\n * Read the envelope + run every diagnostic. Read-only.\n */\n async inspect(saasRoot: string): Promise<LockfileRepairReport> {\n const lockfilePath = getWildoSaasLockPath(saasRoot);\n const findings: LockfileRepairFinding[] = [];\n\n // Read raw bytes ourselves so we can produce per-section\n // diagnostics: a malformed `modules` section under an\n // otherwise-valid envelope should report `modules-section-malformed`,\n // NOT `envelope-malformed` (the latter would tell the operator\n // to re-author the whole file when only one section is bad).\n let raw: string;\n try {\n raw = await fs.readFile(lockfilePath, 'utf8');\n } catch (err: unknown) {\n const errno = (err as NodeJS.ErrnoException)?.code;\n if (errno === 'ENOENT') {\n // Missing lockfile is \"ok\" from a repair POV.\n return { lockfilePath, ok: true, findings: [] };\n }\n findings.push({\n severity: 'error',\n kind: 'envelope-malformed',\n message: 'Failed to read lockfile bytes.',\n detail: err instanceof Error ? err.message : String(err),\n });\n return { lockfilePath, ok: false, findings };\n }\n\n let parsedJson: unknown;\n try {\n parsedJson = JSON.parse(raw);\n } catch (err) {\n findings.push({\n severity: 'error',\n kind: 'envelope-malformed',\n message: 'Lockfile is not valid JSON.',\n detail: err instanceof Error ? err.message : String(err),\n });\n return { lockfilePath, ok: false, findings };\n }\n\n const envelopeParsed = RelaxedEnvelopeSchema.safeParse(parsedJson);\n if (!envelopeParsed.success) {\n findings.push({\n severity: 'error',\n kind: 'envelope-malformed',\n message: 'Top-level envelope failed to parse.',\n detail: formatZodIssuesAsSingleLine(envelopeParsed.error),\n });\n return { lockfilePath, ok: false, findings };\n }\n const envelope = envelopeParsed.data;\n\n const modulesPayload = envelope.sections.modules;\n if (modulesPayload === undefined) {\n // No modules section present — nothing to inspect.\n return { lockfilePath, ok: true, findings };\n }\n\n const parsed = RegistryModulesLockSectionSchema.safeParse(modulesPayload);\n if (!parsed.success) {\n findings.push({\n severity: 'error',\n kind: 'modules-section-malformed',\n message: '`sections.modules` failed schema validation.',\n detail: formatZodIssuesAsSingleLine(parsed.error),\n });\n return { lockfilePath, ok: false, findings };\n }\n const modules: RegistryModulesLockSection = parsed.data;\n\n for (const [moduleId, entry] of Object.entries(modules.installed)) {\n await this.collectEntryFindings(saasRoot, moduleId, entry, findings);\n }\n\n const ok = findings.every((f) => f.severity !== 'error');\n return { lockfilePath, ok, findings };\n },\n\n /**\n * Per-entry diagnostic helper. Exposed so Phase 7 mutating-\n * repair flows can reuse the same predicates.\n */\n async collectEntryFindings(\n saasRoot: string,\n moduleId: string,\n entry: RegistryModulesLockEntry,\n findings: LockfileRepairFinding[],\n ): Promise<void> {\n if (!Sha256DigestSchema.safeParse(entry.artifactDigest).success) {\n findings.push({\n severity: 'warn',\n kind: 'module-digest-not-sha256',\n moduleId,\n observedDigest: entry.artifactDigest,\n });\n }\n for (const filePath of entry.files) {\n const abs = path.resolve(saasRoot, filePath);\n if (!(await fileExists(abs))) {\n findings.push({\n severity: 'warn',\n kind: 'module-file-missing',\n moduleId,\n filePath,\n });\n }\n }\n },\n} as const;\n\n// L6 (third-pass review): re-export removed — barrel-discipline rule\n// says \"Never re-export except in the root `index.ts`.\" Consumers\n// import the symbol directly from `@wildo-ai/platform-config-lib`.\n"]}
1
+ {"version":3,"file":"lockfile-repair.service.js","sourceRoot":"","sources":["../../../../src/services/lockfile-repair.service.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `LockfileRepairService` — read-only lockfile diagnostics.\n *\n * Backs `wildo registry repair-lockfile`. Strict envelope and module-section\n * validation runs before file inspection. Malformed records, including invalid\n * artifact digests, require restoration from a trusted record; they are never\n * admitted into the automatic repair path. Valid records can report missing\n * source files, which the CLI can re-fetch at their recorded exact version.\n * This inspector does not mutate files.\n *\n * `i18n` validation is intentionally NOT performed here (KD-15 +\n * Review-pass-2 / MEDIUM-5): repair is the wrong place to\n * second-guess companion-owned section semantics. The shared\n * lockfile library STILL validates i18n on the companion-owned\n * read path; this service only owns module diagnostics.\n */\n\nimport { promises as fs } from 'node:fs';\nimport * as path from 'node:path';\nimport { z } from 'zod';\nimport { formatZodIssuesAsSingleLine } from './_zod-issue-formatting';\nimport {\n RegistryModulesLockSectionSchema,\n getWildoSaasLockPath,\n type RegistryModulesLockEntry,\n type RegistryModulesLockSection,\n} from '@wildo-ai/platform-config-lib';\n\n/**\n * Permissive envelope schema used by repair only — it accepts any\n * `sections.*` payload without delegating to per-section schemas\n * so the repair flow can isolate \"envelope is malformed\" from\n * \"envelope is fine but modules section is malformed\".\n */\nconst RelaxedEnvelopeSchema = z.strictObject({\n schemaVersion: z.literal(2),\n generatedAt: z.iso.datetime({ offset: true }),\n sections: z.record(z.string(), z.unknown()),\n});\n\nexport type LockfileRepairFinding =\n | {\n severity: 'error';\n kind: 'envelope-malformed';\n message: string;\n detail?: string;\n }\n | {\n severity: 'error';\n kind: 'modules-section-malformed';\n message: string;\n detail?: string;\n }\n | {\n severity: 'warn';\n kind: 'module-file-missing';\n moduleId: string;\n filePath: string;\n };\n\nexport interface LockfileRepairReport {\n /** Absolute path the report covers. */\n lockfilePath: string;\n /** True when no errors were produced (warnings allowed). */\n ok: boolean;\n findings: readonly LockfileRepairFinding[];\n}\n\nasync function fileExists(absPath: string): Promise<boolean> {\n try {\n await fs.access(absPath);\n return true;\n } catch {\n return false;\n }\n}\n\nexport const LockfileRepairService = {\n /** Compute the lockfile path for a saas root. */\n pathFor(saasRoot: string): string {\n return getWildoSaasLockPath(saasRoot);\n },\n\n /**\n * Read the envelope + run every diagnostic. Read-only.\n */\n async inspect(saasRoot: string): Promise<LockfileRepairReport> {\n const lockfilePath = getWildoSaasLockPath(saasRoot);\n const findings: LockfileRepairFinding[] = [];\n\n // Read raw bytes ourselves so we can produce per-section\n // diagnostics: a malformed `modules` section under an\n // otherwise-valid envelope should report `modules-section-malformed`,\n // NOT `envelope-malformed` (the latter would tell the operator\n // to re-author the whole file when only one section is bad).\n let raw: string;\n try {\n raw = await fs.readFile(lockfilePath, 'utf8');\n } catch (err: unknown) {\n const errno = (err as NodeJS.ErrnoException)?.code;\n if (errno === 'ENOENT') {\n // Missing lockfile is \"ok\" from a repair POV.\n return { lockfilePath, ok: true, findings: [] };\n }\n findings.push({\n severity: 'error',\n kind: 'envelope-malformed',\n message: 'Failed to read lockfile bytes.',\n detail: err instanceof Error ? err.message : String(err),\n });\n return { lockfilePath, ok: false, findings };\n }\n\n let parsedJson: unknown;\n try {\n parsedJson = JSON.parse(raw);\n } catch (err) {\n findings.push({\n severity: 'error',\n kind: 'envelope-malformed',\n message: 'Lockfile is not valid JSON.',\n detail: err instanceof Error ? err.message : String(err),\n });\n return { lockfilePath, ok: false, findings };\n }\n\n const envelopeParsed = RelaxedEnvelopeSchema.safeParse(parsedJson);\n if (!envelopeParsed.success) {\n findings.push({\n severity: 'error',\n kind: 'envelope-malformed',\n message: 'Top-level envelope failed to parse.',\n detail: formatZodIssuesAsSingleLine(envelopeParsed.error),\n });\n return { lockfilePath, ok: false, findings };\n }\n const envelope = envelopeParsed.data;\n\n const modulesPayload = envelope.sections.modules;\n if (modulesPayload === undefined) {\n // No modules section present — nothing to inspect.\n return { lockfilePath, ok: true, findings };\n }\n\n const parsed = RegistryModulesLockSectionSchema.safeParse(modulesPayload);\n if (!parsed.success) {\n findings.push({\n severity: 'error',\n kind: 'modules-section-malformed',\n message: '`sections.modules` failed schema validation. Restore the affected record from a trusted lockfile revision; automatic repair does not infer missing or invalid metadata.',\n detail: formatZodIssuesAsSingleLine(parsed.error),\n });\n return { lockfilePath, ok: false, findings };\n }\n const modules: RegistryModulesLockSection = parsed.data;\n\n for (const [moduleId, entry] of Object.entries(modules.installed)) {\n await this.collectEntryFindings(saasRoot, moduleId, entry, findings);\n }\n\n const ok = findings.every((f) => f.severity !== 'error');\n return { lockfilePath, ok, findings };\n },\n\n /**\n * Missing-file diagnostics for an entry already validated by the strict section schema.\n */\n async collectEntryFindings(\n saasRoot: string,\n moduleId: string,\n entry: RegistryModulesLockEntry,\n findings: LockfileRepairFinding[],\n ): Promise<void> {\n for (const filePath of entry.files) {\n const abs = path.resolve(saasRoot, filePath);\n if (!(await fileExists(abs))) {\n findings.push({\n severity: 'warn',\n kind: 'module-file-missing',\n moduleId,\n filePath,\n });\n }\n }\n },\n} as const;\n\n// L6 (third-pass review): re-export removed — barrel-discipline rule\n// says \"Never re-export except in the root `index.ts`.\" Consumers\n// import the symbol directly from `@wildo-ai/platform-config-lib`.\n"]}
@@ -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":"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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 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) {