patchwork-os 1.2.0-beta.2.canary.756 → 1.2.0-beta.2.canary.757
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 +114 -50
- package/dist/index.js.map +1 -1
- package/dist/workers/authorityDelta.d.ts +69 -0
- package/dist/workers/authorityDelta.js +252 -0
- package/dist/workers/authorityDelta.js.map +1 -0
- package/package.json +1 -1
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
|
-
(
|
|
4376
|
-
|
|
4377
|
-
const
|
|
4378
|
-
const
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
const
|
|
4383
|
-
|
|
4384
|
-
|
|
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
|
-
|
|
4388
|
-
|
|
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
|
|
4403
|
-
const
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
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
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
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.
|