patchwork-os 1.2.0-beta.2.canary.756 → 1.2.0-beta.2.canary.758

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/index.js CHANGED
@@ -4350,10 +4350,17 @@ if (process.argv[2] === "workers") {
4350
4350
  if ((sub !== "shadow" &&
4351
4351
  sub !== "backtest" &&
4352
4352
  sub !== "list" &&
4353
- sub !== "validate") ||
4353
+ sub !== "validate" &&
4354
+ sub !== "authority-delta") ||
4354
4355
  args.includes("--help") ||
4355
4356
  args.includes("-h")) {
4356
- process.stdout.write("Usage: patchwork workers <list|validate|shadow|backtest> [--workers-dir <path>]\n\n" +
4357
+ process.stdout.write("Usage: patchwork workers <list|validate|authority-delta|shadow|backtest> [--workers-dir <path>]\n\n" +
4358
+ " authority-delta What a change does to worker AUTHORITY, comparing manifests\n" +
4359
+ " between two git refs (--base, --head, --dir). Reuses the runtime\n" +
4360
+ " policy primitives, with one inversion: an unreadable `forbids`\n" +
4361
+ " entry fails OPEN at runtime and is reported here as a WIDENING,\n" +
4362
+ ' because "I could not read your deny-list" must never resolve to\n' +
4363
+ ' "looks fine". Exits 1 on a widening.\n' +
4357
4364
  " list What is installed, and — the point — what the bridge IGNORES.\n" +
4358
4365
  " A manifest that does not parse is skipped by the loader, so no\n" +
4359
4366
  " worker owns its recipe and the autonomy gate never runs for it.\n" +
@@ -4372,57 +4379,114 @@ if (process.argv[2] === "workers") {
4372
4379
  " --workers-dir <path> Where *.worker.yaml live (default ~/.patchwork/workers)\n");
4373
4380
  process.exit(0);
4374
4381
  }
4375
- (async () => {
4376
- try {
4377
- const dirIdx = args.indexOf("--workers-dir");
4378
- const workersDir = dirIdx !== -1 ? args[dirIdx + 1] : undefined;
4379
- if (sub === "list" || sub === "validate") {
4380
- const { scanWorkers, validateWorkers, formatWorkersList, formatWorkersValidate, defaultWorkersDir, defaultRecipesDir, } = await import("./workers/workersCli.js");
4381
- const dir = workersDir ?? defaultWorkersDir();
4382
- const json = args.includes("--json");
4383
- if (sub === "list") {
4384
- const scan = scanWorkers(dir);
4382
+ if (sub === "authority-delta") {
4383
+ (async () => {
4384
+ const { execFileSync } = await import("node:child_process");
4385
+ const { parse: parseYaml } = await import("yaml");
4386
+ const { parseWorker } = await import("./workers/worker.js");
4387
+ const { authorityDeltaForSet, formatAuthorityDeltas, widensAuthority } = await import("./workers/authorityDelta.js");
4388
+ const argAfter = (flag) => {
4389
+ const i = args.indexOf(flag);
4390
+ return i >= 0 ? args[i + 1] : undefined;
4391
+ };
4392
+ const base = argAfter("--base") ?? "main";
4393
+ const head = argAfter("--head") ?? "HEAD";
4394
+ const dir = argAfter("--dir") ?? "templates/workers";
4395
+ const git = (a) => execFileSync("git", a, { encoding: "utf-8" });
4396
+ const listAt = (ref) => {
4397
+ const m = new Map();
4398
+ let files = [];
4399
+ try {
4400
+ files = git(["ls-tree", "-r", "--name-only", ref, "--", dir])
4401
+ .split("\n")
4402
+ .filter((f) => /\.ya?ml$/i.test(f));
4403
+ }
4404
+ catch {
4405
+ // A ref that cannot be read is a usage error, not "no manifests" —
4406
+ // returning empty would report every worker as newly added, which
4407
+ // this classifier treats as a widening. Failing loudly is the only
4408
+ // honest answer.
4409
+ process.stderr.write(`[authority] cannot read ref "${ref}"\n`);
4410
+ process.exit(2);
4411
+ }
4412
+ for (const f of files) {
4413
+ try {
4414
+ m.set(f, parseWorker(parseYaml(git(["show", `${ref}:${f}`]))));
4415
+ }
4416
+ catch {
4417
+ // An unparseable manifest is SKIPPED by the loader at runtime, so
4418
+ // the worker governs nothing. Omitting it here would masquerade as
4419
+ // a deletion — itself reported as a widening — so say what
4420
+ // happened and point at the verb that diagnoses it.
4421
+ process.stderr.write(`[authority] ${ref}:${f} does not parse — run \`patchwork workers validate\`\n`);
4422
+ }
4423
+ }
4424
+ return m;
4425
+ };
4426
+ const deltas = authorityDeltaForSet(listAt(base), listAt(head));
4427
+ if (args.includes("--json")) {
4428
+ process.stdout.write(`${JSON.stringify(deltas, null, 2)}\n`);
4429
+ }
4430
+ else {
4431
+ process.stdout.write(`${formatAuthorityDeltas(deltas)}\n`);
4432
+ }
4433
+ // Exit 1 on a widening so CI can gate on it. A widening is not a defect;
4434
+ // it is a change that requires a person to say so.
4435
+ process.exit(widensAuthority(deltas) ? 1 : 0);
4436
+ })();
4437
+ }
4438
+ else
4439
+ (async () => {
4440
+ try {
4441
+ const dirIdx = args.indexOf("--workers-dir");
4442
+ const workersDir = dirIdx !== -1 ? args[dirIdx + 1] : undefined;
4443
+ if (sub === "list" || sub === "validate") {
4444
+ const { scanWorkers, validateWorkers, formatWorkersList, formatWorkersValidate, defaultWorkersDir, defaultRecipesDir, } = await import("./workers/workersCli.js");
4445
+ const dir = workersDir ?? defaultWorkersDir();
4446
+ const json = args.includes("--json");
4447
+ if (sub === "list") {
4448
+ const scan = scanWorkers(dir);
4449
+ process.stdout.write(json
4450
+ ? `${JSON.stringify({
4451
+ dir: scan.dir,
4452
+ loaded: scan.loaded.map((l) => ({
4453
+ file: l.file,
4454
+ id: l.worker.id,
4455
+ name: l.worker.name,
4456
+ recipe: l.worker.recipe ?? null,
4457
+ autonomyCeiling: l.worker.autonomyCeiling,
4458
+ owns: l.worker.owns,
4459
+ })),
4460
+ broken: scan.broken,
4461
+ }, null, 2)}\n`
4462
+ : formatWorkersList(scan));
4463
+ // A manifest the bridge ignores is a governance gap, not a note.
4464
+ process.exit(scan.broken.length > 0 ? 1 : 0);
4465
+ }
4466
+ const tIdx = args.indexOf("--templates-dir");
4467
+ const templatesDir = tIdx !== -1 ? args[tIdx + 1] : undefined;
4468
+ const rIdx = args.indexOf("--recipes-dir");
4469
+ const result = validateWorkers({
4470
+ workersDir: dir,
4471
+ recipesDir: rIdx !== -1 ? args[rIdx + 1] : defaultRecipesDir(),
4472
+ ...(templatesDir ? { templatesDir } : {}),
4473
+ });
4385
4474
  process.stdout.write(json
4386
- ? `${JSON.stringify({
4387
- dir: scan.dir,
4388
- loaded: scan.loaded.map((l) => ({
4389
- file: l.file,
4390
- id: l.worker.id,
4391
- name: l.worker.name,
4392
- recipe: l.worker.recipe ?? null,
4393
- autonomyCeiling: l.worker.autonomyCeiling,
4394
- owns: l.worker.owns,
4395
- })),
4396
- broken: scan.broken,
4397
- }, null, 2)}\n`
4398
- : formatWorkersList(scan));
4399
- // A manifest the bridge ignores is a governance gap, not a note.
4400
- process.exit(scan.broken.length > 0 ? 1 : 0);
4475
+ ? `${JSON.stringify(result, null, 2)}\n`
4476
+ : formatWorkersValidate(result));
4477
+ process.exit(result.healthy ? 0 : 1);
4401
4478
  }
4402
- const tIdx = args.indexOf("--templates-dir");
4403
- const templatesDir = tIdx !== -1 ? args[tIdx + 1] : undefined;
4404
- const rIdx = args.indexOf("--recipes-dir");
4405
- const result = validateWorkers({
4406
- workersDir: dir,
4407
- recipesDir: rIdx !== -1 ? args[rIdx + 1] : defaultRecipesDir(),
4408
- ...(templatesDir ? { templatesDir } : {}),
4409
- });
4410
- process.stdout.write(json
4411
- ? `${JSON.stringify(result, null, 2)}\n`
4412
- : formatWorkersValidate(result));
4413
- process.exit(result.healthy ? 0 : 1);
4479
+ const { runWorkerShadowReport, runWorkerBacktest } = await import("./workers/runWorkerShadow.js");
4480
+ const opts = workersDir ? { workersDir } : {};
4481
+ process.stdout.write(sub === "backtest"
4482
+ ? runWorkerBacktest(opts)
4483
+ : runWorkerShadowReport(opts));
4414
4484
  }
4415
- const { runWorkerShadowReport, runWorkerBacktest } = await import("./workers/runWorkerShadow.js");
4416
- const opts = workersDir ? { workersDir } : {};
4417
- process.stdout.write(sub === "backtest"
4418
- ? runWorkerBacktest(opts)
4419
- : runWorkerShadowReport(opts));
4420
- }
4421
- catch (err) {
4422
- process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}\n`);
4423
- process.exit(1);
4424
- }
4425
- })();
4485
+ catch (err) {
4486
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}\n`);
4487
+ process.exit(1);
4488
+ }
4489
+ })();
4426
4490
  }
4427
4491
  // Handle approvals subcommand — considered-approval KPI. Reads the local
4428
4492
  // approval-decision log directly (like `workers shadow`); no bridge round-trip.