diff --git a/plugins/autonomy/.claude-plugin/plugin.json b/plugins/autonomy/.claude-plugin/plugin.json index 1da9ee8f53..1b090d79c3 100644 --- a/plugins/autonomy/.claude-plugin/plugin.json +++ b/plugins/autonomy/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "autonomy", - "version": "0.20.0", + "version": "0.21.0", "description": "Governed autonomous agent operation: role-topology, binding-seam, wiring-vs-advisor, telemetry, return-accounting, trigger-dispatch, per-work-class guardrail-matrix, standing-routine-catalog, and design-only runner-charter contracts for climbing the AI-adoption ladder, plus a guided-setup skill that discovers an adopting org's state, writes its schema-versioned binding, wires standards-pinned OTLP emission with a zero-cost file-artifact default, wires human-attested return capture at the task boundary, wires signal adapters with one governed dispatch entrypoint, binds the five-class guardrail matrix to an org's isolation substrates with an in-boundary live-validation probe before recording each fail-closed binding, and stands up standing-routine-catalog classes as scheduled temporal signal adapters behind the one governed queue with free scheduling defaults wired as reviewable changes and each routine's work-class mapping homed on the security surface.", "author": { "name": "Melodic Software", diff --git a/plugins/autonomy/CHANGELOG.md b/plugins/autonomy/CHANGELOG.md index fcd18a5f8e..33a3b3dc59 100644 --- a/plugins/autonomy/CHANGELOG.md +++ b/plugins/autonomy/CHANGELOG.md @@ -3,6 +3,24 @@ All notable changes to the `autonomy` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.21.0] + +### Added + +- **Deterministic prerequisite resolver** (#2724). + `skills/setup/scripts/resolve-prerequisites.mjs` resolves the verdict set for every + `v1` identity on a named scheduling surface, reading + `generated/identity-prerequisites.json` (never leaf prose). Repo-file and + harness-context probes compose `.claude/autonomy/binding.json` declarations, + `.claude/ecosystems/*.yaml` (resolved / `enabled: false` not configured), + `.work-item-tracker.json` + adapter `capabilities.json`, and `.mcp.json` + (presence vs enablement). Precedence follows ADR 0011 Decision 2: declaration + narrows; a ran-negative probe caps declarations (contradiction finding); + unprobeable stays distinct from absent. Output is wall-clock-free (byte-identical + consecutive runs). Liveness: engine health-check taxonomy row, fail-loud. + Seven graded fixtures under `skills/setup/scripts/fixtures/prerequisite-resolution/` with + co-located `*.test.sh` + manifest. + ## [0.20.0] ### Added diff --git a/plugins/autonomy/README.md b/plugins/autonomy/README.md index b67d085875..0450783a2b 100644 --- a/plugins/autonomy/README.md +++ b/plugins/autonomy/README.md @@ -50,7 +50,8 @@ state and records that binding. which catalog identities can run against a repository, composing owning seams rather than a new prober — with `deferred-class` marking `join:` rows that have no identities yet. Per-identity facts live in each `v1` leaf; `generated/identity-prerequisites.json` is the drift-gated - emission derived from those leaves (generator under `skills/setup/scripts/`). + emission derived from those leaves (generator under `skills/setup/scripts/`); + `skills/setup/scripts/resolve-prerequisites.mjs` is the deterministic per-surface resolver. - **Runner design pack** (`reference/runner.md`): the architect-ready design contract for the autonomous-drain runner — the composition spine and its eight seams, the lifecycle state model, the two-family stop-criteria taxonomy with terminal-handoff escalation and diff --git a/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/bare-repo/repo/.gitkeep b/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/bare-repo/repo/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/declared-absent-narrows/repo/.claude/autonomy/binding.json b/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/declared-absent-narrows/repo/.claude/autonomy/binding.json new file mode 100644 index 0000000000..d6371e92b0 --- /dev/null +++ b/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/declared-absent-narrows/repo/.claude/autonomy/binding.json @@ -0,0 +1,15 @@ +{ + "schema_version": "1.0", + "prerequisite_resolution": { + "schema_version": "1.0", + "declarations": [ + { + "surface": "ci-cron", + "identity": "issue-triage-sweep", + "need": "tracker", + "state": "absent", + "rung": "repo-local" + } + ] + } +} diff --git a/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/declared-absent-narrows/repo/.work-item-tracker.json b/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/declared-absent-narrows/repo/.work-item-tracker.json new file mode 100644 index 0000000000..3712808571 --- /dev/null +++ b/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/declared-absent-narrows/repo/.work-item-tracker.json @@ -0,0 +1 @@ +{"provider":"github"} diff --git a/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/declared-absent-narrows/repo/tools/work-item-tracker/adapters/github/capabilities.json b/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/declared-absent-narrows/repo/tools/work-item-tracker/adapters/github/capabilities.json new file mode 100644 index 0000000000..16da58397e --- /dev/null +++ b/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/declared-absent-narrows/repo/tools/work-item-tracker/adapters/github/capabilities.json @@ -0,0 +1 @@ +{"verbs":["create-item"]} diff --git a/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/fail-closed/repo/src/index.js b/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/fail-closed/repo/src/index.js new file mode 100644 index 0000000000..e14c4f2a95 --- /dev/null +++ b/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/fail-closed/repo/src/index.js @@ -0,0 +1 @@ +console.log(1) diff --git a/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/positive-verdict/repo/.claude/autonomy/binding.json b/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/positive-verdict/repo/.claude/autonomy/binding.json new file mode 100644 index 0000000000..47616244ee --- /dev/null +++ b/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/positive-verdict/repo/.claude/autonomy/binding.json @@ -0,0 +1,31 @@ +{ + "schema_version": "1.0", + "triggers": { + "surfaces": { + "ci-cron": { + "class": "temporal", + "transport": "poll", + "scheduler_class": "ci-cron", + "execution_surface": "github-actions", + "merge_policy_capable": true + } + } + }, + "prerequisite_resolution": { + "schema_version": "1.0", + "declarations": [ + { + "surface": "ci-cron", + "need": "advisory_database", + "state": "present", + "rung": "repo-local" + }, + { + "surface": "ci-cron", + "need": "upstream_changelog", + "state": "present", + "rung": "repo-local" + } + ] + } +} diff --git a/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/positive-verdict/repo/.claude/ecosystems/typescript.yaml b/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/positive-verdict/repo/.claude/ecosystems/typescript.yaml new file mode 100644 index 0000000000..3cf2a46ced --- /dev/null +++ b/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/positive-verdict/repo/.claude/ecosystems/typescript.yaml @@ -0,0 +1,2 @@ +enabled: true +globs: ["**/*.ts"] diff --git a/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/positive-verdict/repo/.github/workflows/ci.yml b/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/positive-verdict/repo/.github/workflows/ci.yml new file mode 100644 index 0000000000..c87443c43f --- /dev/null +++ b/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/positive-verdict/repo/.github/workflows/ci.yml @@ -0,0 +1 @@ +name: ci diff --git a/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/positive-verdict/repo/.work-item-tracker.json b/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/positive-verdict/repo/.work-item-tracker.json new file mode 100644 index 0000000000..3712808571 --- /dev/null +++ b/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/positive-verdict/repo/.work-item-tracker.json @@ -0,0 +1 @@ +{"provider":"github"} diff --git a/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/positive-verdict/repo/docs/README.md b/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/positive-verdict/repo/docs/README.md new file mode 100644 index 0000000000..d0fa60edab --- /dev/null +++ b/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/positive-verdict/repo/docs/README.md @@ -0,0 +1 @@ +# Docs diff --git a/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/positive-verdict/repo/package.json b/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/positive-verdict/repo/package.json new file mode 100644 index 0000000000..cd24f9eae3 --- /dev/null +++ b/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/positive-verdict/repo/package.json @@ -0,0 +1 @@ +{"name":"app","version":"1.0.0"} diff --git a/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/positive-verdict/repo/src/main.ts b/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/positive-verdict/repo/src/main.ts new file mode 100644 index 0000000000..01b2d72fa7 --- /dev/null +++ b/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/positive-verdict/repo/src/main.ts @@ -0,0 +1 @@ +code diff --git a/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/positive-verdict/repo/tools/work-item-tracker/adapters/github/capabilities.json b/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/positive-verdict/repo/tools/work-item-tracker/adapters/github/capabilities.json new file mode 100644 index 0000000000..16da58397e --- /dev/null +++ b/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/positive-verdict/repo/tools/work-item-tracker/adapters/github/capabilities.json @@ -0,0 +1 @@ +{"verbs":["create-item"]} diff --git a/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/posture-divergence/repo/.claude/ecosystems/typescript.yaml b/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/posture-divergence/repo/.claude/ecosystems/typescript.yaml new file mode 100644 index 0000000000..5a0df5e12f --- /dev/null +++ b/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/posture-divergence/repo/.claude/ecosystems/typescript.yaml @@ -0,0 +1,2 @@ +enabled: true +globs: ["**/*"] diff --git a/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/posture-divergence/repo/.github/workflows/ci.yml b/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/posture-divergence/repo/.github/workflows/ci.yml new file mode 100644 index 0000000000..c87443c43f --- /dev/null +++ b/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/posture-divergence/repo/.github/workflows/ci.yml @@ -0,0 +1 @@ +name: ci diff --git a/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/posture-divergence/repo/.work-item-tracker.json b/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/posture-divergence/repo/.work-item-tracker.json new file mode 100644 index 0000000000..3712808571 --- /dev/null +++ b/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/posture-divergence/repo/.work-item-tracker.json @@ -0,0 +1 @@ +{"provider":"github"} diff --git a/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/posture-divergence/repo/package.json b/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/posture-divergence/repo/package.json new file mode 100644 index 0000000000..5268a59e50 --- /dev/null +++ b/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/posture-divergence/repo/package.json @@ -0,0 +1 @@ +{"name":"app"} diff --git a/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/posture-divergence/repo/tools/work-item-tracker/adapters/github/capabilities.json b/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/posture-divergence/repo/tools/work-item-tracker/adapters/github/capabilities.json new file mode 100644 index 0000000000..16da58397e --- /dev/null +++ b/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/posture-divergence/repo/tools/work-item-tracker/adapters/github/capabilities.json @@ -0,0 +1 @@ +{"verbs":["create-item"]} diff --git a/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/probe-could-not-run/repo/.claude/autonomy/binding.json b/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/probe-could-not-run/repo/.claude/autonomy/binding.json new file mode 100644 index 0000000000..72e07e3cc8 --- /dev/null +++ b/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/probe-could-not-run/repo/.claude/autonomy/binding.json @@ -0,0 +1,15 @@ +{ + "schema_version": "1.0", + "prerequisite_resolution": { + "schema_version": "1.0", + "declarations": [ + { + "surface": "ci-cron", + "identity": "advisory-cve-triage", + "need": "advisory_database", + "state": "present", + "rung": "repo-local" + } + ] + } +} diff --git a/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/probe-could-not-run/repo/.github/workflows/ci.yml b/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/probe-could-not-run/repo/.github/workflows/ci.yml new file mode 100644 index 0000000000..c87443c43f --- /dev/null +++ b/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/probe-could-not-run/repo/.github/workflows/ci.yml @@ -0,0 +1 @@ +name: ci diff --git a/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/probe-could-not-run/repo/.work-item-tracker.json b/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/probe-could-not-run/repo/.work-item-tracker.json new file mode 100644 index 0000000000..3712808571 --- /dev/null +++ b/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/probe-could-not-run/repo/.work-item-tracker.json @@ -0,0 +1 @@ +{"provider":"github"} diff --git a/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/probe-could-not-run/repo/package.json b/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/probe-could-not-run/repo/package.json new file mode 100644 index 0000000000..5268a59e50 --- /dev/null +++ b/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/probe-could-not-run/repo/package.json @@ -0,0 +1 @@ +{"name":"app"} diff --git a/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/probe-could-not-run/repo/tools/work-item-tracker/adapters/github/capabilities.json b/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/probe-could-not-run/repo/tools/work-item-tracker/adapters/github/capabilities.json new file mode 100644 index 0000000000..16da58397e --- /dev/null +++ b/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/probe-could-not-run/repo/tools/work-item-tracker/adapters/github/capabilities.json @@ -0,0 +1 @@ +{"verbs":["create-item"]} diff --git a/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/probe-negative-caps/repo/.claude/autonomy/binding.json b/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/probe-negative-caps/repo/.claude/autonomy/binding.json new file mode 100644 index 0000000000..bf68b1fa4d --- /dev/null +++ b/plugins/autonomy/skills/setup/scripts/fixtures/prerequisite-resolution/probe-negative-caps/repo/.claude/autonomy/binding.json @@ -0,0 +1,15 @@ +{ + "schema_version": "1.0", + "prerequisite_resolution": { + "schema_version": "1.0", + "declarations": [ + { + "surface": "ci-cron", + "identity": "issue-triage-sweep", + "need": "tracker", + "state": "present", + "rung": "repo-local" + } + ] + } +} diff --git a/plugins/autonomy/skills/setup/scripts/resolve-prerequisites.fixtures.test.manifest.json b/plugins/autonomy/skills/setup/scripts/resolve-prerequisites.fixtures.test.manifest.json new file mode 100644 index 0000000000..58a94f7fea --- /dev/null +++ b/plugins/autonomy/skills/setup/scripts/resolve-prerequisites.fixtures.test.manifest.json @@ -0,0 +1,33 @@ +{ + "_comment": "Graded-fixture manifest for resolve-prerequisites.fixtures.test.mjs. Each key is a fixture directory under scripts/fixtures/prerequisite-resolution/. Assertions name identities and expected verdict substrings / finding kinds.", + "fixtures": { + "bare-repo": { + "surface": "ci-cron", + "assert": "bare-repo" + }, + "fail-closed": { + "surface": "ci-cron", + "assert": "fail-closed" + }, + "declared-absent-narrows": { + "surface": "ci-cron", + "assert": "declared-absent-narrows" + }, + "probe-negative-caps": { + "surface": "ci-cron", + "assert": "probe-negative-caps" + }, + "probe-could-not-run": { + "surface": "ci-cron", + "assert": "probe-could-not-run" + }, + "posture-divergence": { + "surface": "ci-cron", + "assert": "posture-divergence" + }, + "positive-verdict": { + "surface": "ci-cron", + "assert": "positive-verdict" + } + } +} diff --git a/plugins/autonomy/skills/setup/scripts/resolve-prerequisites.fixtures.test.mjs b/plugins/autonomy/skills/setup/scripts/resolve-prerequisites.fixtures.test.mjs new file mode 100755 index 0000000000..15d748d736 --- /dev/null +++ b/plugins/autonomy/skills/setup/scripts/resolve-prerequisites.fixtures.test.mjs @@ -0,0 +1,233 @@ +#!/usr/bin/env node + +// Graded-fixture suite for resolve-prerequisites.mjs. Every directory under +// scripts/fixtures/prerequisite-resolution/ must appear in the co-located +// manifest; each fixture is a mini repo tree graded by named assertions. +// +// Also asserts wall-clock-free reproducibility: two consecutive runs on the +// same tree emit byte-identical JSON. + +import { spawnSync } from "node:child_process"; +import { readdirSync, readFileSync, statSync } from "node:fs"; +import { dirname, join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import process from "node:process"; + +const scriptsDir = dirname(fileURLToPath(import.meta.url)); +const resolver = join(scriptsDir, "resolve-prerequisites.mjs"); +const fixturesRoot = resolve(scriptsDir, "fixtures", "prerequisite-resolution"); +const manifestPath = join( + scriptsDir, + "resolve-prerequisites.fixtures.test.manifest.json", +); + +const manifest = JSON.parse(readFileSync(manifestPath, "utf8")); +const fixtures = manifest.fixtures ?? {}; + +let failed = 0; +let cases = 0; +const pass = (name) => { + cases += 1; + process.stdout.write(`PASS: ${name}\n`); +}; +const fail = (name, detail) => { + cases += 1; + failed += 1; + process.stderr.write(`FAIL: ${name}\n detail: ${detail}\n`); +}; + +function runResolver(repoDir, surface) { + const result = spawnSync( + process.execPath, + [resolver, "--repo", repoDir, "--surface", surface], + { + encoding: "utf8", + maxBuffer: 10 * 1024 * 1024, + timeout: 30_000, + }, + ); + return result; +} + +function parseOut(result) { + if (result.error) throw new Error(result.error.message); + if (result.status !== 0) { + throw new Error(`exit ${result.status}: ${result.stderr.trim()}`); + } + return JSON.parse(result.stdout); +} + +function byIdentity(output) { + return new Map(output.identities.map((row) => [row.identity, row])); +} + +// --- Self-policing: every fixture directory is graded ---------------------- +const onDisk = readdirSync(fixturesRoot, { withFileTypes: true }) + .filter((e) => e.isDirectory()) + .map((e) => e.name) + .sort(); +for (const name of onDisk) { + if (Object.hasOwn(fixtures, name)) pass(`covered: ${name}`); + else fail(`covered: ${name}`, "fixture dir has no manifest entry"); +} +for (const name of Object.keys(fixtures)) { + if (onDisk.includes(name)) pass(`manifest-refs-exist: ${name}`); + else fail(`manifest-refs-exist: ${name}`, "manifest names missing fixture dir"); +} + +// --- Reproducibility on positive-verdict ----------------------------------- +{ + const repo = join(fixturesRoot, "positive-verdict", "repo"); + const a = runResolver(repo, "ci-cron"); + const b = runResolver(repo, "ci-cron"); + if (a.status === 0 && b.status === 0 && a.stdout === b.stdout) { + pass("reproducibility: byte-identical consecutive runs"); + } else { + fail( + "reproducibility: byte-identical consecutive runs", + `status a=${a.status} b=${b.status}; equal=${a.stdout === b.stdout}`, + ); + } +} + +function assertBareRepo(output) { + for (const row of output.identities) { + if (row.verdict === "supported" || row.verdict === "conditional") { + throw new Error(`${row.identity} verdict ${row.verdict} on bare repo`); + } + } +} + +function assertFailClosed(output) { + const row = byIdentity(output).get("issue-triage-sweep"); + if (!row) throw new Error("missing issue-triage-sweep"); + if (row.verdict === "supported" || row.verdict === "conditional") { + throw new Error(`issue-triage-sweep must not clear; got ${row.verdict}`); + } + if (row.verdict !== "unsupported" && row.verdict !== "unknown") { + throw new Error(`unexpected verdict ${row.verdict}`); + } +} + +function assertDeclaredAbsentNarrows(output) { + const row = byIdentity(output).get("issue-triage-sweep"); + if (!row) throw new Error("missing issue-triage-sweep"); + if (row.verdict !== "unsupported") { + throw new Error(`expected unsupported, got ${row.verdict}`); + } + const tracker = row.signals.find((s) => s.need === "tracker"); + if (!tracker || tracker.provenance.kind !== "declaration") { + throw new Error("expected declaration provenance on tracker"); + } + if (tracker.provenance.probe_result !== "present") { + throw new Error( + `probe should have found present; got ${tracker.provenance.probe_result}`, + ); + } +} + +function assertProbeNegativeCaps(output) { + const row = byIdentity(output).get("issue-triage-sweep"); + if (!row) throw new Error("missing issue-triage-sweep"); + if (row.verdict !== "unsupported") { + throw new Error(`expected unsupported, got ${row.verdict}`); + } + const finding = row.findings.find( + (f) => f.kind === "declaration-probe-contradiction", + ); + if (!finding) throw new Error("missing declaration-probe-contradiction finding"); +} + +function assertProbeCouldNotRun(output) { + const row = byIdentity(output).get("advisory-cve-triage"); + if (!row) throw new Error("missing advisory-cve-triage"); + if (row.verdict !== "conditional") { + throw new Error(`expected conditional (declaration stands), got ${row.verdict}`); + } + const advisory = row.signals.find((s) => s.need === "advisory_database"); + if (!advisory || advisory.provenance.kind !== "declaration") { + throw new Error("expected declaration provenance"); + } + if (!advisory.provenance.unprobeable) { + throw new Error("expected unprobeable named in provenance"); + } +} + +function assertPostureDivergence(output) { + const map = byIdentity(output); + const mech = map.get("dependency-update-wave/mechanical"); + const chg = map.get("dependency-update-wave/changelog-informed"); + if (!mech || !chg) throw new Error("missing dependency-update-wave postures"); + if (mech.verdict !== "supported") { + throw new Error(`mechanical expected supported, got ${mech.verdict}`); + } + if (chg.verdict === "supported") { + throw new Error("changelog-informed must not be supported without upstream"); + } + if (mech.verdict === chg.verdict) { + throw new Error(`verdicts should diverge; both ${mech.verdict}`); + } +} + +function assertPositiveVerdict(output) { + const row = byIdentity(output).get("issue-triage-sweep"); + if (!row) throw new Error("missing issue-triage-sweep"); + if (row.verdict !== "supported") { + throw new Error(`expected supported, got ${row.verdict}`); + } + const tracker = row.signals.find((s) => s.need === "tracker"); + if (!tracker || tracker.result !== "present") { + throw new Error("expected tracker present with provenance"); + } + if (!tracker.provenance || !tracker.provenance.kind) { + throw new Error("missing provenance on positive verdict"); + } +} + +const ASSERTIONS = { + "bare-repo": assertBareRepo, + "fail-closed": assertFailClosed, + "declared-absent-narrows": assertDeclaredAbsentNarrows, + "probe-negative-caps": assertProbeNegativeCaps, + "probe-could-not-run": assertProbeCouldNotRun, + "posture-divergence": assertPostureDivergence, + "positive-verdict": assertPositiveVerdict, +}; + +for (const [name, entry] of Object.entries(fixtures)) { + const repo = join(fixturesRoot, name, "repo"); + if (!statSync(repo).isDirectory()) { + fail(`grade: ${name}`, "missing repo/ directory"); + continue; + } + const result = runResolver(repo, entry.surface); + let output; + try { + output = parseOut(result); + } catch (error) { + fail(`grade: ${name}`, String(error.message ?? error)); + continue; + } + if (!output.liveness || output.liveness.taxonomy_row !== "engine health-check") { + fail(`grade: ${name}`, "missing liveness taxonomy_row engine health-check"); + continue; + } + const assertFn = ASSERTIONS[entry.assert]; + if (!assertFn) { + fail(`grade: ${name}`, `unknown assert ${entry.assert}`); + continue; + } + try { + assertFn(output); + pass(`grade: ${name}`); + } catch (error) { + fail(`grade: ${name}`, String(error.message ?? error)); + } +} + +if (failed === 0) { + process.stdout.write(`\nAll ${cases} checks passed.\n`); + process.exit(0); +} +process.stderr.write(`\n${failed} of ${cases} checks failed.\n`); +process.exit(1); diff --git a/plugins/autonomy/skills/setup/scripts/resolve-prerequisites.fixtures.test.sh b/plugins/autonomy/skills/setup/scripts/resolve-prerequisites.fixtures.test.sh new file mode 100755 index 0000000000..18c3e6561c --- /dev/null +++ b/plugins/autonomy/skills/setup/scripts/resolve-prerequisites.fixtures.test.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +# Discovery wrapper: scripts/run-plugin-tests.sh finds plugins/**/*.test.sh. +set -uo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +if ! command -v node >/dev/null 2>&1; then + echo "SKIP: node not installed" >&2 + exit 0 +fi + +exec node "$SCRIPT_DIR/resolve-prerequisites.fixtures.test.mjs" diff --git a/plugins/autonomy/skills/setup/scripts/resolve-prerequisites.mjs b/plugins/autonomy/skills/setup/scripts/resolve-prerequisites.mjs new file mode 100755 index 0000000000..a0954ce917 --- /dev/null +++ b/plugins/autonomy/skills/setup/scripts/resolve-prerequisites.mjs @@ -0,0 +1,927 @@ +#!/usr/bin/env node + +// Deterministic prerequisite resolver for v1 routine identities on a named +// scheduling surface. Reads the generated identity-prerequisite emission — +// never leaf prose. No agent session. Never persists a verdict; never writes +// config. Wall-clock-free JSON on stdout so consecutive runs are byte-identical. +// +// Liveness (engine health-check taxonomy row): fail-loud on internal failure +// (non-zero exit, never a verdict-shaped fallback). +// +// Usage: +// node resolve-prerequisites.mjs --repo