@wildo-ai/wildo-module-client 1.1.3 → 1.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/__tests__/lockfile-repair.service.test.d.ts +0 -0
- package/dist/esm/__tests__/lockfile-repair.service.test.d.ts.map +0 -0
- package/dist/esm/__tests__/lockfile-repair.service.test.js.map +0 -0
- package/dist/esm/__tests__/module-adopter.service.test.d.ts +0 -0
- package/dist/esm/__tests__/module-adopter.service.test.d.ts.map +0 -0
- package/dist/esm/__tests__/module-adopter.service.test.js.map +0 -0
- package/dist/esm/__tests__/module-packager.service.test.d.ts +0 -0
- package/dist/esm/__tests__/module-packager.service.test.d.ts.map +0 -0
- package/dist/esm/__tests__/module-packager.service.test.js.map +0 -0
- package/dist/esm/__tests__/provider-materialization-hook.test.d.ts +0 -0
- package/dist/esm/__tests__/provider-materialization-hook.test.d.ts.map +0 -0
- package/dist/esm/__tests__/provider-materialization-hook.test.js.map +0 -0
- package/dist/esm/__tests__/provider-sync-runner.test.d.ts +0 -0
- package/dist/esm/__tests__/provider-sync-runner.test.d.ts.map +0 -0
- package/dist/esm/__tests__/provider-sync-runner.test.js.map +0 -0
- package/dist/esm/__tests__/registry-lockfile.service.test.d.ts +0 -0
- package/dist/esm/__tests__/registry-lockfile.service.test.d.ts.map +0 -0
- package/dist/esm/__tests__/registry-lockfile.service.test.js.map +0 -0
- package/dist/esm/__tests__/registry-server-client.test.d.ts +0 -0
- package/dist/esm/__tests__/registry-server-client.test.d.ts.map +0 -0
- package/dist/esm/__tests__/registry-server-client.test.js.map +0 -0
- package/dist/esm/services/lockfile-repair.service.d.ts +8 -23
- package/dist/esm/services/lockfile-repair.service.d.ts.map +1 -1
- package/dist/esm/services/lockfile-repair.service.js +10 -28
- package/dist/esm/services/lockfile-repair.service.js.map +1 -1
- package/dist/esm/services/registry-companion-client.d.ts +26 -4
- package/dist/esm/services/registry-companion-client.d.ts.map +1 -1
- package/dist/esm/services/registry-companion-client.js +15 -1
- package/dist/esm/services/registry-companion-client.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/__tests__/lockfile-repair.service.test.ts +2 -2
- package/src/services/lockfile-repair.service.ts +9 -34
- package/src/services/registry-companion-client.ts +40 -6
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -1,21 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `LockfileRepairService` —
|
|
2
|
+
* `LockfileRepairService` — read-only lockfile diagnostics.
|
|
3
3
|
*
|
|
4
|
-
* Backs `wildo registry repair-lockfile
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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
|
-
*
|
|
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
|
|
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` —
|
|
2
|
+
* `LockfileRepairService` — read-only lockfile diagnostics.
|
|
3
3
|
*
|
|
4
|
-
* Backs `wildo registry repair-lockfile
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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,
|
|
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
|
-
*
|
|
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"]}
|
|
@@ -13,12 +13,34 @@
|
|
|
13
13
|
* `platform/factory/wildo-dev-companion/src/companion-api/controllers/`)
|
|
14
14
|
* implements them with the actual ts-morph + filesystem work in
|
|
15
15
|
* Phase 5+.
|
|
16
|
+
*
|
|
17
|
+
* The four mutations are GATED: the companion refuses any non-`GET` route that does not present the
|
|
18
|
+
* machine-local token, so they take `RegistryCompanionMutationOptions` and `ping` — a `GET` — does
|
|
19
|
+
* not. See that type for why the credential is required rather than optional.
|
|
16
20
|
*/
|
|
17
21
|
import { type RegistryInstallRequest, type RegistryPublishRequest, type RegistryRemoveRequest, type RegistryRouteResult, type RegistryUpdateRequest } from '@wildo-ai/platform-config-lib';
|
|
18
22
|
export interface RegistryCompanionClientOptions {
|
|
19
23
|
baseUrl: string;
|
|
20
24
|
timeoutMs?: number;
|
|
21
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* What a MUTATING companion registry call needs, over and above an address.
|
|
28
|
+
*
|
|
29
|
+
* `companionToken` is REQUIRED, and that is the whole point of this second type. Every non-`GET`
|
|
30
|
+
* route under `/api/companion/**` is refused without it, and the four methods below have no caller
|
|
31
|
+
* yet (the routes answer a not-implemented envelope until Phase 5) — so making it required now costs
|
|
32
|
+
* nothing and means the caller Phase 5 writes cannot be written without the credential. An optional
|
|
33
|
+
* field would have been a rule to remember; a required one is a compile error.
|
|
34
|
+
*
|
|
35
|
+
* The value is the content of `<appRoot>/.wildo-saas/state/companion.token` (mode 0600), which the
|
|
36
|
+
* companion mints on every boot. This package has no notion of an application root, so reading it is
|
|
37
|
+
* deliberately the caller's job: `wildo-cli` already owns a hardened no-follow reader
|
|
38
|
+
* (`readCompanionTokenNoFollow`), and a second copy of a permission-checked read is precisely where
|
|
39
|
+
* the checks quietly diverge.
|
|
40
|
+
*/
|
|
41
|
+
export interface RegistryCompanionMutationOptions extends RegistryCompanionClientOptions {
|
|
42
|
+
companionToken: string;
|
|
43
|
+
}
|
|
22
44
|
export declare const RegistryCompanionClient: {
|
|
23
45
|
/**
|
|
24
46
|
* Liveness probe against `/health` (the same endpoint the
|
|
@@ -31,9 +53,9 @@ export declare const RegistryCompanionClient: {
|
|
|
31
53
|
* the three `modules-registry.*.ts` files, refresh
|
|
32
54
|
* `sections.modules`, run provider sync.
|
|
33
55
|
*/
|
|
34
|
-
readonly install: (options:
|
|
35
|
-
readonly update: (options:
|
|
36
|
-
readonly remove: (options:
|
|
37
|
-
readonly publish: (options:
|
|
56
|
+
readonly install: (options: RegistryCompanionMutationOptions, request: RegistryInstallRequest) => Promise<RegistryRouteResult>;
|
|
57
|
+
readonly update: (options: RegistryCompanionMutationOptions, request: RegistryUpdateRequest) => Promise<RegistryRouteResult>;
|
|
58
|
+
readonly remove: (options: RegistryCompanionMutationOptions, request: RegistryRemoveRequest) => Promise<RegistryRouteResult>;
|
|
59
|
+
readonly publish: (options: RegistryCompanionMutationOptions, request: RegistryPublishRequest) => Promise<RegistryRouteResult>;
|
|
38
60
|
};
|
|
39
61
|
//# sourceMappingURL=registry-companion-client.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry-companion-client.d.ts","sourceRoot":"","sources":["../../../../src/services/registry-companion-client.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"registry-companion-client.d.ts","sourceRoot":"","sources":["../../../../src/services/registry-companion-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAKL,KAAK,sBAAsB,EAC3B,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,EAC3B,MAAM,+BAA+B,CAAC;AAiBvC,MAAM,WAAW,8BAA8B;IAC7C,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,gCAAiC,SAAQ,8BAA8B;IACtF,cAAc,EAAE,MAAM,CAAC;CACxB;AA2CD,eAAO,MAAM,uBAAuB;IAClC;;;OAGG;6BACiB,8BAA8B,KAAG,OAAO,CAAC,OAAO,CAAC;IAcrE;;;;;OAKG;gCAEQ,gCAAgC,WAChC,sBAAsB,KAC9B,OAAO,CAAC,mBAAmB,CAAC;+BAMpB,gCAAgC,WAChC,qBAAqB,KAC7B,OAAO,CAAC,mBAAmB,CAAC;+BAMpB,gCAAgC,WAChC,qBAAqB,KAC7B,OAAO,CAAC,mBAAmB,CAAC;gCAMpB,gCAAgC,WAChC,sBAAsB,KAC9B,OAAO,CAAC,mBAAmB,CAAC;CAIvB,CAAC"}
|
|
@@ -13,16 +13,30 @@
|
|
|
13
13
|
* `platform/factory/wildo-dev-companion/src/companion-api/controllers/`)
|
|
14
14
|
* implements them with the actual ts-morph + filesystem work in
|
|
15
15
|
* Phase 5+.
|
|
16
|
+
*
|
|
17
|
+
* The four mutations are GATED: the companion refuses any non-`GET` route that does not present the
|
|
18
|
+
* machine-local token, so they take `RegistryCompanionMutationOptions` and `ping` — a `GET` — does
|
|
19
|
+
* not. See that type for why the credential is required rather than optional.
|
|
16
20
|
*/
|
|
17
21
|
import { REGISTRY_COMPANION_ROUTES, RegistryMalformedPayloadError, RegistryRouteResultSchema, RegistryUnexpectedStatusError, } from '@wildo-ai/platform-config-lib';
|
|
18
22
|
import { fetchWithTimeout, joinUrl } from './_http-client.js';
|
|
19
23
|
import { formatZodIssuesAsSingleLine } from './_zod-issue-formatting.js';
|
|
20
24
|
const DIAGNOSTIC_LABEL = 'Companion registry';
|
|
25
|
+
/**
|
|
26
|
+
* The header the companion's P-29b token floor reads on a mutating route.
|
|
27
|
+
*
|
|
28
|
+
* A wire contract between two processes that share no package, so it is named at both ends rather
|
|
29
|
+
* than at neither. Its server twin is `COMPANION_TOKEN_HEADER` in the companion's
|
|
30
|
+
* `core/auth/companion-auth-floor.companion.service.ts`; the CLI's is in
|
|
31
|
+
* `wildo-cli/src/services/companion/companion-local-token.utils.ts`. Move one, move all three.
|
|
32
|
+
*/
|
|
33
|
+
// semantic-literal-boundary: private companion HTTP protocol, translated only at this client edge.
|
|
34
|
+
const COMPANION_TOKEN_HEADER = 'x-wildo-companion-token';
|
|
21
35
|
async function postJson(options, routePath, body) {
|
|
22
36
|
const url = joinUrl(options.baseUrl, routePath);
|
|
23
37
|
const res = await fetchWithTimeout(url, {
|
|
24
38
|
method: 'POST',
|
|
25
|
-
headers: { 'content-type': 'application/json' },
|
|
39
|
+
headers: { 'content-type': 'application/json', [COMPANION_TOKEN_HEADER]: options.companionToken },
|
|
26
40
|
body: JSON.stringify(body),
|
|
27
41
|
timeoutMs: options.timeoutMs,
|
|
28
42
|
diagnosticLabel: DIAGNOSTIC_LABEL,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry-companion-client.js","sourceRoot":"","sources":["../../../../src/services/registry-companion-client.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `RegistryCompanionClient` — HTTP client for the local dev\n * companion's registry routes.\n *\n * Distinct from `RegistryServerClient` (which talks to the central\n * `platform-module-registry` server). The companion client targets\n * `/api/companion/registry/*` on `http://localhost:<companion-port>`\n * for AST/source mutations the CLI delegates to the companion\n * (KD-6).\n *\n * Phase 2 ships the four mutation routes as method calls; the\n * companion controller (under\n * `platform/factory/wildo-dev-companion/src/companion-api/controllers/`)\n * implements them with the actual ts-morph + filesystem work in\n * Phase 5+.\n */\n\nimport {\n REGISTRY_COMPANION_ROUTES,\n RegistryMalformedPayloadError,\n RegistryRouteResultSchema,\n RegistryUnexpectedStatusError,\n type RegistryInstallRequest,\n type RegistryPublishRequest,\n type RegistryRemoveRequest,\n type RegistryRouteResult,\n type RegistryUpdateRequest,\n} from '@wildo-ai/platform-config-lib';\nimport { fetchWithTimeout, joinUrl } from './_http-client';\nimport { formatZodIssuesAsSingleLine } from './_zod-issue-formatting';\n\nconst DIAGNOSTIC_LABEL = 'Companion registry';\n\nexport interface RegistryCompanionClientOptions {\n baseUrl: string;\n timeoutMs?: number;\n}\n\nasync function postJson<R>(\n options: RegistryCompanionClientOptions,\n routePath: string,\n body: unknown,\n): Promise<R> {\n const url = joinUrl(options.baseUrl, routePath);\n const res = await fetchWithTimeout(url, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n timeoutMs: options.timeoutMs,\n diagnosticLabel: DIAGNOSTIC_LABEL,\n });\n if (!res.ok) {\n // 3rd-pass M5: typed `RegistryUnexpectedStatusError` instead of\n // bare `new Error(...)` so callers can `isRegistryError(err)` and\n // discriminate by status code without re-parsing message text.\n // The companion deliberately reuses the same error vocabulary\n // as `RegistryServerClient` since the wire shape (status + URL +\n // action) is identical even though the lane is different.\n throw new RegistryUnexpectedStatusError(\n options.baseUrl,\n res.status,\n res.statusText,\n `companion ${routePath}`,\n );\n }\n // 6th-pass M3: typed JSON-parse — a misconfigured proxy returning\n // HTML at 2xx would otherwise throw a bare `SyntaxError` that\n // bypasses the typed-error contract.\n try {\n return (await res.json()) as R;\n } catch (err) {\n throw new RegistryMalformedPayloadError(\n options.baseUrl,\n `companion ${routePath}`,\n `response is not valid JSON: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n}\n\nexport const RegistryCompanionClient = {\n /**\n * Liveness probe against `/health` (the same endpoint the\n * dev-supervisor monitors). Used by `wildo registry ping`.\n */\n async ping(options: RegistryCompanionClientOptions): Promise<boolean> {\n const url = joinUrl(options.baseUrl, '/health');\n try {\n const res = await fetchWithTimeout(url, {\n method: 'GET',\n timeoutMs: options.timeoutMs,\n diagnosticLabel: DIAGNOSTIC_LABEL,\n });\n return res.ok;\n } catch {\n return false;\n }\n },\n\n /**\n * Ask the companion to install a module: download bytes, copy\n * source, mutate `wildo.saas.config.ts > moduleRegistries` +\n * the three `modules-registry.*.ts` files, refresh\n * `sections.modules`, run provider sync.\n */\n async install(\n options: RegistryCompanionClientOptions,\n request: RegistryInstallRequest,\n ): Promise<RegistryRouteResult> {\n const raw = await postJson<unknown>(options, REGISTRY_COMPANION_ROUTES.install, request);\n return parseCompanionResult(options.baseUrl, REGISTRY_COMPANION_ROUTES.install, raw);\n },\n\n async update(\n options: RegistryCompanionClientOptions,\n request: RegistryUpdateRequest,\n ): Promise<RegistryRouteResult> {\n const raw = await postJson<unknown>(options, REGISTRY_COMPANION_ROUTES.update, request);\n return parseCompanionResult(options.baseUrl, REGISTRY_COMPANION_ROUTES.update, raw);\n },\n\n async remove(\n options: RegistryCompanionClientOptions,\n request: RegistryRemoveRequest,\n ): Promise<RegistryRouteResult> {\n const raw = await postJson<unknown>(options, REGISTRY_COMPANION_ROUTES.remove, request);\n return parseCompanionResult(options.baseUrl, REGISTRY_COMPANION_ROUTES.remove, raw);\n },\n\n async publish(\n options: RegistryCompanionClientOptions,\n request: RegistryPublishRequest,\n ): Promise<RegistryRouteResult> {\n const raw = await postJson<unknown>(options, REGISTRY_COMPANION_ROUTES.publish, request);\n return parseCompanionResult(options.baseUrl, REGISTRY_COMPANION_ROUTES.publish, raw);\n },\n} as const;\n\n/**\n * M5: thin wrapper around `RegistryRouteResultSchema.parse(raw)` that\n * converts a Zod parse failure into the typed\n * `RegistryMalformedPayloadError` instead of letting a bare `ZodError`\n * bubble up. Keeps the typed-handler contract aligned with\n * `RegistryServerClient`.\n */\nfunction parseCompanionResult(\n baseUrl: string,\n routePath: string,\n raw: unknown,\n): RegistryRouteResult {\n const parsed = RegistryRouteResultSchema.safeParse(raw);\n if (!parsed.success) {\n // 7th-pass C-L2: format the issues array for operator\n // readability instead of using Zod 4's JSON-stringified\n // `error.message`.\n const detail = formatZodIssuesAsSingleLine(parsed.error);\n throw new RegistryMalformedPayloadError(\n baseUrl,\n `companion ${routePath}`,\n detail,\n );\n }\n return parsed.data;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"registry-companion-client.js","sourceRoot":"","sources":["../../../../src/services/registry-companion-client.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `RegistryCompanionClient` — HTTP client for the local dev\n * companion's registry routes.\n *\n * Distinct from `RegistryServerClient` (which talks to the central\n * `platform-module-registry` server). The companion client targets\n * `/api/companion/registry/*` on `http://localhost:<companion-port>`\n * for AST/source mutations the CLI delegates to the companion\n * (KD-6).\n *\n * Phase 2 ships the four mutation routes as method calls; the\n * companion controller (under\n * `platform/factory/wildo-dev-companion/src/companion-api/controllers/`)\n * implements them with the actual ts-morph + filesystem work in\n * Phase 5+.\n *\n * The four mutations are GATED: the companion refuses any non-`GET` route that does not present the\n * machine-local token, so they take `RegistryCompanionMutationOptions` and `ping` — a `GET` — does\n * not. See that type for why the credential is required rather than optional.\n */\n\nimport {\n REGISTRY_COMPANION_ROUTES,\n RegistryMalformedPayloadError,\n RegistryRouteResultSchema,\n RegistryUnexpectedStatusError,\n type RegistryInstallRequest,\n type RegistryPublishRequest,\n type RegistryRemoveRequest,\n type RegistryRouteResult,\n type RegistryUpdateRequest,\n} from '@wildo-ai/platform-config-lib';\nimport { fetchWithTimeout, joinUrl } from './_http-client';\nimport { formatZodIssuesAsSingleLine } from './_zod-issue-formatting';\n\nconst DIAGNOSTIC_LABEL = 'Companion registry';\n\n/**\n * The header the companion's P-29b token floor reads on a mutating route.\n *\n * A wire contract between two processes that share no package, so it is named at both ends rather\n * than at neither. Its server twin is `COMPANION_TOKEN_HEADER` in the companion's\n * `core/auth/companion-auth-floor.companion.service.ts`; the CLI's is in\n * `wildo-cli/src/services/companion/companion-local-token.utils.ts`. Move one, move all three.\n */\n// semantic-literal-boundary: private companion HTTP protocol, translated only at this client edge.\nconst COMPANION_TOKEN_HEADER = 'x-wildo-companion-token';\n\nexport interface RegistryCompanionClientOptions {\n baseUrl: string;\n timeoutMs?: number;\n}\n\n/**\n * What a MUTATING companion registry call needs, over and above an address.\n *\n * `companionToken` is REQUIRED, and that is the whole point of this second type. Every non-`GET`\n * route under `/api/companion/**` is refused without it, and the four methods below have no caller\n * yet (the routes answer a not-implemented envelope until Phase 5) — so making it required now costs\n * nothing and means the caller Phase 5 writes cannot be written without the credential. An optional\n * field would have been a rule to remember; a required one is a compile error.\n *\n * The value is the content of `<appRoot>/.wildo-saas/state/companion.token` (mode 0600), which the\n * companion mints on every boot. This package has no notion of an application root, so reading it is\n * deliberately the caller's job: `wildo-cli` already owns a hardened no-follow reader\n * (`readCompanionTokenNoFollow`), and a second copy of a permission-checked read is precisely where\n * the checks quietly diverge.\n */\nexport interface RegistryCompanionMutationOptions extends RegistryCompanionClientOptions {\n companionToken: string;\n}\n\nasync function postJson<R>(\n options: RegistryCompanionMutationOptions,\n routePath: string,\n body: unknown,\n): Promise<R> {\n const url = joinUrl(options.baseUrl, routePath);\n const res = await fetchWithTimeout(url, {\n method: 'POST',\n headers: { 'content-type': 'application/json', [COMPANION_TOKEN_HEADER]: options.companionToken },\n body: JSON.stringify(body),\n timeoutMs: options.timeoutMs,\n diagnosticLabel: DIAGNOSTIC_LABEL,\n });\n if (!res.ok) {\n // 3rd-pass M5: typed `RegistryUnexpectedStatusError` instead of\n // bare `new Error(...)` so callers can `isRegistryError(err)` and\n // discriminate by status code without re-parsing message text.\n // The companion deliberately reuses the same error vocabulary\n // as `RegistryServerClient` since the wire shape (status + URL +\n // action) is identical even though the lane is different.\n throw new RegistryUnexpectedStatusError(\n options.baseUrl,\n res.status,\n res.statusText,\n `companion ${routePath}`,\n );\n }\n // 6th-pass M3: typed JSON-parse — a misconfigured proxy returning\n // HTML at 2xx would otherwise throw a bare `SyntaxError` that\n // bypasses the typed-error contract.\n try {\n return (await res.json()) as R;\n } catch (err) {\n throw new RegistryMalformedPayloadError(\n options.baseUrl,\n `companion ${routePath}`,\n `response is not valid JSON: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n}\n\nexport const RegistryCompanionClient = {\n /**\n * Liveness probe against `/health` (the same endpoint the\n * dev-supervisor monitors). Used by `wildo registry ping`.\n */\n async ping(options: RegistryCompanionClientOptions): Promise<boolean> {\n const url = joinUrl(options.baseUrl, '/health');\n try {\n const res = await fetchWithTimeout(url, {\n method: 'GET',\n timeoutMs: options.timeoutMs,\n diagnosticLabel: DIAGNOSTIC_LABEL,\n });\n return res.ok;\n } catch {\n return false;\n }\n },\n\n /**\n * Ask the companion to install a module: download bytes, copy\n * source, mutate `wildo.saas.config.ts > moduleRegistries` +\n * the three `modules-registry.*.ts` files, refresh\n * `sections.modules`, run provider sync.\n */\n async install(\n options: RegistryCompanionMutationOptions,\n request: RegistryInstallRequest,\n ): Promise<RegistryRouteResult> {\n const raw = await postJson<unknown>(options, REGISTRY_COMPANION_ROUTES.install, request);\n return parseCompanionResult(options.baseUrl, REGISTRY_COMPANION_ROUTES.install, raw);\n },\n\n async update(\n options: RegistryCompanionMutationOptions,\n request: RegistryUpdateRequest,\n ): Promise<RegistryRouteResult> {\n const raw = await postJson<unknown>(options, REGISTRY_COMPANION_ROUTES.update, request);\n return parseCompanionResult(options.baseUrl, REGISTRY_COMPANION_ROUTES.update, raw);\n },\n\n async remove(\n options: RegistryCompanionMutationOptions,\n request: RegistryRemoveRequest,\n ): Promise<RegistryRouteResult> {\n const raw = await postJson<unknown>(options, REGISTRY_COMPANION_ROUTES.remove, request);\n return parseCompanionResult(options.baseUrl, REGISTRY_COMPANION_ROUTES.remove, raw);\n },\n\n async publish(\n options: RegistryCompanionMutationOptions,\n request: RegistryPublishRequest,\n ): Promise<RegistryRouteResult> {\n const raw = await postJson<unknown>(options, REGISTRY_COMPANION_ROUTES.publish, request);\n return parseCompanionResult(options.baseUrl, REGISTRY_COMPANION_ROUTES.publish, raw);\n },\n} as const;\n\n/**\n * M5: thin wrapper around `RegistryRouteResultSchema.parse(raw)` that\n * converts a Zod parse failure into the typed\n * `RegistryMalformedPayloadError` instead of letting a bare `ZodError`\n * bubble up. Keeps the typed-handler contract aligned with\n * `RegistryServerClient`.\n */\nfunction parseCompanionResult(\n baseUrl: string,\n routePath: string,\n raw: unknown,\n): RegistryRouteResult {\n const parsed = RegistryRouteResultSchema.safeParse(raw);\n if (!parsed.success) {\n // 7th-pass C-L2: format the issues array for operator\n // readability instead of using Zod 4's JSON-stringified\n // `error.message`.\n const detail = formatZodIssuesAsSingleLine(parsed.error);\n throw new RegistryMalformedPayloadError(\n baseUrl,\n `companion ${routePath}`,\n detail,\n );\n }\n return parsed.data;\n}\n"]}
|