@cairn-tool/cairn 2.0.1 → 2.1.0

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.
@@ -0,0 +1,315 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { loadBundle } from "../parser.js";
4
+ import { renderBundle } from "../render.js";
5
+ import { readInstallManifest, planInstall } from "../install/index.js";
6
+ import { CONVERSION_REPORT, diffOutput } from "../output.js";
7
+ import { PROFILE_SCHEMA_VERSION, compareSemver } from "../targets/schema.js";
8
+ import { profileFor } from "../targets/index.js";
9
+ import { packageName, packageVersion } from "../../version.js";
10
+ import { diffTree, treeMatches, walkRootsFor } from "./compare.js";
11
+ function error(code, message, extra = {}) {
12
+ return { code, severity: "error", message, quality: "exact", ...extra };
13
+ }
14
+ function notice(code, message, extra = {}) {
15
+ return { code, severity: "notice", message, quality: "exact", ...extra };
16
+ }
17
+ /**
18
+ * Evaluates one bound with a caller-supplied ordering.
19
+ *
20
+ * Documentation revisions are ISO dates compared lexicographically, exactly as
21
+ * `agent doctor` already compares them; CLI versions go through
22
+ * {@link compareSemver}.
23
+ */
24
+ function satisfies(actual, declared, compare) {
25
+ if (declared.exact !== undefined)
26
+ return compare(actual, declared.exact) === 0;
27
+ if (declared.min !== undefined && compare(actual, declared.min) < 0)
28
+ return false;
29
+ if (declared.max !== undefined && compare(actual, declared.max) > 0)
30
+ return false;
31
+ return true;
32
+ }
33
+ function lexical(a, b) {
34
+ return a < b ? -1 : a > b ? 1 : 0;
35
+ }
36
+ function describe(bound) {
37
+ if (bound.exact !== undefined)
38
+ return `exactly ${bound.exact}`;
39
+ return [
40
+ bound.min !== undefined ? `>= ${bound.min}` : undefined,
41
+ bound.max !== undefined ? `<= ${bound.max}` : undefined,
42
+ ]
43
+ .filter(Boolean)
44
+ .join(" and ");
45
+ }
46
+ export function evaluatePins(config, targets) {
47
+ const diagnostics = [];
48
+ const { pins } = config;
49
+ const cli = {
50
+ declared: pins.cli ?? null,
51
+ actual: packageVersion,
52
+ status: !pins.cli
53
+ ? "unpinned"
54
+ : satisfies(packageVersion, pins.cli, compareSemver)
55
+ ? "satisfied"
56
+ : "violated",
57
+ };
58
+ if (cli.status === "violated" && pins.cli)
59
+ diagnostics.push(error("AB420", `Running ${packageName} ${packageVersion} does not satisfy the pinned range (${describe(pins.cli)})`, {
60
+ remediation: "Install a cairn matching the pin, or update the pin and regenerate the trees in the same commit.",
61
+ }));
62
+ const schema = {
63
+ declared: pins.profileSchemaVersion ?? null,
64
+ actual: PROFILE_SCHEMA_VERSION,
65
+ status: !pins.profileSchemaVersion
66
+ ? "unpinned"
67
+ : pins.profileSchemaVersion === PROFILE_SCHEMA_VERSION
68
+ ? "satisfied"
69
+ : "violated",
70
+ };
71
+ if (schema.status === "violated")
72
+ diagnostics.push(error("AB421", `Target profile schema version is ${PROFILE_SCHEMA_VERSION}, but ${pins.profileSchemaVersion} is pinned`, { remediation: "Update the pin and regenerate the trees in the same commit." }));
73
+ const targetReports = [];
74
+ for (const target of targets) {
75
+ const declared = pins.targets[target];
76
+ const actual = profileFor(target).host.documentationRevision;
77
+ const status = !declared
78
+ ? "unpinned"
79
+ : satisfies(actual, declared, lexical)
80
+ ? "satisfied"
81
+ : "violated";
82
+ targetReports.push({ target, declared: declared ?? null, actual, status });
83
+ if (status === "violated" && declared)
84
+ diagnostics.push(error("AB422", `The ${target} target profile revision is ${actual}, which does not satisfy the pinned range (${describe(declared)})`, {
85
+ target,
86
+ remediation: "Update the pin and regenerate the trees in the same commit.",
87
+ }));
88
+ }
89
+ return { report: { cli, profileSchemaVersion: schema, targets: targetReports }, diagnostics };
90
+ }
91
+ function readConversionProvenance(destination) {
92
+ const file = path.join(destination, CONVERSION_REPORT);
93
+ if (!fs.existsSync(file))
94
+ return undefined;
95
+ try {
96
+ const parsed = JSON.parse(fs.readFileSync(file, "utf8"));
97
+ return {
98
+ source: CONVERSION_REPORT,
99
+ generator: parsed.generator ?? null,
100
+ profileSchemaVersion: parsed.profileSchemaVersion ?? null,
101
+ status: "matching",
102
+ };
103
+ }
104
+ catch {
105
+ return {
106
+ source: CONVERSION_REPORT,
107
+ generator: null,
108
+ profileSchemaVersion: null,
109
+ status: "malformed",
110
+ };
111
+ }
112
+ }
113
+ function provenanceOf(manifest) {
114
+ return {
115
+ source: ".cairn-install.json",
116
+ generator: manifest.generator,
117
+ // An install manifest records no profile schema version. Reported as null
118
+ // rather than invented, so a consumer can tell "not recorded" from "2".
119
+ profileSchemaVersion: null,
120
+ status: "matching",
121
+ };
122
+ }
123
+ /**
124
+ * A recorded version this build cannot parse counts as older rather than
125
+ * throwing: provenance is corroboration, and never the verdict.
126
+ */
127
+ function isNewer(recorded) {
128
+ try {
129
+ return compareSemver(recorded, packageVersion) > 0;
130
+ }
131
+ catch {
132
+ return false;
133
+ }
134
+ }
135
+ function gradeProvenance(provenance) {
136
+ if (provenance.status === "malformed" || !provenance.generator)
137
+ return provenance;
138
+ const recorded = provenance.generator.version;
139
+ if (recorded === packageVersion)
140
+ return provenance;
141
+ return { ...provenance, status: isNewer(recorded) ? "newer" : "older" };
142
+ }
143
+ function verifyEntry(entry) {
144
+ const diagnostics = [];
145
+ const base = {
146
+ name: entry.name,
147
+ bundle: entry.bundle,
148
+ target: entry.target,
149
+ profile: entry.profile,
150
+ scope: entry.scope,
151
+ layout: entry.layout,
152
+ destination: entry.destination,
153
+ mode: entry.unmanaged,
154
+ };
155
+ if (!fs.existsSync(entry.destination) || !fs.statSync(entry.destination).isDirectory()) {
156
+ diagnostics.push(error("AB423", `Destination does not exist or is not a directory: ${entry.destination}`, {
157
+ target: entry.target,
158
+ profile: entry.profile,
159
+ path: entry.destination,
160
+ remediation: "Generate the tree, or remove the entry from the verify configuration.",
161
+ }));
162
+ return {
163
+ report: {
164
+ ...base,
165
+ expected: 0,
166
+ missing: [],
167
+ changed: [],
168
+ orphaned: [],
169
+ unmanaged: [],
170
+ ok: false,
171
+ },
172
+ diagnostics,
173
+ };
174
+ }
175
+ const bundle = loadBundle(entry.bundle);
176
+ // A conversion root keeps the `<target>/<profile>` prefix the renderer emits,
177
+ // so it is the one layout `diffOutput` describes and is reused unchanged.
178
+ if (entry.layout === "conversion") {
179
+ const rendered = renderBundle(bundle, [entry.target], [entry.profile]);
180
+ diagnostics.push(...rendered.diagnostics);
181
+ const diff = diffOutput(entry.destination, rendered.artifacts, [entry.target], [entry.profile]);
182
+ const provenance = readConversionProvenance(entry.destination);
183
+ const report = {
184
+ ...base,
185
+ expected: rendered.artifacts.length,
186
+ missing: diff.missing,
187
+ changed: diff.changed,
188
+ orphaned: [],
189
+ unmanaged: entry.unmanaged === "strict" ? diff.unmanaged : [],
190
+ ...(provenance ? { provenance: gradeProvenance(provenance) } : {}),
191
+ ok: false,
192
+ };
193
+ return { report: finish(report, diagnostics, entry), diagnostics };
194
+ }
195
+ const plan = planInstall(bundle, entry.target, {
196
+ scope: entry.scope,
197
+ into: entry.destination,
198
+ profile: entry.profile,
199
+ // The destination is expected to be occupied — that is the whole point —
200
+ // so the occupancy check must not turn into an AB801 error here.
201
+ force: true,
202
+ });
203
+ diagnostics.push(...plan.diagnostics.filter((item) => item.code !== "AB802"));
204
+ const prior = readInstallManifest(plan.destination || entry.destination);
205
+ if (prior === "malformed")
206
+ diagnostics.push(error("AB806", `Install manifest at ${entry.destination} is malformed`, {
207
+ target: entry.target,
208
+ path: entry.destination,
209
+ remediation: "Remove it and reinstall, or repair it by hand.",
210
+ }));
211
+ const inventory = prior !== "missing" && prior !== "malformed" ? prior.files.map((file) => file.path) : undefined;
212
+ if (!inventory && entry.unmanaged !== "off")
213
+ diagnostics.push(notice("AB426", `No install manifest at ${entry.destination}; files this bundle no longer renders cannot be detected`, {
214
+ target: entry.target,
215
+ path: entry.destination,
216
+ remediation: "Reinstall so the inventory is recorded.",
217
+ }));
218
+ const expectedPaths = plan.artifacts.map((artifact) => artifact.path);
219
+ const diff = diffTree(plan.destination || entry.destination, plan.artifacts, {
220
+ unmanaged: entry.unmanaged,
221
+ priorInventory: inventory,
222
+ walkRoots: entry.unmanaged === "strict"
223
+ ? walkRootsFor(entry.target, plan.profile, expectedPaths)
224
+ : undefined,
225
+ });
226
+ const provenance = prior !== "missing" && prior !== "malformed" ? gradeProvenance(provenanceOf(prior)) : undefined;
227
+ const report = {
228
+ ...base,
229
+ profile: plan.profile,
230
+ layout: entry.layout,
231
+ expected: plan.artifacts.length,
232
+ missing: diff.missing,
233
+ changed: diff.changed,
234
+ orphaned: diff.orphaned,
235
+ unmanaged: diff.unmanaged,
236
+ ...(provenance ? { provenance } : {}),
237
+ ok: false,
238
+ };
239
+ return { report: finish(report, diagnostics, entry), diagnostics };
240
+ }
241
+ /** Maps a comparison into findings and decides the entry's own verdict. */
242
+ function finish(report, diagnostics, entry) {
243
+ const where = { target: entry.target, profile: report.profile };
244
+ for (const missing of report.missing)
245
+ diagnostics.push(error("AB402", `Generated output is missing '${missing}'`, {
246
+ ...where,
247
+ path: missing,
248
+ remediation: "Regenerate the tree from the bundle.",
249
+ }));
250
+ for (const changed of report.changed)
251
+ diagnostics.push(error("AB402", `Generated output differs from the bundle at '${changed}'`, {
252
+ ...where,
253
+ path: changed,
254
+ remediation: "Regenerate the tree; edit the bundle rather than the generated tree.",
255
+ }));
256
+ for (const orphaned of report.orphaned)
257
+ diagnostics.push(error("AB424", `'${orphaned}' was generated previously but is no longer rendered`, {
258
+ ...where,
259
+ path: orphaned,
260
+ remediation: "Delete the file, or restore the component to the bundle.",
261
+ }));
262
+ for (const unmanaged of report.unmanaged)
263
+ diagnostics.push({
264
+ code: "AB403",
265
+ severity: "warning",
266
+ quality: "exact",
267
+ message: `Unmanaged file in the generated tree: '${unmanaged}'`,
268
+ ...where,
269
+ path: unmanaged,
270
+ });
271
+ if (report.provenance && report.provenance.status !== "matching" && report.provenance.generator)
272
+ diagnostics.push(notice("AB425", `The tree records generator ${report.provenance.generator.version}, but ${packageVersion} is verifying it`, { ...where, path: report.destination }));
273
+ return {
274
+ ...report,
275
+ ok: !report.missing.length &&
276
+ !report.changed.length &&
277
+ !report.orphaned.length &&
278
+ !report.unmanaged.length,
279
+ };
280
+ }
281
+ export function runVerify(config) {
282
+ const diagnostics = [];
283
+ const targets = [...new Set(config.entries.map((entry) => entry.target))];
284
+ const pins = evaluatePins(config, targets);
285
+ diagnostics.push(...pins.diagnostics);
286
+ const entries = [];
287
+ for (const entry of config.entries) {
288
+ const result = verifyEntry(entry);
289
+ entries.push(result.report);
290
+ diagnostics.push(...result.diagnostics);
291
+ }
292
+ const counts = {
293
+ entries: entries.length,
294
+ ok: entries.filter((entry) => entry.ok).length,
295
+ missing: entries.reduce((total, entry) => total + entry.missing.length, 0),
296
+ changed: entries.reduce((total, entry) => total + entry.changed.length, 0),
297
+ orphaned: entries.reduce((total, entry) => total + entry.orphaned.length, 0),
298
+ unmanaged: entries.reduce((total, entry) => total + entry.unmanaged.length, 0),
299
+ };
300
+ return {
301
+ report: {
302
+ config: { path: config.file, entries: config.entries.length },
303
+ generator: { name: packageName, version: packageVersion },
304
+ profileSchemaVersion: PROFILE_SCHEMA_VERSION,
305
+ pins: pins.report,
306
+ entries,
307
+ counts,
308
+ },
309
+ diagnostics,
310
+ targets,
311
+ profiles: [...new Set(entries.map((entry) => entry.profile))],
312
+ };
313
+ }
314
+ export { treeMatches };
315
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/agent/verify/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEvE,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE7D,OAAO,EAAE,sBAAsB,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC7E,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAG/D,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAuEnE,SAAS,KAAK,CAAC,IAAY,EAAE,OAAe,EAAE,QAAkC,EAAE;IAChF,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,KAAK,EAAqB,CAAC;AAC7F,CAAC;AAED,SAAS,MAAM,CAAC,IAAY,EAAE,OAAe,EAAE,QAAkC,EAAE;IACjF,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,KAAK,EAAqB,CAAC;AAC9F,CAAC;AAED;;;;;;GAMG;AACH,SAAS,SAAS,CAChB,MAAc,EACd,QAAsB,EACtB,OAAyC;IAEzC,IAAI,QAAQ,CAAC,KAAK,KAAK,SAAS;QAAE,OAAO,OAAO,CAAC,MAAM,EAAE,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC/E,IAAI,QAAQ,CAAC,GAAG,KAAK,SAAS,IAAI,OAAO,CAAC,MAAM,EAAE,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IAClF,IAAI,QAAQ,CAAC,GAAG,KAAK,SAAS,IAAI,OAAO,CAAC,MAAM,EAAE,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IAClF,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,OAAO,CAAC,CAAS,EAAE,CAAS;IACnC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACpC,CAAC;AAED,SAAS,QAAQ,CAAC,KAAmB;IACnC,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS;QAAE,OAAO,WAAW,KAAK,CAAC,KAAK,EAAE,CAAC;IAC/D,OAAO;QACL,KAAK,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,SAAS;QACvD,KAAK,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,SAAS;KACxD;SACE,MAAM,CAAC,OAAO,CAAC;SACf,IAAI,CAAC,OAAO,CAAC,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,YAAY,CAC1B,MAAoB,EACpB,OAA+B;IAE/B,MAAM,WAAW,GAAsB,EAAE,CAAC;IAC1C,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,CAAC;IAExB,MAAM,GAAG,GAA4B;QACnC,QAAQ,EAAE,IAAI,CAAC,GAAG,IAAI,IAAI;QAC1B,MAAM,EAAE,cAAc;QACtB,MAAM,EAAE,CAAC,IAAI,CAAC,GAAG;YACf,CAAC,CAAC,UAAU;YACZ,CAAC,CAAC,SAAS,CAAC,cAAc,EAAE,IAAI,CAAC,GAAG,EAAE,aAAa,CAAC;gBAClD,CAAC,CAAC,WAAW;gBACb,CAAC,CAAC,UAAU;KACjB,CAAC;IACF,IAAI,GAAG,CAAC,MAAM,KAAK,UAAU,IAAI,IAAI,CAAC,GAAG;QACvC,WAAW,CAAC,IAAI,CACd,KAAK,CACH,OAAO,EACP,WAAW,WAAW,IAAI,cAAc,uCAAuC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EACpG;YACE,WAAW,EACT,kGAAkG;SACrG,CACF,CACF,CAAC;IAEJ,MAAM,MAAM,GAAsB;QAChC,QAAQ,EAAE,IAAI,CAAC,oBAAoB,IAAI,IAAI;QAC3C,MAAM,EAAE,sBAAsB;QAC9B,MAAM,EAAE,CAAC,IAAI,CAAC,oBAAoB;YAChC,CAAC,CAAC,UAAU;YACZ,CAAC,CAAC,IAAI,CAAC,oBAAoB,KAAK,sBAAsB;gBACpD,CAAC,CAAC,WAAW;gBACb,CAAC,CAAC,UAAU;KACjB,CAAC;IACF,IAAI,MAAM,CAAC,MAAM,KAAK,UAAU;QAC9B,WAAW,CAAC,IAAI,CACd,KAAK,CACH,OAAO,EACP,oCAAoC,sBAAsB,SAAS,IAAI,CAAC,oBAAoB,YAAY,EACxG,EAAE,WAAW,EAAE,6DAA6D,EAAE,CAC/E,CACF,CAAC;IAEJ,MAAM,aAAa,GAAgC,EAAE,CAAC;IACtD,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACtC,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,qBAAqB,CAAC;QAC7D,MAAM,MAAM,GAAc,CAAC,QAAQ;YACjC,CAAC,CAAC,UAAU;YACZ,CAAC,CAAC,SAAS,CAAC,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC;gBACpC,CAAC,CAAC,WAAW;gBACb,CAAC,CAAC,UAAU,CAAC;QACjB,aAAa,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,IAAI,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;QAC3E,IAAI,MAAM,KAAK,UAAU,IAAI,QAAQ;YACnC,WAAW,CAAC,IAAI,CACd,KAAK,CACH,OAAO,EACP,OAAO,MAAM,+BAA+B,MAAM,8CAA8C,QAAQ,CAAC,QAAQ,CAAC,GAAG,EACrH;gBACE,MAAM;gBACN,WAAW,EAAE,6DAA6D;aAC3E,CACF,CACF,CAAC;IACN,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,oBAAoB,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,EAAE,EAAE,WAAW,EAAE,CAAC;AAChG,CAAC;AAED,SAAS,wBAAwB,CAAC,WAAmB;IACnD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,iBAAiB,CAAC,CAAC;IACvD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,SAAS,CAAC;IAC3C,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAkC,CAAC;QAC1F,OAAO;YACL,MAAM,EAAE,iBAAiB;YACzB,SAAS,EAAE,MAAM,CAAC,SAAS,IAAI,IAAI;YACnC,oBAAoB,EAAE,MAAM,CAAC,oBAAoB,IAAI,IAAI;YACzD,MAAM,EAAE,UAAU;SACnB,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO;YACL,MAAM,EAAE,iBAAiB;YACzB,SAAS,EAAE,IAAI;YACf,oBAAoB,EAAE,IAAI;YAC1B,MAAM,EAAE,WAAW;SACpB,CAAC;IACJ,CAAC;AACH,CAAC;AAED,SAAS,YAAY,CAAC,QAAyB;IAC7C,OAAO;QACL,MAAM,EAAE,qBAAqB;QAC7B,SAAS,EAAE,QAAQ,CAAC,SAAS;QAC7B,0EAA0E;QAC1E,wEAAwE;QACxE,oBAAoB,EAAE,IAAI;QAC1B,MAAM,EAAE,UAAU;KACnB,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,SAAS,OAAO,CAAC,QAAgB;IAC/B,IAAI,CAAC;QACH,OAAO,aAAa,CAAC,QAAQ,EAAE,cAAc,CAAC,GAAG,CAAC,CAAC;IACrD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,SAAS,eAAe,CAAC,UAA4B;IACnD,IAAI,UAAU,CAAC,MAAM,KAAK,WAAW,IAAI,CAAC,UAAU,CAAC,SAAS;QAAE,OAAO,UAAU,CAAC;IAClF,MAAM,QAAQ,GAAG,UAAU,CAAC,SAAS,CAAC,OAAO,CAAC;IAC9C,IAAI,QAAQ,KAAK,cAAc;QAAE,OAAO,UAAU,CAAC;IACnD,OAAO,EAAE,GAAG,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;AAC1E,CAAC;AAED,SAAS,WAAW,CAAC,KAAkB;IAIrC,MAAM,WAAW,GAAsB,EAAE,CAAC;IAC1C,MAAM,IAAI,GAAG;QACX,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,WAAW,EAAE,KAAK,CAAC,WAAW;QAC9B,IAAI,EAAE,KAAK,CAAC,SAAS;KACtB,CAAC;IAEF,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC;QACvF,WAAW,CAAC,IAAI,CACd,KAAK,CAAC,OAAO,EAAE,qDAAqD,KAAK,CAAC,WAAW,EAAE,EAAE;YACvF,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,IAAI,EAAE,KAAK,CAAC,WAAW;YACvB,WAAW,EAAE,uEAAuE;SACrF,CAAC,CACH,CAAC;QACF,OAAO;YACL,MAAM,EAAE;gBACN,GAAG,IAAI;gBACP,QAAQ,EAAE,CAAC;gBACX,OAAO,EAAE,EAAE;gBACX,OAAO,EAAE,EAAE;gBACX,QAAQ,EAAE,EAAE;gBACZ,SAAS,EAAE,EAAE;gBACb,EAAE,EAAE,KAAK;aACV;YACD,WAAW;SACZ,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAExC,8EAA8E;IAC9E,0EAA0E;IAC1E,IAAI,KAAK,CAAC,MAAM,KAAK,YAAY,EAAE,CAAC;QAClC,MAAM,QAAQ,GAAG,YAAY,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;QACvE,WAAW,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,WAAW,CAAC,CAAC;QAC1C,MAAM,IAAI,GAAG,UAAU,CAAC,KAAK,CAAC,WAAW,EAAE,QAAQ,CAAC,SAAS,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;QAChG,MAAM,UAAU,GAAG,wBAAwB,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;QAC/D,MAAM,MAAM,GAAsB;YAChC,GAAG,IAAI;YACP,QAAQ,EAAE,QAAQ,CAAC,SAAS,CAAC,MAAM;YACnC,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,QAAQ,EAAE,EAAE;YACZ,SAAS,EAAE,KAAK,CAAC,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE;YAC7D,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,eAAe,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAClE,EAAE,EAAE,KAAK;SACV,CAAC;QACF,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,CAAC,EAAE,WAAW,EAAE,CAAC;IACrE,CAAC;IAED,MAAM,IAAI,GAAG,WAAW,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE;QAC7C,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,IAAI,EAAE,KAAK,CAAC,WAAW;QACvB,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,yEAAyE;QACzE,iEAAiE;QACjE,KAAK,EAAE,IAAI;KACZ,CAAC,CAAC;IACH,WAAW,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC;IAE9E,MAAM,KAAK,GAAG,mBAAmB,CAAC,IAAI,CAAC,WAAW,IAAI,KAAK,CAAC,WAAW,CAAC,CAAC;IACzE,IAAI,KAAK,KAAK,WAAW;QACvB,WAAW,CAAC,IAAI,CACd,KAAK,CAAC,OAAO,EAAE,uBAAuB,KAAK,CAAC,WAAW,eAAe,EAAE;YACtE,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,IAAI,EAAE,KAAK,CAAC,WAAW;YACvB,WAAW,EAAE,gDAAgD;SAC9D,CAAC,CACH,CAAC;IAEJ,MAAM,SAAS,GACb,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAClG,IAAI,CAAC,SAAS,IAAI,KAAK,CAAC,SAAS,KAAK,KAAK;QACzC,WAAW,CAAC,IAAI,CACd,MAAM,CACJ,OAAO,EACP,0BAA0B,KAAK,CAAC,WAAW,0DAA0D,EACrG;YACE,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,IAAI,EAAE,KAAK,CAAC,WAAW;YACvB,WAAW,EAAE,yCAAyC;SACvD,CACF,CACF,CAAC;IAEJ,MAAM,aAAa,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IACtE,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,WAAW,IAAI,KAAK,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,EAAE;QAC3E,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,cAAc,EAAE,SAAS;QACzB,SAAS,EACP,KAAK,CAAC,SAAS,KAAK,QAAQ;YAC1B,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,EAAE,aAAa,CAAC;YACzD,CAAC,CAAC,SAAS;KAChB,CAAC,CAAC;IAEH,MAAM,UAAU,GACd,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,WAAW,CAAC,CAAC,CAAC,eAAe,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAElG,MAAM,MAAM,GAAsB;QAChC,GAAG,IAAI;QACP,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM;QAC/B,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACrC,EAAE,EAAE,KAAK;KACV,CAAC;IACF,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,CAAC,EAAE,WAAW,EAAE,CAAC;AACrE,CAAC;AAED,2EAA2E;AAC3E,SAAS,MAAM,CACb,MAAyB,EACzB,WAA8B,EAC9B,KAAkB;IAElB,MAAM,KAAK,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC;IAChE,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,OAAO;QAClC,WAAW,CAAC,IAAI,CACd,KAAK,CAAC,OAAO,EAAE,gCAAgC,OAAO,GAAG,EAAE;YACzD,GAAG,KAAK;YACR,IAAI,EAAE,OAAO;YACb,WAAW,EAAE,sCAAsC;SACpD,CAAC,CACH,CAAC;IACJ,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,OAAO;QAClC,WAAW,CAAC,IAAI,CACd,KAAK,CAAC,OAAO,EAAE,gDAAgD,OAAO,GAAG,EAAE;YACzE,GAAG,KAAK;YACR,IAAI,EAAE,OAAO;YACb,WAAW,EAAE,sEAAsE;SACpF,CAAC,CACH,CAAC;IACJ,KAAK,MAAM,QAAQ,IAAI,MAAM,CAAC,QAAQ;QACpC,WAAW,CAAC,IAAI,CACd,KAAK,CAAC,OAAO,EAAE,IAAI,QAAQ,sDAAsD,EAAE;YACjF,GAAG,KAAK;YACR,IAAI,EAAE,QAAQ;YACd,WAAW,EAAE,0DAA0D;SACxE,CAAC,CACH,CAAC;IACJ,KAAK,MAAM,SAAS,IAAI,MAAM,CAAC,SAAS;QACtC,WAAW,CAAC,IAAI,CAAC;YACf,IAAI,EAAE,OAAO;YACb,QAAQ,EAAE,SAAS;YACnB,OAAO,EAAE,OAAO;YAChB,OAAO,EAAE,0CAA0C,SAAS,GAAG;YAC/D,GAAG,KAAK;YACR,IAAI,EAAE,SAAS;SAChB,CAAC,CAAC;IACL,IAAI,MAAM,CAAC,UAAU,IAAI,MAAM,CAAC,UAAU,CAAC,MAAM,KAAK,UAAU,IAAI,MAAM,CAAC,UAAU,CAAC,SAAS;QAC7F,WAAW,CAAC,IAAI,CACd,MAAM,CACJ,OAAO,EACP,8BAA8B,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC,OAAO,SAAS,cAAc,kBAAkB,EAC1G,EAAE,GAAG,KAAK,EAAE,IAAI,EAAE,MAAM,CAAC,WAAW,EAAE,CACvC,CACF,CAAC;IACJ,OAAO;QACL,GAAG,MAAM;QACT,EAAE,EACA,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM;YACtB,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM;YACtB,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM;YACvB,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM;KAC3B,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,MAAoB;IAM5C,MAAM,WAAW,GAAsB,EAAE,CAAC;IAC1C,MAAM,OAAO,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAC1E,MAAM,IAAI,GAAG,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC3C,WAAW,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC;IAEtC,MAAM,OAAO,GAAwB,EAAE,CAAC;IACxC,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACnC,MAAM,MAAM,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;QAClC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAC5B,WAAW,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC;IAC1C,CAAC;IAED,MAAM,MAAM,GAAG;QACb,OAAO,EAAE,OAAO,CAAC,MAAM;QACvB,EAAE,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,MAAM;QAC9C,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;QAC1E,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;QAC1E,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;QAC5E,SAAS,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,GAAG,KAAK,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC;KAC/E,CAAC;IAEF,OAAO;QACL,MAAM,EAAE;YACN,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE;YAC7D,SAAS,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,cAAc,EAAE;YACzD,oBAAoB,EAAE,sBAAsB;YAC5C,IAAI,EAAE,IAAI,CAAC,MAAM;YACjB,OAAO;YACP,MAAM;SACP;QACD,WAAW;QACX,OAAO;QACP,QAAQ,EAAE,CAAC,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;KAC9D,CAAC;AACJ,CAAC;AAED,OAAO,EAAE,WAAW,EAAE,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { VerifyConfig } from "./config.js";
2
+ export interface VerifySelection {
3
+ /** `--config <file>`, resolved against the working directory. */
4
+ explicitPath?: string;
5
+ /** Invocation directory; defaults to the process working directory. */
6
+ cwd?: string;
7
+ }
8
+ export declare function resolveVerifyConfig(selection?: VerifySelection): VerifyConfig;
@@ -0,0 +1,97 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { parse as parseYaml } from "yaml";
4
+ import { configIn } from "../../config.js";
5
+ import { object } from "../../config-schema.js";
6
+ import { parseVerifyBlock } from "./config.js";
7
+ /**
8
+ * Finds the document declaring `agent.verify`.
9
+ *
10
+ * Unlike the `scripts` walk this stops at the *nearest* document that declares
11
+ * the block. Entries are a set describing one repository, not a name lookup, so
12
+ * there is nothing for a farther ancestor to contribute — and merging two
13
+ * repositories' entry lists would verify trees the nearer document never
14
+ * mentioned.
15
+ */
16
+ /** A configuration file larger than this is not one anybody wrote by hand. */
17
+ const MAX_CONFIG_BYTES = 1024 * 1024;
18
+ /** Enough of the head to catch a binary file mistakenly named `.cairn.yml`. */
19
+ const NUL_PROBE_BYTES = 8 * 1024;
20
+ /**
21
+ * Opens for reading without ever blocking.
22
+ *
23
+ * Opening a FIFO `O_RDONLY` blocks until a writer appears, which would wedge
24
+ * the process with no output — so a plain `open` cannot be the first thing
25
+ * that touches the path. `O_NONBLOCK` makes the open return immediately for
26
+ * every file type, leaving `fstat` to reject what is not a regular file.
27
+ * Checking the type by path first would be a check-then-use race; this way
28
+ * every guard is made against the descriptor that is actually read.
29
+ *
30
+ * `O_NONBLOCK` is undefined on Windows, which has no FIFOs in this sense.
31
+ */
32
+ function openForRead(file) {
33
+ const { O_RDONLY, O_NONBLOCK } = fs.constants;
34
+ return fs.openSync(file, O_RDONLY | (O_NONBLOCK ?? 0));
35
+ }
36
+ function readDocument(file) {
37
+ // The guards mirror `readRegistry` in `src/scripts/resolve.ts`: a
38
+ // regular-file check so a FIFO cannot wedge the process, a size cap, and a
39
+ // NUL probe. Unlike that one, all three are made against an open descriptor,
40
+ // so the file they describe is the file that is read.
41
+ const handle = openForRead(file);
42
+ try {
43
+ const stat = fs.fstatSync(handle);
44
+ if (!stat.isFile())
45
+ throw new Error(`Not a regular file: ${file}`);
46
+ if (stat.size > MAX_CONFIG_BYTES)
47
+ throw new Error(`Configuration file is larger than ${MAX_CONFIG_BYTES} bytes: ${file}`);
48
+ const buffer = Buffer.alloc(stat.size);
49
+ let read = 0;
50
+ while (read < stat.size) {
51
+ const chunk = fs.readSync(handle, buffer, read, stat.size - read, read);
52
+ if (chunk === 0)
53
+ break;
54
+ read += chunk;
55
+ }
56
+ const contents = buffer.subarray(0, read);
57
+ if (contents.subarray(0, NUL_PROBE_BYTES).includes(0))
58
+ throw new Error(`Not a text file: ${file}`);
59
+ return object(parseYaml(contents.toString("utf-8")), "configuration");
60
+ }
61
+ finally {
62
+ fs.closeSync(handle);
63
+ }
64
+ }
65
+ function parseAt(file) {
66
+ const directory = path.dirname(path.resolve(file));
67
+ const document = readDocument(file);
68
+ if (document.agent === undefined)
69
+ return undefined;
70
+ return parseVerifyBlock(document.agent, { file: path.resolve(file), directory });
71
+ }
72
+ export function resolveVerifyConfig(selection = {}) {
73
+ if (selection.explicitPath) {
74
+ const file = path.resolve(selection.explicitPath);
75
+ if (!fs.existsSync(file))
76
+ throw new Error(`Configuration file not found: ${file}`);
77
+ const config = parseAt(file);
78
+ if (!config)
79
+ throw new Error(`No 'agent.verify' block in ${file}`);
80
+ return config;
81
+ }
82
+ let current = path.resolve(selection.cwd ?? process.cwd());
83
+ while (true) {
84
+ const candidate = configIn(current);
85
+ if (candidate) {
86
+ const config = parseAt(candidate);
87
+ if (config)
88
+ return config;
89
+ }
90
+ const parent = path.dirname(current);
91
+ if (parent === current)
92
+ break;
93
+ current = parent;
94
+ }
95
+ throw new Error("No 'agent.verify' block found. Declare one in .cairn.yml, or pass --config <file>.");
96
+ }
97
+ //# sourceMappingURL=resolve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../../src/agent/verify/resolve.ts"],"names":[],"mappings":"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"}
package/dist/cli.js CHANGED
@@ -51,6 +51,7 @@ import { agentDoctorAction } from "./commands/agent-doctor.js";
51
51
  import { agentInstallAction } from "./commands/agent-install.js";
52
52
  import { agentUninstallAction } from "./commands/agent-uninstall.js";
53
53
  import { agentInstalledAction } from "./commands/agent-installed.js";
54
+ import { agentVerifyAction } from "./commands/agent-verify.js";
54
55
  import { describeAction } from "./commands/describe.js";
55
56
  import { schemaAction } from "./commands/schema.js";
56
57
  import { completionAction } from "./commands/completion.js";
@@ -366,6 +367,16 @@ agent
366
367
  .option("--envelope", "Wrap --format json output in the versioned result envelope")
367
368
  .addHelpText("after", "\nScans the install roots declared on the target profiles and lists every\n.cairn-install.json it finds.\n\nExit codes:\n 0 Listing written to stdout\n 1 Invocation error")
368
369
  .action((opts) => agentActionBoundary("installed", opts, () => agentInstalledAction(opts)));
370
+ agent
371
+ .command("verify")
372
+ .description("Check committed agent trees against the bundles they were generated from")
373
+ .option("--config <file>", "Configuration document declaring the agent.verify block")
374
+ .option("--name <name>", "Verify only this entry (repeatable)", collect)
375
+ .option("--strict", "Treat warnings as blocking findings")
376
+ .option("--format <fmt>", "Output format: llm, human, json", "llm")
377
+ .option("--envelope", "Wrap --format json output in the versioned result envelope")
378
+ .addHelpText("after", "\nReads what to verify from the agent.verify block of a cairn configuration\ndocument, so a CI pipeline can run it with no arguments. Each declared bundle\nis rendered in memory and compared against the committed tree, and the pinned\nCLI and target profile versions are asserted against the running build.\n\nExit codes:\n 0 Every entry matches and every pin is satisfied\n 1 Invocation, configuration, or I/O error\n 2 Drift, an orphaned file, or a violated pin")
379
+ .action((opts) => agentActionBoundary("verify", opts, () => agentVerifyAction(opts)));
369
380
  agent
370
381
  .command("specs")
371
382
  .description("Print the versioned target conformance profiles")