@cairn-tool/cairn 2.0.0 → 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.
@@ -6,6 +6,7 @@ import type { AuditReport } from "./audit/index.js";
6
6
  import type { TestReport } from "./test/index.js";
7
7
  import type { InstallReport } from "./install/index.js";
8
8
  import type { MarketplaceReport } from "./marketplace/index.js";
9
+ import type { VerifyReport } from "./verify/index.js";
9
10
  export declare const TARGETS: readonly ["claude-code", "codex", "cursor", "antigravity", "opencode"];
10
11
  export type AgentTarget = (typeof TARGETS)[number];
11
12
  export type AgentProfile = "plugin" | "project";
@@ -164,7 +165,7 @@ export interface AgentPlan {
164
165
  operations: PlanOperation[];
165
166
  }
166
167
  export interface AgentResult {
167
- command: "convert" | "validate" | "inspect" | "compat" | "doctor" | "specs" | "init" | "add" | "upgrade" | "import" | "package" | "audit" | "test" | "install" | "uninstall" | "installed" | "marketplace";
168
+ command: "convert" | "validate" | "inspect" | "compat" | "doctor" | "specs" | "init" | "add" | "upgrade" | "import" | "package" | "audit" | "test" | "install" | "uninstall" | "installed" | "marketplace" | "verify";
168
169
  ok: boolean;
169
170
  source?: string;
170
171
  targets: AgentTarget[];
@@ -197,6 +198,8 @@ export interface AgentResult {
197
198
  install?: InstallReport;
198
199
  /** Collection build result, emitted by `agent marketplace`. */
199
200
  marketplace?: MarketplaceReport;
201
+ /** Drift and pin result, emitted by `agent verify`. */
202
+ verify?: VerifyReport;
200
203
  dryRun?: boolean;
201
204
  check?: boolean;
202
205
  stale?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/agent/types.ts"],"names":[],"mappings":"AASA,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,aAAa,EAAE,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,UAAU,CAAU,CAAC;AA0M9F,MAAM,UAAU,UAAU,CACxB,IAAY,EACZ,OAAe,EACf,OAAuB,EACvB,QAAkC,EAAE;IAEpC,OAAO;QACL,IAAI;QACJ,QAAQ,EACN,OAAO,KAAK,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,KAAK,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ;QAC1F,OAAO;QACP,OAAO;QACP,GAAG,KAAK;KACT,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/agent/types.ts"],"names":[],"mappings":"AAUA,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,aAAa,EAAE,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,UAAU,CAAU,CAAC;AA6M9F,MAAM,UAAU,UAAU,CACxB,IAAY,EACZ,OAAe,EACf,OAAuB,EACvB,QAAkC,EAAE;IAEpC,OAAO;QACL,IAAI;QACJ,QAAQ,EACN,OAAO,KAAK,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,KAAK,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ;QAC1F,OAAO;QACP,OAAO;QACP,GAAG,KAAK;KACT,CAAC;AACJ,CAAC"}
@@ -0,0 +1,49 @@
1
+ import type { AgentProfile, AgentTarget, Artifact } from "../types.js";
2
+ export interface TreeDiff {
3
+ /** Expected artifacts absent from the tree, or present but not a regular file. */
4
+ missing: string[];
5
+ /** Expected artifacts whose bytes or permission bits differ. */
6
+ changed: string[];
7
+ /** Recorded in a prior install's inventory, but no longer rendered. */
8
+ orphaned: string[];
9
+ /** Inside managed territory, accounted for by neither the render nor the inventory. */
10
+ unmanaged: string[];
11
+ }
12
+ /**
13
+ * The longest leading run of segments containing no wildcard.
14
+ *
15
+ * `.claude/skills/{name}/**` yields `.claude/skills`; `.mcp.json` yields itself,
16
+ * which is how a wholly-literal pattern is recognized as a leaf file rather
17
+ * than a directory to walk.
18
+ */
19
+ export declare function literalPrefix(pattern: string): string;
20
+ /**
21
+ * The directories a hand-added file could hide in.
22
+ *
23
+ * Derived from the target profile's own declared output patterns, so it can
24
+ * never claim a surface the renderer does not describe. Three filters matter,
25
+ * and each one is load-bearing:
26
+ *
27
+ * - a wholly-literal pattern is a *leaf file* (`AGENTS.md`, `.mcp.json`,
28
+ * `.claude/settings.json`). It is compared byte for byte by the expected-set
29
+ * pass and never walked.
30
+ * - an empty prefix would make the destination root itself a walk root, which
31
+ * is precisely the repository-enumeration failure this function exists to
32
+ * prevent. No shipped profile declares such a pattern; the guard is here so a
33
+ * future one cannot introduce the bug silently.
34
+ * - a prefix the render did not populate is not this bundle's territory. A
35
+ * repository keeping its own `assets/` is only walked when the bundle
36
+ * actually renders assets there.
37
+ */
38
+ export declare function walkRootsFor(target: AgentTarget, profile: AgentProfile, expected: readonly string[]): string[];
39
+ export interface TreeDiffOptions {
40
+ /** `off` skips both extra-file passes; `orphaned` reads only the inventory. */
41
+ unmanaged: "off" | "orphaned" | "strict";
42
+ /** Paths recorded by a prior install, destination-relative. */
43
+ priorInventory?: readonly string[];
44
+ /** Directory prefixes a `strict` walk may descend into. */
45
+ walkRoots?: readonly string[];
46
+ }
47
+ export declare function diffTree(destination: string, artifacts: readonly Artifact[], options: TreeDiffOptions): TreeDiff;
48
+ /** True when the tree matches the artifacts exactly. */
49
+ export declare function treeMatches(diff: TreeDiff): boolean;
@@ -0,0 +1,159 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { isInside } from "../../config-schema.js";
4
+ import { profileFor } from "../targets/index.js";
5
+ import { INSTALL_MANIFEST, LEGACY_INSTALL_MANIFEST } from "../install/index.js";
6
+ /**
7
+ * Compares a tree a host actually reads — `.claude/skills/…` at a repository
8
+ * root, say — against the artifacts that should produce it.
9
+ *
10
+ * This cannot be {@link diffOutput}, which assumes an
11
+ * `<output>/<target>/<profile>/` conversion root and walks those roots
12
+ * exhaustively. A merge install has no such prefix, and an unbounded walk from
13
+ * a repository root would enumerate `node_modules`, `.git`, and every source
14
+ * file as unmanaged. The containment rule below is what replaces that
15
+ * assumption.
16
+ */
17
+ /** A directory holding more files than this is not a generated tree. */
18
+ const MAX_WALK_ENTRIES = 20_000;
19
+ /** Byte comparison, never `localeCompare`: sort order must not depend on the ICU build. */
20
+ function byBytes(a, b) {
21
+ return a < b ? -1 : a > b ? 1 : 0;
22
+ }
23
+ /**
24
+ * The longest leading run of segments containing no wildcard.
25
+ *
26
+ * `.claude/skills/{name}/**` yields `.claude/skills`; `.mcp.json` yields itself,
27
+ * which is how a wholly-literal pattern is recognized as a leaf file rather
28
+ * than a directory to walk.
29
+ */
30
+ export function literalPrefix(pattern) {
31
+ const segments = pattern.split("/");
32
+ const literal = [];
33
+ for (const segment of segments) {
34
+ if (segment.includes("*") || segment.includes("{"))
35
+ break;
36
+ literal.push(segment);
37
+ }
38
+ return literal.join("/");
39
+ }
40
+ /**
41
+ * The directories a hand-added file could hide in.
42
+ *
43
+ * Derived from the target profile's own declared output patterns, so it can
44
+ * never claim a surface the renderer does not describe. Three filters matter,
45
+ * and each one is load-bearing:
46
+ *
47
+ * - a wholly-literal pattern is a *leaf file* (`AGENTS.md`, `.mcp.json`,
48
+ * `.claude/settings.json`). It is compared byte for byte by the expected-set
49
+ * pass and never walked.
50
+ * - an empty prefix would make the destination root itself a walk root, which
51
+ * is precisely the repository-enumeration failure this function exists to
52
+ * prevent. No shipped profile declares such a pattern; the guard is here so a
53
+ * future one cannot introduce the bug silently.
54
+ * - a prefix the render did not populate is not this bundle's territory. A
55
+ * repository keeping its own `assets/` is only walked when the bundle
56
+ * actually renders assets there.
57
+ */
58
+ export function walkRootsFor(target, profile, expected) {
59
+ const patterns = profileFor(target).outputs[profile] ?? [];
60
+ const roots = new Set();
61
+ for (const { pattern } of patterns) {
62
+ const prefix = literalPrefix(pattern);
63
+ if (!prefix || prefix === pattern)
64
+ continue;
65
+ if (!expected.some((candidate) => candidate.startsWith(`${prefix}/`)))
66
+ continue;
67
+ roots.add(prefix);
68
+ }
69
+ return [...roots].sort(byBytes);
70
+ }
71
+ function walk(directory, root, onFile) {
72
+ let seen = 0;
73
+ for (const dirent of fs.readdirSync(directory, { withFileTypes: true })) {
74
+ const full = path.join(directory, dirent.name);
75
+ // Dirent is lstat-based, so a symlinked directory reports false here and is
76
+ // never descended into.
77
+ if (dirent.isDirectory()) {
78
+ seen += walk(full, root, onFile);
79
+ }
80
+ else {
81
+ seen += 1;
82
+ onFile(path.relative(root, full).split(path.sep).join("/"));
83
+ }
84
+ if (seen > MAX_WALK_ENTRIES)
85
+ throw new Error(`Refusing to walk more than ${MAX_WALK_ENTRIES} files under ${root}`);
86
+ }
87
+ return seen;
88
+ }
89
+ /**
90
+ * The install manifest is never compared by bytes, and never reported missing.
91
+ *
92
+ * It embeds the generator version, so byte-comparing it would report every tree
93
+ * as drifted after any CLI upgrade — the same concession `payloadMatches` and
94
+ * `diffOutput` both make, for the same reason. It is bookkeeping rather than
95
+ * agent content, so a repository that ignores it is not drifted; the cost is
96
+ * orphan detection, reported once as AB426.
97
+ */
98
+ function existenceOnly(relative) {
99
+ return relative === INSTALL_MANIFEST || relative === LEGACY_INSTALL_MANIFEST;
100
+ }
101
+ export function diffTree(destination, artifacts, options) {
102
+ const diff = { missing: [], changed: [], orphaned: [], unmanaged: [] };
103
+ const expected = new Set(artifacts.map((artifact) => artifact.path));
104
+ for (const artifact of artifacts) {
105
+ const file = path.join(destination, artifact.path);
106
+ let stat;
107
+ try {
108
+ stat = fs.statSync(file);
109
+ }
110
+ catch {
111
+ stat = undefined;
112
+ }
113
+ if (!stat || !stat.isFile()) {
114
+ // An absent install manifest is bookkeeping, not drift: a repository may
115
+ // legitimately commit the generated tree while ignoring the manifest.
116
+ // Its absence is reported once, as AB426, and costs only orphan
117
+ // detection.
118
+ if (!existenceOnly(artifact.path))
119
+ diff.missing.push(artifact.path);
120
+ continue;
121
+ }
122
+ if (existenceOnly(artifact.path))
123
+ continue;
124
+ if (!fs.readFileSync(file).equals(artifact.content) ||
125
+ (stat.mode & 0o777) !== (artifact.mode & 0o777))
126
+ diff.changed.push(artifact.path);
127
+ }
128
+ if (options.unmanaged !== "off")
129
+ for (const recorded of options.priorInventory ?? [])
130
+ if (!expected.has(recorded) && !existenceOnly(recorded))
131
+ diff.orphaned.push(recorded);
132
+ if (options.unmanaged === "strict") {
133
+ const inventory = new Set(options.priorInventory ?? []);
134
+ for (const root of options.walkRoots ?? []) {
135
+ const absolute = path.join(destination, root);
136
+ if (!fs.existsSync(absolute) || !fs.statSync(absolute).isDirectory())
137
+ continue;
138
+ // A walk root reached through a symlink could leave the destination
139
+ // entirely; refuse rather than report someone else's files.
140
+ if (!isInside(fs.realpathSync(destination), fs.realpathSync(absolute)))
141
+ continue;
142
+ walk(absolute, destination, (relative) => {
143
+ if (expected.has(relative) || inventory.has(relative) || existenceOnly(relative))
144
+ return;
145
+ diff.unmanaged.push(relative);
146
+ });
147
+ }
148
+ }
149
+ diff.missing.sort(byBytes);
150
+ diff.changed.sort(byBytes);
151
+ diff.orphaned.sort(byBytes);
152
+ diff.unmanaged.sort(byBytes);
153
+ return diff;
154
+ }
155
+ /** True when the tree matches the artifacts exactly. */
156
+ export function treeMatches(diff) {
157
+ return (!diff.missing.length && !diff.changed.length && !diff.orphaned.length && !diff.unmanaged.length);
158
+ }
159
+ //# sourceMappingURL=compare.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compare.js","sourceRoot":"","sources":["../../../src/agent/verify/compare.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ import type { AgentProfile, AgentTarget } from "../types.js";
2
+ export declare const UNMANAGED_MODES: readonly ["off", "orphaned", "strict"];
3
+ export type UnmanagedMode = (typeof UNMANAGED_MODES)[number];
4
+ export declare const VERIFY_LAYOUTS: readonly ["merge", "plugin-dir", "conversion"];
5
+ export type VerifyLayout = (typeof VERIFY_LAYOUTS)[number];
6
+ export declare const VERIFY_SCOPES: readonly ["user", "project"];
7
+ export type VerifyScope = (typeof VERIFY_SCOPES)[number];
8
+ /**
9
+ * An inclusive bound, never a range grammar.
10
+ *
11
+ * {@link compareSemver} is deliberately an ordering rather than a range parser,
12
+ * and the target profiles record single bounds for the same reason. Accepting
13
+ * `">=2.1 <3"` here would promise a grammar nothing else in the project has.
14
+ */
15
+ export interface VersionBound {
16
+ exact?: string;
17
+ min?: string;
18
+ max?: string;
19
+ }
20
+ export interface VerifyPins {
21
+ cli?: VersionBound;
22
+ profileSchemaVersion?: string;
23
+ targets: Partial<Record<AgentTarget, VersionBound>>;
24
+ }
25
+ export interface VerifyEntry {
26
+ /** Stable identifier for the entry, reported in findings and the payload. */
27
+ name: string;
28
+ /** Absolute bundle root: the directory holding `agent-bundle.yaml`. */
29
+ bundle: string;
30
+ target: AgentTarget;
31
+ profile: AgentProfile;
32
+ /** Absolute root the rendered tree was placed at. */
33
+ destination: string;
34
+ scope: VerifyScope;
35
+ layout: VerifyLayout;
36
+ unmanaged: UnmanagedMode;
37
+ }
38
+ export interface VerifyConfig {
39
+ /** Absolute path of the document this block came from. */
40
+ file: string;
41
+ /** Directory holding the document; every relative path resolves against it. */
42
+ directory: string;
43
+ pins: VerifyPins;
44
+ entries: VerifyEntry[];
45
+ }
46
+ export declare function emptyVerifyConfig(file: string, directory: string): VerifyConfig;
47
+ /**
48
+ * Parses an `agent:` block. Returns an empty configuration when the key is
49
+ * absent, so a document that declares nothing is valid rather than an error.
50
+ */
51
+ export declare function parseVerifyBlock(value: unknown, context: {
52
+ file: string;
53
+ directory: string;
54
+ }): VerifyConfig;
@@ -0,0 +1,196 @@
1
+ import path from "node:path";
2
+ import { isInside, knownKeys, object, optionalString } from "../../config-schema.js";
3
+ import { parseSemver } from "../targets/schema.js";
4
+ import { TARGETS } from "../types.js";
5
+ /** Local, because `src/agent/types.ts` declares the profile as a union, not a list. */
6
+ const PROFILES = ["plugin", "project"];
7
+ /**
8
+ * The `agent.verify` block: what a repository claims its committed agent trees
9
+ * were generated from, and which toolchain is allowed to have generated them.
10
+ *
11
+ * Parsed here rather than in `src/config.ts` for the reason `scripts:` is:
12
+ * `agent` commands run with configuration discovery disabled, so they resolve
13
+ * this block through their own walk. The loader still calls
14
+ * {@link parseVerifyBlock} for its throw, so a typo is an error at `md lint`
15
+ * rather than a surprise in CI.
16
+ */
17
+ const ROOT_KEYS = new Set(["verify"]);
18
+ const VERIFY_KEYS = new Set(["pins", "defaults", "entries"]);
19
+ const PIN_KEYS = new Set(["cli", "profileSchemaVersion", "targets"]);
20
+ const BOUND_KEYS = new Set(["exact", "min", "max"]);
21
+ const DEFAULT_KEYS = new Set(["unmanaged", "scope", "profile", "layout"]);
22
+ const ENTRY_KEYS = new Set([
23
+ "name",
24
+ "bundle",
25
+ "target",
26
+ "profile",
27
+ "destination",
28
+ "scope",
29
+ "layout",
30
+ "unmanaged",
31
+ ]);
32
+ export const UNMANAGED_MODES = ["off", "orphaned", "strict"];
33
+ export const VERIFY_LAYOUTS = ["merge", "plugin-dir", "conversion"];
34
+ export const VERIFY_SCOPES = ["user", "project"];
35
+ export function emptyVerifyConfig(file, directory) {
36
+ return { file, directory, pins: { targets: {} }, entries: [] };
37
+ }
38
+ function version(value, name) {
39
+ const text = optionalString(value, name);
40
+ if (text === undefined)
41
+ throw new Error(`${name} is required`);
42
+ if (!parseSemver(text))
43
+ throw new Error(`${name} must be a version: ${text}`);
44
+ return text;
45
+ }
46
+ /**
47
+ * A documentation revision is an ISO date, compared lexicographically — the
48
+ * same comparison `agent doctor` already makes against
49
+ * `host.documentationRevision`.
50
+ */
51
+ function revision(value, name) {
52
+ const text = optionalString(value, name);
53
+ if (text === undefined)
54
+ throw new Error(`${name} is required`);
55
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(text))
56
+ throw new Error(`${name} must be an ISO date (YYYY-MM-DD): ${text}`);
57
+ return text;
58
+ }
59
+ function bound(value, name, parse) {
60
+ const block = object(value, name);
61
+ knownKeys(block, BOUND_KEYS, name);
62
+ if (!Object.keys(block).length)
63
+ throw new Error(`${name} must declare exact, min, or max`);
64
+ if (block.exact !== undefined && (block.min !== undefined || block.max !== undefined))
65
+ throw new Error(`${name}.exact cannot be combined with min or max`);
66
+ const parsed = {};
67
+ if (block.exact !== undefined)
68
+ parsed.exact = parse(block.exact, `${name}.exact`);
69
+ if (block.min !== undefined)
70
+ parsed.min = parse(block.min, `${name}.min`);
71
+ if (block.max !== undefined)
72
+ parsed.max = parse(block.max, `${name}.max`);
73
+ return parsed;
74
+ }
75
+ function enumerated(value, name, allowed, fallback) {
76
+ const text = optionalString(value, name);
77
+ if (text === undefined)
78
+ return fallback;
79
+ if (!allowed.includes(text))
80
+ throw new Error(`${name} must be one of ${allowed.join(", ")}: ${text}`);
81
+ return text;
82
+ }
83
+ function pins(value) {
84
+ const block = object(value, "agent.verify.pins");
85
+ knownKeys(block, PIN_KEYS, "agent.verify.pins");
86
+ const targets = {};
87
+ const declared = object(block.targets, "agent.verify.pins.targets");
88
+ for (const [key, entry] of Object.entries(declared)) {
89
+ if (!TARGETS.includes(key))
90
+ throw new Error(`Unknown target in agent.verify.pins.targets: ${key}`);
91
+ targets[key] = bound(entry, `agent.verify.pins.targets.${key}`, revision);
92
+ }
93
+ return {
94
+ ...(block.cli !== undefined ? { cli: bound(block.cli, "agent.verify.pins.cli", version) } : {}),
95
+ ...(block.profileSchemaVersion !== undefined
96
+ ? {
97
+ profileSchemaVersion: (() => {
98
+ const text = optionalString(block.profileSchemaVersion, "agent.verify.pins.profileSchemaVersion");
99
+ if (!text)
100
+ throw new Error("agent.verify.pins.profileSchemaVersion must be a string");
101
+ return text;
102
+ })(),
103
+ }
104
+ : {}),
105
+ targets,
106
+ };
107
+ }
108
+ /**
109
+ * Resolves a declared path against the document's directory and refuses one
110
+ * that escapes it. A verify document describes its own repository; reaching
111
+ * outside would let a checked-in file name an arbitrary destination.
112
+ */
113
+ function contained(raw, directory, name) {
114
+ const resolved = path.resolve(directory, raw);
115
+ if (!isInside(directory, resolved))
116
+ throw new Error(`${name} escapes the configuration directory: ${raw}`);
117
+ return resolved;
118
+ }
119
+ function entry(value, index, directory, defaults) {
120
+ const name = `agent.verify.entries[${index}]`;
121
+ const block = object(value, name);
122
+ knownKeys(block, ENTRY_KEYS, name);
123
+ const bundleRaw = optionalString(block.bundle, `${name}.bundle`);
124
+ if (!bundleRaw)
125
+ throw new Error(`${name}.bundle is required`);
126
+ const targetRaw = optionalString(block.target, `${name}.target`);
127
+ if (!targetRaw)
128
+ throw new Error(`${name}.target is required`);
129
+ if (!TARGETS.includes(targetRaw))
130
+ throw new Error(`Unknown target in ${name}.target: ${targetRaw}`);
131
+ const target = targetRaw;
132
+ const profileRaw = optionalString(block.profile, `${name}.profile`) ?? defaults.profile;
133
+ if (!profileRaw)
134
+ throw new Error(`${name}.profile is required`);
135
+ if (!PROFILES.includes(profileRaw))
136
+ throw new Error(`Unknown profile in ${name}.profile: ${profileRaw}`);
137
+ const profile = profileRaw;
138
+ const destinationRaw = optionalString(block.destination, `${name}.destination`) ?? ".";
139
+ const bundle = contained(bundleRaw, directory, `${name}.bundle`);
140
+ const destination = contained(destinationRaw, directory, `${name}.destination`);
141
+ return {
142
+ name: optionalString(block.name, `${name}.name`) ?? `${bundleRaw}/${target}/${profile}`,
143
+ bundle,
144
+ target,
145
+ profile,
146
+ destination,
147
+ scope: enumerated(block.scope, `${name}.scope`, VERIFY_SCOPES, defaults.scope),
148
+ layout: enumerated(block.layout, `${name}.layout`, VERIFY_LAYOUTS, defaults.layout ?? "merge"),
149
+ unmanaged: enumerated(block.unmanaged, `${name}.unmanaged`, UNMANAGED_MODES, defaults.unmanaged),
150
+ };
151
+ }
152
+ /**
153
+ * Parses an `agent:` block. Returns an empty configuration when the key is
154
+ * absent, so a document that declares nothing is valid rather than an error.
155
+ */
156
+ export function parseVerifyBlock(value, context) {
157
+ if (value === undefined)
158
+ return emptyVerifyConfig(context.file, context.directory);
159
+ const root = object(value, "agent");
160
+ knownKeys(root, ROOT_KEYS, "agent");
161
+ if (root.verify === undefined)
162
+ return emptyVerifyConfig(context.file, context.directory);
163
+ const block = object(root.verify, "agent.verify");
164
+ knownKeys(block, VERIFY_KEYS, "agent.verify");
165
+ const defaultBlock = object(block.defaults, "agent.verify.defaults");
166
+ knownKeys(defaultBlock, DEFAULT_KEYS, "agent.verify.defaults");
167
+ const defaults = {
168
+ scope: enumerated(defaultBlock.scope, "agent.verify.defaults.scope", VERIFY_SCOPES, "project"),
169
+ unmanaged: enumerated(defaultBlock.unmanaged, "agent.verify.defaults.unmanaged", UNMANAGED_MODES, "orphaned"),
170
+ ...(defaultBlock.profile !== undefined
171
+ ? {
172
+ profile: enumerated(defaultBlock.profile, "agent.verify.defaults.profile", PROFILES, "project"),
173
+ }
174
+ : {}),
175
+ ...(defaultBlock.layout !== undefined
176
+ ? {
177
+ layout: enumerated(defaultBlock.layout, "agent.verify.defaults.layout", VERIFY_LAYOUTS, "merge"),
178
+ }
179
+ : {}),
180
+ };
181
+ const declared = block.entries;
182
+ if (declared !== undefined && !Array.isArray(declared))
183
+ throw new Error("agent.verify.entries must be a list");
184
+ const list = (declared ?? []);
185
+ if (!list.length)
186
+ throw new Error("agent.verify.entries must declare at least one entry");
187
+ const entries = list.map((item, index) => entry(item, index, context.directory, defaults));
188
+ const seen = new Set();
189
+ for (const item of entries) {
190
+ if (seen.has(item.name))
191
+ throw new Error(`Duplicate agent.verify entry name: ${item.name}`);
192
+ seen.add(item.name);
193
+ }
194
+ return { file: context.file, directory: context.directory, pins: pins(block.pins), entries };
195
+ }
196
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../../src/agent/verify/config.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACrF,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAGtC,uFAAuF;AACvF,MAAM,QAAQ,GAAG,CAAC,QAAQ,EAAE,SAAS,CAAU,CAAC;AAEhD;;;;;;;;;GASG;AAEH,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;AACtC,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC,CAAC;AAC7D,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,sBAAsB,EAAE,SAAS,CAAC,CAAC,CAAC;AACrE,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;AACpD,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,CAAC,WAAW,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAC;AAC1E,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC;IACzB,MAAM;IACN,QAAQ;IACR,QAAQ;IACR,SAAS;IACT,aAAa;IACb,OAAO;IACP,QAAQ;IACR,WAAW;CACZ,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,CAAU,CAAC;AAGtE,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,OAAO,EAAE,YAAY,EAAE,YAAY,CAAU,CAAC;AAG7E,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,MAAM,EAAE,SAAS,CAAU,CAAC;AA6C1D,MAAM,UAAU,iBAAiB,CAAC,IAAY,EAAE,SAAiB;IAC/D,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;AACjE,CAAC;AAED,SAAS,OAAO,CAAC,KAAc,EAAE,IAAY;IAC3C,MAAM,IAAI,GAAG,cAAc,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IACzC,IAAI,IAAI,KAAK,SAAS;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,cAAc,CAAC,CAAC;IAC/D,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,uBAAuB,IAAI,EAAE,CAAC,CAAC;IAC9E,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;GAIG;AACH,SAAS,QAAQ,CAAC,KAAc,EAAE,IAAY;IAC5C,MAAM,IAAI,GAAG,cAAc,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IACzC,IAAI,IAAI,KAAK,SAAS;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,cAAc,CAAC,CAAC;IAC/D,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,IAAI,CAAC;QACnC,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,sCAAsC,IAAI,EAAE,CAAC,CAAC;IACvE,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,KAAK,CAAC,KAAc,EAAE,IAAY,EAAE,KAA4C;IACvF,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAClC,SAAS,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,CAAC,CAAC;IACnC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,kCAAkC,CAAC,CAAC;IAC3F,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS,IAAI,CAAC,KAAK,CAAC,GAAG,KAAK,SAAS,IAAI,KAAK,CAAC,GAAG,KAAK,SAAS,CAAC;QACnF,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,2CAA2C,CAAC,CAAC;IACtE,MAAM,MAAM,GAAiB,EAAE,CAAC;IAChC,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS;QAAE,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,IAAI,QAAQ,CAAC,CAAC;IAClF,IAAI,KAAK,CAAC,GAAG,KAAK,SAAS;QAAE,MAAM,CAAC,GAAG,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE,GAAG,IAAI,MAAM,CAAC,CAAC;IAC1E,IAAI,KAAK,CAAC,GAAG,KAAK,SAAS;QAAE,MAAM,CAAC,GAAG,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE,GAAG,IAAI,MAAM,CAAC,CAAC;IAC1E,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,UAAU,CACjB,KAAc,EACd,IAAY,EACZ,OAAqB,EACrB,QAAW;IAEX,MAAM,IAAI,GAAG,cAAc,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IACzC,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,QAAQ,CAAC;IACxC,IAAI,CAAE,OAA6B,CAAC,QAAQ,CAAC,IAAI,CAAC;QAChD,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,mBAAmB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;IAC3E,OAAO,IAAS,CAAC;AACnB,CAAC;AAED,SAAS,IAAI,CAAC,KAAc;IAC1B,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,EAAE,mBAAmB,CAAC,CAAC;IACjD,SAAS,CAAC,KAAK,EAAE,QAAQ,EAAE,mBAAmB,CAAC,CAAC;IAChD,MAAM,OAAO,GAA+C,EAAE,CAAC;IAC/D,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,2BAA2B,CAAC,CAAC;IACpE,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;QACpD,IAAI,CAAE,OAA6B,CAAC,QAAQ,CAAC,GAAG,CAAC;YAC/C,MAAM,IAAI,KAAK,CAAC,gDAAgD,GAAG,EAAE,CAAC,CAAC;QACzE,OAAO,CAAC,GAAkB,CAAC,GAAG,KAAK,CAAC,KAAK,EAAE,6BAA6B,GAAG,EAAE,EAAE,QAAQ,CAAC,CAAC;IAC3F,CAAC;IACD,OAAO;QACL,GAAG,CAAC,KAAK,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE,uBAAuB,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/F,GAAG,CAAC,KAAK,CAAC,oBAAoB,KAAK,SAAS;YAC1C,CAAC,CAAC;gBACE,oBAAoB,EAAE,CAAC,GAAG,EAAE;oBAC1B,MAAM,IAAI,GAAG,cAAc,CACzB,KAAK,CAAC,oBAAoB,EAC1B,wCAAwC,CACzC,CAAC;oBACF,IAAI,CAAC,IAAI;wBAAE,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;oBACtF,OAAO,IAAI,CAAC;gBACd,CAAC,CAAC,EAAE;aACL;YACH,CAAC,CAAC,EAAE,CAAC;QACP,OAAO;KACR,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,SAAS,SAAS,CAAC,GAAW,EAAE,SAAiB,EAAE,IAAY;IAC7D,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;IAC9C,IAAI,CAAC,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC;QAChC,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,yCAAyC,GAAG,EAAE,CAAC,CAAC;IACzE,OAAO,QAAQ,CAAC;AAClB,CAAC;AASD,SAAS,KAAK,CAAC,KAAc,EAAE,KAAa,EAAE,SAAiB,EAAE,QAAuB;IACtF,MAAM,IAAI,GAAG,wBAAwB,KAAK,GAAG,CAAC;IAC9C,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAClC,SAAS,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,CAAC,CAAC;IAEnC,MAAM,SAAS,GAAG,cAAc,CAAC,KAAK,CAAC,MAAM,EAAE,GAAG,IAAI,SAAS,CAAC,CAAC;IACjE,IAAI,CAAC,SAAS;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,qBAAqB,CAAC,CAAC;IAC9D,MAAM,SAAS,GAAG,cAAc,CAAC,KAAK,CAAC,MAAM,EAAE,GAAG,IAAI,SAAS,CAAC,CAAC;IACjE,IAAI,CAAC,SAAS;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,qBAAqB,CAAC,CAAC;IAC9D,IAAI,CAAE,OAA6B,CAAC,QAAQ,CAAC,SAAS,CAAC;QACrD,MAAM,IAAI,KAAK,CAAC,qBAAqB,IAAI,YAAY,SAAS,EAAE,CAAC,CAAC;IACpE,MAAM,MAAM,GAAG,SAAwB,CAAC;IAExC,MAAM,UAAU,GAAG,cAAc,CAAC,KAAK,CAAC,OAAO,EAAE,GAAG,IAAI,UAAU,CAAC,IAAI,QAAQ,CAAC,OAAO,CAAC;IACxF,IAAI,CAAC,UAAU;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,sBAAsB,CAAC,CAAC;IAChE,IAAI,CAAE,QAA8B,CAAC,QAAQ,CAAC,UAAU,CAAC;QACvD,MAAM,IAAI,KAAK,CAAC,sBAAsB,IAAI,aAAa,UAAU,EAAE,CAAC,CAAC;IACvE,MAAM,OAAO,GAAG,UAA0B,CAAC;IAE3C,MAAM,cAAc,GAAG,cAAc,CAAC,KAAK,CAAC,WAAW,EAAE,GAAG,IAAI,cAAc,CAAC,IAAI,GAAG,CAAC;IACvF,MAAM,MAAM,GAAG,SAAS,CAAC,SAAS,EAAE,SAAS,EAAE,GAAG,IAAI,SAAS,CAAC,CAAC;IACjE,MAAM,WAAW,GAAG,SAAS,CAAC,cAAc,EAAE,SAAS,EAAE,GAAG,IAAI,cAAc,CAAC,CAAC;IAEhF,OAAO;QACL,IAAI,EAAE,cAAc,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,IAAI,OAAO,CAAC,IAAI,GAAG,SAAS,IAAI,MAAM,IAAI,OAAO,EAAE;QACvF,MAAM;QACN,MAAM;QACN,OAAO;QACP,WAAW;QACX,KAAK,EAAE,UAAU,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,IAAI,QAAQ,EAAE,aAAa,EAAE,QAAQ,CAAC,KAAK,CAAC;QAC9E,MAAM,EAAE,UAAU,CAAC,KAAK,CAAC,MAAM,EAAE,GAAG,IAAI,SAAS,EAAE,cAAc,EAAE,QAAQ,CAAC,MAAM,IAAI,OAAO,CAAC;QAC9F,SAAS,EAAE,UAAU,CACnB,KAAK,CAAC,SAAS,EACf,GAAG,IAAI,YAAY,EACnB,eAAe,EACf,QAAQ,CAAC,SAAS,CACnB;KACoB,CAAC;AAC1B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAC9B,KAAc,EACd,OAA4C;IAE5C,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,iBAAiB,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;IACnF,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IACpC,SAAS,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;IACpC,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS;QAAE,OAAO,iBAAiB,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;IAEzF,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAClD,SAAS,CAAC,KAAK,EAAE,WAAW,EAAE,cAAc,CAAC,CAAC;IAE9C,MAAM,YAAY,GAAG,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,uBAAuB,CAAC,CAAC;IACrE,SAAS,CAAC,YAAY,EAAE,YAAY,EAAE,uBAAuB,CAAC,CAAC;IAC/D,MAAM,QAAQ,GAAkB;QAC9B,KAAK,EAAE,UAAU,CAAC,YAAY,CAAC,KAAK,EAAE,6BAA6B,EAAE,aAAa,EAAE,SAAS,CAAC;QAC9F,SAAS,EAAE,UAAU,CACnB,YAAY,CAAC,SAAS,EACtB,iCAAiC,EACjC,eAAe,EACf,UAAU,CACX;QACD,GAAG,CAAC,YAAY,CAAC,OAAO,KAAK,SAAS;YACpC,CAAC,CAAC;gBACE,OAAO,EAAE,UAAU,CACjB,YAAY,CAAC,OAAO,EACpB,+BAA+B,EAC/B,QAAQ,EACR,SAAS,CACV;aACF;YACH,CAAC,CAAC,EAAE,CAAC;QACP,GAAG,CAAC,YAAY,CAAC,MAAM,KAAK,SAAS;YACnC,CAAC,CAAC;gBACE,MAAM,EAAE,UAAU,CAChB,YAAY,CAAC,MAAM,EACnB,8BAA8B,EAC9B,cAAc,EACd,OAAO,CACR;aACF;YACH,CAAC,CAAC,EAAE,CAAC;KACR,CAAC;IAEF,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC;IAC/B,IAAI,QAAQ,KAAK,SAAS,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC;QACpD,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;IACzD,MAAM,IAAI,GAAG,CAAC,QAAQ,IAAI,EAAE,CAAc,CAAC;IAC3C,IAAI,CAAC,IAAI,CAAC,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,sDAAsD,CAAC,CAAC;IAE1F,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAC;IAC3F,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;QAC3B,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,sCAAsC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QAC5F,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACtB,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,CAAC;AAC/F,CAAC"}
@@ -0,0 +1,87 @@
1
+ import type { AgentDiagnostic, AgentProfile, AgentTarget } from "../types.js";
2
+ import type { VerifyConfig, VersionBound } from "./config.js";
3
+ import { treeMatches } from "./compare.js";
4
+ /**
5
+ * Checks that the agent-facing files committed in a repository are still what
6
+ * the bundles they came from render, and that the toolchain doing the checking
7
+ * is the one the repository pinned.
8
+ *
9
+ * Pins are asserted against the *running* CLI rather than against provenance
10
+ * recorded in the tree. Together with byte equality that proves the tree was
11
+ * produced by a conforming cairn, without requiring a provenance document to
12
+ * exist — which matters, because an install manifest records a generator
13
+ * version but neither a profile schema version nor a documentation revision.
14
+ * Provenance found at a destination is corroboration only, and never decides
15
+ * the verdict.
16
+ */
17
+ export type PinStatus = "satisfied" | "violated" | "unpinned";
18
+ export interface PinReport<T> {
19
+ declared: T | null;
20
+ actual: string;
21
+ status: PinStatus;
22
+ }
23
+ export interface VerifyPinsReport {
24
+ cli: PinReport<VersionBound>;
25
+ profileSchemaVersion: PinReport<string>;
26
+ targets: Array<{
27
+ target: AgentTarget;
28
+ } & PinReport<VersionBound>>;
29
+ }
30
+ export interface VerifyProvenance {
31
+ source: string;
32
+ generator: {
33
+ name: string;
34
+ version: string;
35
+ } | null;
36
+ profileSchemaVersion: string | null;
37
+ status: "matching" | "older" | "newer" | "malformed";
38
+ }
39
+ export interface VerifyEntryReport {
40
+ name: string;
41
+ bundle: string;
42
+ target: AgentTarget;
43
+ profile: AgentProfile;
44
+ scope: string;
45
+ layout: string;
46
+ destination: string;
47
+ mode: string;
48
+ expected: number;
49
+ missing: string[];
50
+ changed: string[];
51
+ orphaned: string[];
52
+ unmanaged: string[];
53
+ provenance?: VerifyProvenance;
54
+ ok: boolean;
55
+ }
56
+ export interface VerifyReport {
57
+ config: {
58
+ path: string;
59
+ entries: number;
60
+ };
61
+ generator: {
62
+ name: string;
63
+ version: string;
64
+ };
65
+ profileSchemaVersion: string;
66
+ pins: VerifyPinsReport;
67
+ entries: VerifyEntryReport[];
68
+ counts: {
69
+ entries: number;
70
+ ok: number;
71
+ missing: number;
72
+ changed: number;
73
+ orphaned: number;
74
+ unmanaged: number;
75
+ };
76
+ }
77
+ export declare function evaluatePins(config: VerifyConfig, targets: readonly AgentTarget[]): {
78
+ report: VerifyPinsReport;
79
+ diagnostics: AgentDiagnostic[];
80
+ };
81
+ export declare function runVerify(config: VerifyConfig): {
82
+ report: VerifyReport;
83
+ diagnostics: AgentDiagnostic[];
84
+ targets: AgentTarget[];
85
+ profiles: AgentProfile[];
86
+ };
87
+ export { treeMatches };