@microck/canonfig 2.0.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.
Files changed (68) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +263 -0
  3. package/dist/agent/agent-resolution.errors.js +42 -0
  4. package/dist/agent/agent-resolution.layer.js +204 -0
  5. package/dist/agent/agent-resolution.service.js +2259 -0
  6. package/dist/agent/agent-resolution.types.js +1 -0
  7. package/dist/agent/controlled-executor.js +704 -0
  8. package/dist/agent/harness-adapters.js +85 -0
  9. package/dist/cli/cli.js +618 -0
  10. package/dist/cli/exit-codes.js +28 -0
  11. package/dist/cli/follower-commands.js +3 -0
  12. package/dist/cli/render.js +56 -0
  13. package/dist/cli/source-commands.js +5 -0
  14. package/dist/domain/brand.js +29 -0
  15. package/dist/domain/identity.js +31 -0
  16. package/dist/domain/npm-package-spec.js +186 -0
  17. package/dist/domain/profile.js +950 -0
  18. package/dist/domain/recipe-versions.js +297 -0
  19. package/dist/domain/resource.js +259 -0
  20. package/dist/domain/synchronization.js +346 -0
  21. package/dist/enrollment/enrollment.errors.js +43 -0
  22. package/dist/enrollment/enrollment.layer.js +724 -0
  23. package/dist/enrollment/enrollment.service.js +3 -0
  24. package/dist/enrollment/enrollment.types.js +59 -0
  25. package/dist/enrollment/follower-client.js +585 -0
  26. package/dist/enrollment/source-server.js +313 -0
  27. package/dist/machine/linux.layer.js +1183 -0
  28. package/dist/machine/machine-state.errors.js +52 -0
  29. package/dist/machine/machine-state.service.js +3 -0
  30. package/dist/machine/machine-state.types.js +1 -0
  31. package/dist/machine/macos.layer.js +470 -0
  32. package/dist/machine/windows.layer.js +879 -0
  33. package/dist/profile/discovery.js +740 -0
  34. package/dist/profile/profile-catalog.errors.js +50 -0
  35. package/dist/profile/profile-catalog.layer.js +20 -0
  36. package/dist/profile/profile-catalog.service.js +7 -0
  37. package/dist/profile/profile-codec.js +153 -0
  38. package/dist/profile/publication.js +298 -0
  39. package/dist/profile/tool-catalog.js +384 -0
  40. package/dist/runtime/doctor.js +306 -0
  41. package/dist/runtime/layers.js +706 -0
  42. package/dist/runtime/main.js +38 -0
  43. package/dist/schedule/linux-schedule.js +24 -0
  44. package/dist/schedule/macos-schedule.js +25 -0
  45. package/dist/schedule/schedule-manager.errors.js +17 -0
  46. package/dist/schedule/schedule-manager.layer.js +205 -0
  47. package/dist/schedule/schedule-manager.service.js +3 -0
  48. package/dist/schedule/schedule-manager.types.js +114 -0
  49. package/dist/schedule/windows-schedule.js +25 -0
  50. package/dist/state/state-repository.errors.js +55 -0
  51. package/dist/state/state-repository.layer.js +1507 -0
  52. package/dist/state/state-repository.service.js +3 -0
  53. package/dist/state/state-repository.types.js +1 -0
  54. package/dist/state/state-schema.js +298 -0
  55. package/dist/synchronization/config-codec.js +97 -0
  56. package/dist/synchronization/executor.js +700 -0
  57. package/dist/synchronization/follower-orchestration.js +939 -0
  58. package/dist/synchronization/follower-sync-config.js +81 -0
  59. package/dist/synchronization/npm-artifact.js +670 -0
  60. package/dist/synchronization/planner.js +378 -0
  61. package/dist/synchronization/recovery.js +397 -0
  62. package/dist/synchronization/resource-executors.js +1198 -0
  63. package/dist/synchronization/resource-plans.js +645 -0
  64. package/dist/synchronization/synchronization.errors.js +102 -0
  65. package/dist/synchronization/synchronization.layer.js +97 -0
  66. package/dist/synchronization/synchronization.service.js +11 -0
  67. package/dist/synchronization/synchronization.types.js +1 -0
  68. package/package.json +66 -0
@@ -0,0 +1,384 @@
1
+ import { Schema } from "effect";
2
+ import { AgentTaskId } from "../domain/brand.js";
3
+ import { parseNpmPackageSpecification } from "../domain/npm-package-spec.js";
4
+ import { canonicalRecipeIndexUrl, defaultPythonIndex, isSafeSourceRevision, recipeValidationError, } from "../domain/recipe-versions.js";
5
+ const compareText = (left, right) => left < right ? -1 : left > right ? 1 : 0;
6
+ const locationKey = (location) => {
7
+ switch (location.kind) {
8
+ case "line":
9
+ return `line:${String(location.line).padStart(10, "0")}:${String(location.column ?? 0).padStart(10, "0")}`;
10
+ case "field":
11
+ return `field:${location.field}:${String(location.line ?? 0).padStart(10, "0")}`;
12
+ }
13
+ };
14
+ const packageKey = (metadata) => metadata === undefined
15
+ ? ""
16
+ : [
17
+ metadata.ecosystem,
18
+ metadata.name,
19
+ metadata.version ?? "",
20
+ metadata.source,
21
+ metadata.integrity ?? "",
22
+ metadata.indexPolicy === undefined ? "" : JSON.stringify(metadata.indexPolicy),
23
+ metadata.upstream ?? "",
24
+ (metadata.buildCommands ?? []).map((command) => command.join("\0")).join("\u0001"),
25
+ metadata.buildPolicy === undefined ? "" : JSON.stringify(metadata.buildPolicy),
26
+ ].join("\0");
27
+ const evidenceKey = (evidence) => [
28
+ evidence.sourcePath,
29
+ locationKey(evidence.location),
30
+ evidence.kind,
31
+ evidence.invocation.join("\0"),
32
+ evidence.resolvedExecutable ?? "",
33
+ packageKey(evidence.package),
34
+ evidence.upstream ?? "",
35
+ evidence.confidence,
36
+ evidence.reviewStatus,
37
+ ].join("\u0002");
38
+ export const orderAndDeduplicateEvidence = (evidence) => {
39
+ const byKey = new Map();
40
+ for (const record of evidence)
41
+ byKey.set(evidenceKey(record), record);
42
+ return [...byKey.entries()]
43
+ .sort(([left], [right]) => compareText(left, right))
44
+ .map(([, record]) => record);
45
+ };
46
+ const packageUpstream = (metadata) => {
47
+ if (metadata.upstream !== undefined)
48
+ return metadata.upstream;
49
+ switch (metadata.ecosystem) {
50
+ case "npm":
51
+ return `https://www.npmjs.com/package/${metadata.name}`;
52
+ case "homebrew":
53
+ return `https://formulae.brew.sh/formula/${metadata.name}`;
54
+ case "winget":
55
+ return `https://winget.run/pkg/${metadata.name.replaceAll(".", "/")}`;
56
+ case "uv":
57
+ return `https://pypi.org/project/${metadata.name}/`;
58
+ case "cargo":
59
+ return `https://crates.io/crates/${metadata.name}`;
60
+ case "source":
61
+ return metadata.source.startsWith("https://") ? metadata.source : undefined;
62
+ }
63
+ };
64
+ const isUnboundedPackageSpecification = (value) => value === "--"
65
+ || /^\s*-{1,2}\S*/u.test(value)
66
+ || /\s/u.test(value)
67
+ || /^(?:git\+|git:\/\/|github:|gitlab:|bitbucket:|git@|file:|link:|workspace:|https?:\/\/)/iu
68
+ .test(value)
69
+ || /(?:^|@)(?:npm:|git\+|git:\/\/|github:|gitlab:|bitbucket:|git@|file:|link:|workspace:|https?:\/\/)/iu
70
+ .test(value);
71
+ const isUnboundedSourceReference = (value) => value === "--"
72
+ || /^\s*-{1,2}\S*/u.test(value)
73
+ || /^(?:git\+|git:\/\/|github:|gitlab:|bitbucket:|git@|file:|link:|workspace:|https?:\/\/.+\.git(?:#.*)?$)/iu
74
+ .test(value)
75
+ || /(?:^|@)(?:npm:|git\+|git:\/\/|github:|gitlab:|bitbucket:|git@|file:|link:|workspace:|https?:\/\/.+\.git(?:#.*)?$)/iu
76
+ .test(value);
77
+ const isUnboundedNpmSpecification = (value) => isUnboundedPackageSpecification(value)
78
+ || parseNpmPackageSpecification(value).kind !== "registry";
79
+ const recipeFromPackage = (metadata) => {
80
+ const version = metadata.version;
81
+ if (version === undefined || version.trim().length === 0)
82
+ return undefined;
83
+ if (metadata.ecosystem !== "source"
84
+ && (isUnboundedSourceReference(metadata.source)
85
+ || (metadata.ecosystem === "npm" && isUnboundedNpmSpecification(metadata.name)))) {
86
+ return undefined;
87
+ }
88
+ if (metadata.ecosystem === "source" && !isSafeSourceRevision(version)) {
89
+ return undefined;
90
+ }
91
+ const recipeMethod = metadata.ecosystem === "homebrew"
92
+ ? "homebrew"
93
+ : metadata.ecosystem;
94
+ if (metadata.ecosystem !== "source" && recipeValidationError({
95
+ method: recipeMethod,
96
+ package: metadata.name,
97
+ version,
98
+ source: metadata.source,
99
+ integrity: metadata.integrity,
100
+ indexPolicy: metadata.indexPolicy,
101
+ }) !== undefined) {
102
+ return undefined;
103
+ }
104
+ const buildPolicy = metadata.buildPolicy ?? { mode: "scripts-disabled" };
105
+ switch (metadata.ecosystem) {
106
+ case "npm": {
107
+ const specification = `${metadata.name}@${version}`;
108
+ return {
109
+ method: "npm",
110
+ package: metadata.name,
111
+ version,
112
+ source: metadata.source,
113
+ integrity: metadata.integrity,
114
+ indexPolicy: metadata.indexPolicy,
115
+ buildPolicy,
116
+ command: buildPolicy.mode === "scripts-disabled"
117
+ ? ["npm", "install", "--global", specification, "--ignore-scripts"]
118
+ : ["npm", "install", "--global", specification],
119
+ };
120
+ }
121
+ case "homebrew":
122
+ return {
123
+ method: "homebrew",
124
+ formula: metadata.name,
125
+ version,
126
+ source: metadata.source,
127
+ integrity: metadata.integrity,
128
+ indexPolicy: metadata.indexPolicy,
129
+ buildPolicy,
130
+ command: ["brew", "install", `${metadata.name}@${version}`],
131
+ };
132
+ case "winget":
133
+ return {
134
+ method: "winget",
135
+ id: metadata.name,
136
+ version,
137
+ source: metadata.source,
138
+ integrity: metadata.integrity,
139
+ indexPolicy: metadata.indexPolicy,
140
+ buildPolicy,
141
+ command: ["winget", "install", "--id", metadata.name, "--version", version, "--exact"],
142
+ };
143
+ case "uv": {
144
+ const specification = `${metadata.name}==${version}`;
145
+ const index = canonicalRecipeIndexUrl(metadata.indexPolicy?.url ?? defaultPythonIndex) ?? defaultPythonIndex;
146
+ return {
147
+ method: "uv",
148
+ package: metadata.name,
149
+ version,
150
+ source: metadata.source,
151
+ integrity: metadata.integrity,
152
+ indexPolicy: metadata.indexPolicy,
153
+ buildPolicy,
154
+ command: buildPolicy.mode === "scripts-disabled"
155
+ ? [
156
+ "uv",
157
+ "tool",
158
+ "install",
159
+ specification,
160
+ "--only-binary=:all:",
161
+ "--no-config",
162
+ `--default-index=${index}`,
163
+ ]
164
+ : [
165
+ "uv",
166
+ "tool",
167
+ "install",
168
+ specification,
169
+ "--no-config",
170
+ `--default-index=${index}`,
171
+ ],
172
+ };
173
+ }
174
+ case "cargo":
175
+ return {
176
+ method: "cargo",
177
+ crate: metadata.name,
178
+ version,
179
+ source: metadata.source,
180
+ integrity: metadata.integrity,
181
+ indexPolicy: metadata.indexPolicy,
182
+ buildPolicy,
183
+ command: ["cargo", "install", metadata.name, "--version", version, "--locked"],
184
+ };
185
+ case "source": {
186
+ const upstream = packageUpstream(metadata);
187
+ if (upstream === undefined
188
+ || metadata.buildPolicy?.mode !== "required"
189
+ || metadata.buildPolicy.steps.length === 0) {
190
+ return undefined;
191
+ }
192
+ return {
193
+ method: "source",
194
+ repository: upstream,
195
+ revision: version,
196
+ version,
197
+ source: metadata.source,
198
+ integrity: metadata.integrity,
199
+ indexPolicy: metadata.indexPolicy,
200
+ buildPolicy: metadata.buildPolicy,
201
+ buildCommands: metadata.buildPolicy.steps.map((step) => [
202
+ step.executable,
203
+ ...step.arguments,
204
+ ]),
205
+ };
206
+ }
207
+ }
208
+ };
209
+ const recipeKey = (recipe) => {
210
+ const index = recipe.indexPolicy === undefined
211
+ ? ""
212
+ : JSON.stringify(recipe.indexPolicy);
213
+ switch (recipe.method) {
214
+ case "npm":
215
+ return `${recipe.method}\0${recipe.package}\0${recipe.version}\0${recipe.source}\0${recipe.integrity ?? ""}\0${index}\0${JSON.stringify(recipe.buildPolicy)}`;
216
+ case "homebrew":
217
+ return `${recipe.method}\0${recipe.formula}\0${recipe.version}\0${recipe.source}\0${recipe.integrity ?? ""}\0${index}\0${JSON.stringify(recipe.buildPolicy)}`;
218
+ case "winget":
219
+ return `${recipe.method}\0${recipe.id}\0${recipe.version}\0${recipe.source}\0${recipe.integrity ?? ""}\0${index}\0${JSON.stringify(recipe.buildPolicy)}`;
220
+ case "uv":
221
+ return `${recipe.method}\0${recipe.package}\0${recipe.version}\0${recipe.source}\0${recipe.integrity ?? ""}\0${index}\0${JSON.stringify(recipe.buildPolicy)}`;
222
+ case "cargo":
223
+ return `${recipe.method}\0${recipe.crate}\0${recipe.version}\0${recipe.source}\0${recipe.integrity ?? ""}\0${index}\0${JSON.stringify(recipe.buildPolicy)}`;
224
+ case "source":
225
+ return `${recipe.method}\0${recipe.repository}\0${recipe.revision}\0${JSON.stringify(recipe.buildPolicy)}\0${recipe.buildCommands.map((command) => command.join("\u0001")).join("\u0002")}`;
226
+ }
227
+ };
228
+ const toolIdForEvidence = (evidence) => {
229
+ const packageName = evidence.package?.name;
230
+ const raw = packageName === undefined
231
+ ? evidence.invocation[0] ?? "unknown-tool"
232
+ : packageName.includes("/")
233
+ ? packageName.slice(packageName.lastIndexOf("/") + 1)
234
+ : packageName;
235
+ return raw
236
+ .replace(/^@/u, "")
237
+ .replace(/[^A-Za-z0-9._-]+/gu, "-")
238
+ .replace(/^-+|-+$/gu, "")
239
+ .toLowerCase() || "unknown-tool";
240
+ };
241
+ const executableForEvidence = (evidence) => {
242
+ const invocation = evidence.invocation[0];
243
+ if (invocation !== undefined && !["npm", "npx", "brew", "winget", "uv", "uvx", "cargo", "git"].includes(invocation)) {
244
+ return invocation;
245
+ }
246
+ const name = evidence.package?.name ?? invocation ?? "unknown-tool";
247
+ return name.includes("/") ? name.slice(name.lastIndexOf("/") + 1) : name;
248
+ };
249
+ const taskId = (toolId, reason) => Schema.decodeUnknownSync(AgentTaskId)(`discovery-${toolId}-${reason}`);
250
+ const makeTask = (toolId, reason, evidence, upstream, bounds) => {
251
+ const evidenceText = evidence.map((record) => `${record.sourcePath}#${locationKey(record.location)} ${record.invocation.join(" ")}`);
252
+ const executables = [...new Set(evidence.flatMap((record) => {
253
+ const executable = record.invocation[0];
254
+ return executable === undefined ? [] : [executable];
255
+ }))].sort(compareText);
256
+ const derivedPaths = [...new Set(evidence.map((record) => record.sourcePath))].sort(compareText);
257
+ const derivedOrigins = upstream === undefined ? [] : [upstream];
258
+ return {
259
+ id: taskId(toolId, reason),
260
+ reason,
261
+ toolId,
262
+ summary: `Resolve ${reason.replaceAll("-", " ")} for ${toolId}`,
263
+ desiredOutcome: `Return reviewed package metadata and a verification command for ${toolId}`,
264
+ observedEvidence: evidenceText,
265
+ allowedCapabilities: bounds?.allowedCapabilities
266
+ ?? ["read-files", "resolve-executable", "lookup-package-metadata"],
267
+ lookupBounds: {
268
+ paths: bounds?.paths ?? derivedPaths,
269
+ executables: bounds?.executables ?? executables,
270
+ origins: bounds?.origins ?? derivedOrigins,
271
+ },
272
+ allowedPaths: bounds?.paths ?? derivedPaths,
273
+ allowedExecutables: bounds?.executables ?? executables,
274
+ allowedOrigins: bounds?.origins ?? derivedOrigins,
275
+ forbidden: ["elevation", "login", "restart", "reboot"],
276
+ timeLimitSeconds: bounds?.timeLimitSeconds ?? 60,
277
+ outputLimitBytes: bounds?.outputLimitBytes ?? 16_384,
278
+ verification: { command: [executables[0] ?? toolId, "--version"] },
279
+ };
280
+ };
281
+ const catalogToolWithBounds = (group, bounds) => {
282
+ const evidence = orderAndDeduplicateEvidence(group.records);
283
+ const upstream = evidence
284
+ .map((record) => record.upstream ?? (record.package === undefined ? undefined : packageUpstream(record.package)))
285
+ .find((value) => value !== undefined);
286
+ const recipeCandidates = evidence
287
+ .filter((record) => record.kind !== "prose" && record.reviewStatus === "accepted")
288
+ .flatMap((record) => {
289
+ const recipe = record.package === undefined ? undefined : recipeFromPackage(record.package);
290
+ return recipe === undefined ? [] : [recipe];
291
+ });
292
+ const byMethod = new Map();
293
+ for (const recipe of recipeCandidates) {
294
+ const recipes = byMethod.get(recipe.method) ?? new Map();
295
+ recipes.set(recipeKey(recipe), recipe);
296
+ byMethod.set(recipe.method, recipes);
297
+ }
298
+ const recipes = [];
299
+ let ambiguous = false;
300
+ for (const method of ["npm", "homebrew", "winget", "uv", "cargo", "source"]) {
301
+ const candidates = byMethod.get(method);
302
+ if (candidates === undefined)
303
+ continue;
304
+ if (candidates.size === 1)
305
+ recipes.push([...candidates.values()][0]);
306
+ else
307
+ ambiguous = true;
308
+ }
309
+ recipes.sort((left, right) => compareText(recipeKey(left), recipeKey(right)));
310
+ const acceptedEvidence = evidence.filter((record) => record.reviewStatus === "accepted");
311
+ const resolved = acceptedEvidence.some((record) => record.resolvedExecutable !== undefined);
312
+ const packageWithoutVersion = acceptedEvidence.some((record) => record.package !== undefined && record.package.version === undefined);
313
+ const tasks = [];
314
+ if (ambiguous)
315
+ tasks.push(makeTask(group.id, "ambiguous-recipe", evidence, upstream, bounds));
316
+ if (recipes.length === 0 && packageWithoutVersion) {
317
+ tasks.push(makeTask(group.id, "missing-version", evidence, upstream, bounds));
318
+ }
319
+ if (upstream === undefined)
320
+ tasks.push(makeTask(group.id, "missing-upstream", evidence, upstream, bounds));
321
+ if (!resolved && recipes.length === 0) {
322
+ tasks.push(makeTask(group.id, "unresolved-executable", evidence, upstream, bounds));
323
+ }
324
+ const executable = executableForEvidence(evidence[0]);
325
+ const reviewStatus = tasks.length === 0 && evidence.some((record) => record.reviewStatus === "accepted")
326
+ ? "accepted"
327
+ : "needs-review";
328
+ return {
329
+ tool: {
330
+ kind: "tool",
331
+ id: group.id,
332
+ executable,
333
+ upstream,
334
+ evidence,
335
+ recipes,
336
+ reviewStatus,
337
+ verify: { command: [executable, "--version"] },
338
+ },
339
+ tasks,
340
+ };
341
+ };
342
+ export const buildToolCatalog = (evidenceInput, skillsInput = [], taskBounds) => {
343
+ const evidence = orderAndDeduplicateEvidence(evidenceInput);
344
+ const groups = new Map();
345
+ for (const record of evidence) {
346
+ const id = toolIdForEvidence(record);
347
+ const records = groups.get(id) ?? [];
348
+ records.push(record);
349
+ groups.set(id, records);
350
+ }
351
+ const cataloged = [...groups.entries()]
352
+ .sort(([left], [right]) => compareText(left, right))
353
+ .map(([id, records]) => catalogToolWithBounds({ id, records }, taskBounds));
354
+ const tools = cataloged.map(({ tool }) => tool);
355
+ const skillGroups = new Map();
356
+ for (const skill of skillsInput) {
357
+ const records = skillGroups.get(skill.id) ?? [];
358
+ records.push(skill);
359
+ skillGroups.set(skill.id, records);
360
+ }
361
+ const skills = [...skillGroups.entries()]
362
+ .sort(([left], [right]) => compareText(left, right))
363
+ .map(([id, records]) => {
364
+ const ordered = records.sort((left, right) => compareText(left.sourcePath, right.sourcePath));
365
+ const reviewStatus = ordered.every((record) => record.reviewStatus === "accepted")
366
+ ? "accepted"
367
+ : "needs-review";
368
+ return {
369
+ kind: "skill",
370
+ id,
371
+ sourcePath: ordered[0].sourcePath,
372
+ target: ordered.find((record) => record.target !== undefined)?.target,
373
+ files: ordered.find((record) => record.files !== undefined)?.files,
374
+ evidence: orderAndDeduplicateEvidence(ordered.flatMap((record) => record.evidence)),
375
+ reviewStatus,
376
+ };
377
+ });
378
+ const agentTasks = cataloged
379
+ .flatMap(({ tasks }) => tasks)
380
+ .sort((left, right) => compareText(left.id, right.id));
381
+ const resources = [...tools, ...skills]
382
+ .sort((left, right) => compareText(`${left.kind}\0${left.id}`, `${right.kind}\0${right.id}`));
383
+ return { resources, tools, skills, evidence, agentTasks };
384
+ };