From ddac56cacbb11b88df3d9c5d982514e6526276f6 Mon Sep 17 00:00:00 2001 From: Nikolay Petrov Date: Fri, 23 Oct 2026 21:31:57 -0700 Subject: [PATCH 1/4] docs(journeys): refresh the catalog for merged and in-flight work Bind merged features to criteria with session evidence (host requirements, repo inspect, repo budget, guidance arms, uptake, skill discovery, built-in review graders, model provenance, CI task selection, context trace, novelty unmeasured share). Add proposed criteria for open PRs with decision_ref, a J2 use case for automatic skill improvement (research), and widen J4 to installed skills and J8 to harnesses. Move the repo inspect CLI tests to their own file so test_repo_environment.py stays under its baseline. Signed-off-by: Nikolay Petrov --- dokimi/docs/user-journeys/README.md | 4 +- dokimi/docs/user-journeys/journeys.md | 63 ++++- dokimi/docs/user-journeys/traceability.md | 2 +- dokimi/scripts/growth_baseline.json | 2 +- dokimi/tests/outcomes/journeys.yaml | 222 +++++++++++++++++- dokimi/tests/test_context_trace.py | 4 + dokimi/tests/test_docker_solver_harness.py | 3 + dokimi/tests/test_novelty_bundle_guards.py | 2 + dokimi/tests/test_novelty_coverage_parity.py | 2 + dokimi/tests/test_repo_arms.py | 5 + dokimi/tests/test_repo_budget.py | 5 + dokimi/tests/test_repo_budget_schema.py | 2 + dokimi/tests/test_repo_builtin_graders.py | 6 + dokimi/tests/test_repo_compare.py | 8 + dokimi/tests/test_repo_environment.py | 44 +--- dokimi/tests/test_repo_eval_task_selection.py | 8 + dokimi/tests/test_repo_host.py | 9 + dokimi/tests/test_repo_inspect_cli.py | 54 +++++ dokimi/tests/test_repo_run.py | 1 + dokimi/tests/test_repo_uptake.py | 3 + dokimi/tests/test_skill_treatment.py | 2 + dokimi/tests/test_task_runs_report.py | 2 + 22 files changed, 401 insertions(+), 52 deletions(-) create mode 100644 dokimi/tests/test_repo_inspect_cli.py diff --git a/dokimi/docs/user-journeys/README.md b/dokimi/docs/user-journeys/README.md index 4475dc98..409ecaea 100644 --- a/dokimi/docs/user-journeys/README.md +++ b/dokimi/docs/user-journeys/README.md @@ -76,11 +76,11 @@ never confused with a use-case or job id. | [J1](journeys.md#j1--start-an-assessment) | Check this and explain the problems and the next step | Initial evidence, unknowns, scope, and a fitting next evaluation plan | | [J2](journeys.md#j2--improve-my-skill) | Improve my skill | Actionable hypotheses and changes, and a measured effect or a justified rejection of the change | | [J3](journeys.md#j3--enable-my-repository-for-agents) | Enable my repository for agents | Obstacles found, guidance and evals prepared, representative real tasks verified | -| [J4](journeys.md#j4--check-agentsmd-or-other-instructions) | Check AGENTS.md or other instructions | Correctness, loading and reach, adherence, and effect, each established on its own | +| [J4](journeys.md#j4--check-agentsmd-other-instructions-or-installed-skills) | Check AGENTS.md, other instructions or installed skills | Correctness, loading and reach, adherence, and effect, each established on its own | | [J5](journeys.md#j5--assess-a-third-party-skill) | Assess a third-party skill | Evidence for the claimed capabilities, problems, applicability, and the limits of the assessment | | [J6](journeys.md#j6--does-this-skill-work-in-my-use-case) | Does this skill work in my use case? | Evals adapted or added for the user's own tasks, compared numerically | | [J7](journeys.md#j7--create-or-improve-evals) | Create or improve evals | Relevant tasks, defensible graders, negative controls, explicit coverage gaps | -| [J8](journeys.md#j8--which-models-can-handle-this) | Which models can handle this task or repository? | Feasibility and reliability under stated requirements and conditions | +| [J8](journeys.md#j8--which-models-or-harnesses-can-handle-this) | Which models or harnesses can handle this task or repository? | Feasibility and reliability under stated requirements and conditions | | [J9](journeys.md#j9--cheapest-suitable-model-or-setup) | What is the cheapest suitable model or setup? | The cost of reaching acceptable outcomes, retries and failures included, under quality constraints | | [J10](journeys.md#j10--what-effect-does-this-change-have) | What effect does this change have? | Comparable effects, regressions, costs, uncertainty and confounds for any declared change | | [J11](journeys.md#j11--explain-results-or-recover-a-run) | Explain results, or recover from a failed or inconclusive run | Task failure, evaluation failure, missing evidence and incomparable data told apart; a recovery or the next discriminating experiment | diff --git a/dokimi/docs/user-journeys/journeys.md b/dokimi/docs/user-journeys/journeys.md index 059976c9..cdcc4682 100644 --- a/dokimi/docs/user-journeys/journeys.md +++ b/dokimi/docs/user-journeys/journeys.md @@ -166,6 +166,16 @@ records it as a gap. - **AC-J02-005** The edited and the original skill run on the same task set, model and repetitions, and the report gives the effect with its uncertainty. *Proposed as one step; today two separate runs.* +#### UC-J02-003 Improve a skill automatically + +A loop proposes edits to the skill, measures each on the skill's tasks and probes, and keeps what the measured reward supports: a reinforcement-style search over the skill text, not over model weights. *Research.* + +- **AC-J02-006** The proposer and the trace analyst see only the development split; the confirmation split is never shown to them, and the report states both splits. *Proposed.* +- **AC-J02-007** An accepted edit is confirmed on the confirmation split with fresh trials, and its gain is reported with its interval; a gain inside the noise band is reported as no improvement. *Proposed.* +- **AC-J02-008** Before the loop scores candidates, each verifier is run against gaming answers; a verifier that passes one blocks the loop and is reported. *Proposed.* +- **AC-J02-009** An accepted edit that copies evaluation answers into the skill, or drops a must-hold constraint of the skill's contract, is blocked and named. *Proposed.* +- **AC-J02-010** The loop stops at its spend cap and returns each kept edit as a diff with its measurement, for the author to accept; it never replaces the skill on its own. *Proposed.* + --- ## J3 — Enable my repository for agents @@ -226,6 +236,8 @@ working. #### UC-J03-001 Find what stops agents in this repository - **AC-J03-001** A static scan of a repository at a commit reports each guidance file's failing checks and writes a versioned readiness artifact; with a fail gate requested, a failing check exits non-zero. +- **AC-J03-007** A static inspection reports, per project root, the instruction files with their scope and activation, the build, test, lint and type checks with how each is known, and launch risks such as hooks, without running anything from the repository; it writes a versioned environment artifact, and with an error gate a blocking finding exits non-zero. +- **AC-J03-008** A context budget lists, per file, what the repository puts into an agent's context at start, lazily, on demand or not at all under a named harness profile, writes a versioned budget artifact, and with a warning gate a warning exits non-zero. #### UC-J03-002 Prepare the evaluation tree @@ -236,10 +248,11 @@ working. - **AC-J03-003** A repeated run skips cells that already have a result, unless forced, so an interrupted batch resumes without paying twice. - **AC-J03-004** A task that needs network egress is refused before any cell runs, and no cell is recorded for it. - **AC-J03-005** A batch report lists the cells that are missing rather than averaging over what finished. *Proposed as an enforced criterion; the behaviour exists.* +- **AC-J03-006** A task whose host requirements this host does not meet is not run: its cells are marked unsupported with the reason, the report lists them as unsupported rather than failed or missing, and a host that meets them runs those cells later. --- -## J4 — Check AGENTS.md or other instructions +## J4 — Check AGENTS.md, other instructions or installed skills **Actor and intent.** A maintainer or skill author, or their agent. "Is my AGENTS.md any good?" "Do agents even read it?" Trigger: new or edited guidance, @@ -317,6 +330,15 @@ so rather than implying the fixed, reached file helps. #### UC-J04-004 Check the instructions improve task outcomes - **AC-J04-006** With and without the guidance, the same tasks run on the same model and repetitions, and the comparison reports the effect per task with missing cells listed. +- **AC-J04-007** A guidance arm taken from another revision is pinned to the full commit once per batch, so a moved branch starts a new arm and never adds cells to the old one. +- **AC-J04-008** A patch arm is recorded by path and digest, and a patch that does not apply fails that cell's preparation. +- **AC-J04-009** For an arm that adds files, tools or servers, each cell records whether the agent used them; a comparison warns when the treatment used none, and a cell that left no trace is unmeasured, not a non-use. + +#### UC-J04-005 Check an installed skill reaches the agent + +- **AC-J04-010** A skill installed with no treatment notice runs beside the same skill announced, on the same tasks, model and repetitions, and the report gives both pass rates, their gap and both skill-invocation rates; the gap is withheld when the two arms attempted or lost different cells. +- **AC-J04-011** Each skill cell records whether the prompt announced the skill and how much of its description the agent's skill listing shows. *Partial: the warning for a cut description is not asserted by a test.* +- **AC-J04-012** A zero skill-invocation rate is reported as "the skill tool was not invoked", never as "the skill was not read". *Manual.* --- @@ -350,6 +372,7 @@ novelty exist. A per-claim assessment report is **proposed**. - **AC-J05-001** The assessment lists each claim with its evidence or with "not verified", and does not certify general usefulness from structure checks. - **AC-J05-002** The assessment states the models, tasks and probe sets it covered and what it did not cover. *Manual.* +- **AC-J05-003** A catalog scan reports each skill's Agent Skills specification violations as findings, and fails on them only in strict mode. *Proposed.* --- @@ -383,6 +406,7 @@ uncertainty. - **AC-J06-001** The comparison runs on tasks the user supplied or approved, and the report names their source. - **AC-J06-002** The report gives the effect per task and overall, with uncertainty, against the user's current configuration. +- **AC-J06-003** A skill or a whole catalog can be installed as an arm on the user's repository tasks, and each cell records which skills were used and whether the expected one was selected. *Proposed.* --- @@ -456,6 +480,7 @@ down in the PR. - **AC-J07-002** The self-check fails a task whose reference solution does not pass its grader, and passes a task where null fails and oracle passes. - **AC-J07-003** The self-check flags an image that exposes the task tree to the agent, and reports an egress task as unsupported rather than passed. - **AC-J07-004** A passing self-check is not reported as evidence that the tasks are relevant or cover the subject. *Manual.* +- **AC-J07-008** A review task can be graded by a built-in grader (seeded problems with decoys, or a replay of past review threads) with no grader in the repository; the self-check runs it on null and oracle, an empty or unparseable review scores zero recall rather than unmeasured, and an unknown built-in name is refused. #### UC-J07-002 Check an existing judge @@ -471,7 +496,7 @@ down in the PR. --- -## J8 — Which models can handle this? +## J8 — Which models or harnesses can handle this? **Actor and intent.** A maintainer or team choosing a model for a task type or a repository. "Which models can do our work?" Trigger: a new model, or a @@ -498,6 +523,17 @@ threshold-based feasibility verdict is **proposed**. - **AC-J08-001** Runs on different models are compared only on the same question or task set; a mismatch is reported. - **AC-J08-002** Each model's result states its repetitions and interval, and a model whose cells are missing is not ranked as failing. +- **AC-J08-003** A cell answered by a model other than the one requested records the model that answered, and report and compare warn; provider-prefix spellings of one model are the same model. + +#### UC-J08-002 Compare harnesses on one task set + +- **AC-J08-004** The same tasks run under different harness profiles (Claude Code, Codex CLI, Copilot CLI) on the same arms, model and repetitions, each cell records the harness, its version and its execution settings, and harnesses are compared only on the same task set. *Proposed.* +- **AC-J08-005** A run with subagent delegation is labelled as such and never compared as a single-agent baseline against a run without it. *Proposed.* + +#### UC-J08-003 Compare code-review agents + +- **AC-J08-006** A review task graded by seeded problems or a replayed review reports recall, false positives on decoys and severe findings per harness and model, and a review with no parseable findings counts as a failure, not dropped. *Proposed.* +- **AC-J08-007** A harness's native review mode and the same harness given a review prompt run as separate arms on the same review tasks, and the report names which arm used the native mode. *Proposed.* --- @@ -603,6 +639,20 @@ cost of both runs. - **AC-J10-008** An experiment declares what differs between its variants, records the resolved settings of each, and reports differences it did not declare as confounds. *Proposed.* +#### UC-J10-004 Know whether a result is stable + +- **AC-J10-009** Repeated runs of one task report pass@k, pass^k and path stability separately, so a stable success is told apart from an unstable one with the same success rate. *Proposed.* + +#### UC-J10-005 See how the agent worked, not only whether it passed + +- **AC-J10-010** Each repository cell records tool calls, failed and denied commands and guidance files read, counted over the agent and its subagents. *Proposed.* +- **AC-J10-011** Report and compare show the process counts per arm as descriptive measures, outside the corrected significance family. *Proposed.* + +#### UC-J10-006 Measure a change across a multi-turn session + +- **AC-J10-012** A session run asks the question and its follow-ups with the history kept, records usage and cost per turn, and reports the session total with the reason the session stopped. *Proposed.* +- **AC-J10-013** A reminder variant differs from its arm only by the reminder added to each user turn, and the comparison reports the per-turn effect, not only the final answer. *Proposed.* + --- ## J11 — Explain results or recover a run @@ -633,10 +683,12 @@ explain-this-run command is **proposed**. - **AC-J11-001** A grader crash is recorded as an evaluation failure and counted as a failure, not dropped from the denominator. - **AC-J11-002** A sample cap reached without a decision is reported as inconclusive, never as pass. +- **AC-J11-005** A check or run with no task, or where every cell is unsupported or errored, exits non-zero as incomplete with its counts, never as a pass. *Proposed.* #### UC-J11-002 Resume a stopped run - **AC-J11-003** Resuming runs only the missing cells and keeps the finished ones. +- **AC-J11-004** A resumed run whose stored cell was produced under different execution settings refuses to reuse it unless forced. *Proposed.* --- @@ -672,6 +724,8 @@ across check kinds (AC-J12-002). - **AC-J12-001** A strict baseline comparison exits non-zero on a regression beyond the threshold. - **AC-J12-002** An inconclusive or budget-stopped check is reported as such, and whether it blocks is the configured policy's decision. +- **AC-J12-003** The reusable CI check runs only the tasks a change touches, adds a smoke set when shared harness files change, and fails on an unknown task name instead of checking nothing. +- **AC-J12-004** A gate on a run whose result lacks planned cells reports it incomplete and names the missing cells; it never passes on the cells that finished. *Proposed.* --- @@ -756,6 +810,9 @@ task outcomes, and differences inside the judge band are not quoted. - **AC-J14-002** The estimate prices the bundle the run will actually send, and points at the spend cap. - **AC-J14-003** The documented authoring path (routing, estimate, extraction, probing, shortening, view) runs end to end on stub models through the CLI and writes its intermediate artifacts. - **AC-J14-004** Probing an inventory against an artifact that changed since extraction is refused. +- **AC-J14-008** A novelty record states the share of the skill's readable words it did not measure and the basis of that share; the gate judges a stored record on its own stamped basis, and a record with an unknown basis is reported unmeasured. +- **AC-J14-011** The novelty report shows a conflict count the gate did not clear as not measured, never as a clean 0. *Proposed.* +- **AC-J14-012** Analyze states on its output the share of readable words it measured, and warns when it is half or less. *Proposed.* #### UC-J14-002 Inspect a run's context @@ -764,6 +821,8 @@ task outcomes, and differences inside the judge band are not quoted. #### UC-J14-003 Probe reach, routing or a judge on its own - **AC-J14-006** Each of reach, routing and judge validity can be run alone with explicit inputs and writes structured output; reach is traced under AC-J04-003. +- **AC-J14-009** A routing run that calls a model records its calls, tokens and dollars, or no dollar figure when a call has no known price. *Proposed.* +- **AC-J14-010** Routing builds its candidate field the way the harness does (catalog manifest, hidden skills left out, each mirror once), and with repeats reports agreement per query as its noise floor. *Proposed.* #### UC-J14-004 Re-render saved evidence diff --git a/dokimi/docs/user-journeys/traceability.md b/dokimi/docs/user-journeys/traceability.md index 731d0502..8b73e047 100644 --- a/dokimi/docs/user-journeys/traceability.md +++ b/dokimi/docs/user-journeys/traceability.md @@ -94,7 +94,7 @@ shows the measurement and its artifact, not how any real harness behaves. Some claims sound alike and are not: - Instructions: correctness, reach, adherence and task effect are separate - criteria ([J4](journeys.md#j4--check-agentsmd-or-other-instructions)). + criteria ([J4](journeys.md#j4--check-agentsmd-other-instructions-or-installed-skills)). - Third-party skills: a structure check cannot certify general usefulness. - Context: telemetry about context size cannot certify downstream behaviour. - Evals: schema validity, grader correctness, task relevance and coverage are diff --git a/dokimi/scripts/growth_baseline.json b/dokimi/scripts/growth_baseline.json index 13f91674..bce94598 100644 --- a/dokimi/scripts/growth_baseline.json +++ b/dokimi/scripts/growth_baseline.json @@ -11,7 +11,7 @@ "tests/test_judge_reliability.py": {"lines": 1695, "rationale": "grew on main in #73 (judge-validity --context stored), 1505 -> 1695, before the ratchet landed; split it in its own PR"}, "tests/test_judge_validity.py": {"lines": 2235, "rationale": "grew on main in #73 (judge-validity --context stored), 1423 -> 2235, before the ratchet landed; split it in its own PR"}, "tests/test_pipeline_question_set.py": {"lines": 1223}, - "tests/test_repo_environment.py": {"lines": 1337, "rationale": "grew on main in #147 before the ratchet landed; +2 for the instructions assert of two crash tests (#103 review); split it in its own PR"}, + "tests/test_repo_environment.py": {"lines": 1305, "rationale": "grew on main in #147 before the ratchet landed; +2 for the instructions assert of two crash tests (#103 review); split it in its own PR"}, "tests/test_subjects.py": {"lines": 1064}, "tests/test_task_formats.py": {"lines": 1003} }, diff --git a/dokimi/tests/outcomes/journeys.yaml b/dokimi/tests/outcomes/journeys.yaml index 2be18e7f..c89424a6 100644 --- a/dokimi/tests/outcomes/journeys.yaml +++ b/dokimi/tests/outcomes/journeys.yaml @@ -76,10 +76,31 @@ criteria: evidence: - {id: cjm-arm-digest-refused, kind: unit, stubs: []} AC-J10-008: - delivery: proposed - gaps: ["no experiment declaration with variants beyond arms and models; confounds are only the checks above"] + delivery: partial + enforce: false + capability: experiment-declaration + evidence: + - {id: cjm-arm-stamp-warn, kind: unit, stubs: []} + gaps: + - repository arms only; no declaration of a variant outside repo arms + - undeclared differences are free-text warnings, not structured confounds # ---------------------------------------------------------------- J4 + AC-J10-009: + delivery: proposed + decision_ref: https://github.com/intel/dokimi/pull/155 + AC-J10-010: + delivery: proposed + decision_ref: https://github.com/intel/dokimi/pull/157 + AC-J10-011: + delivery: proposed + decision_ref: https://github.com/intel/dokimi/pull/177 + AC-J10-012: + delivery: proposed + decision_ref: https://github.com/intel/dokimi/pull/174 + AC-J10-013: + delivery: proposed + decision_ref: https://github.com/intel/dokimi/pull/174 AC-J04-001: delivery: available enforce: true @@ -114,13 +135,58 @@ criteria: - a failed harness is recorded as error; a refusal is not - "a token-free refusal is classified not_loaded (repos/reach.py classify), the same as a file that was not reached; the refusal test asserts only the prose warning" AC-J04-006: - delivery: partial - enforce: false + delivery: available + enforce: true capability: guidance-task-impact surfaces: ["repo run --arm", "repo compare --baseline"] + evidence: + - id: cjm-repo-compare-arms + kind: cli_scenario + invokes: ["repo compare --baseline --arm", "repo compare --json", "repo run --arm"] + stubs: [Docker (FakeRunner), agent (fake harness executable)] gaps: - no test or recorded run asserts an effect claim of guidance on tasks - - needs Docker, a harness and paid agent runs; not run to fill this row + - the arms test calls compare() in-process after repo run, with one repetition + AC-J04-007: + delivery: available + enforce: true + capability: guidance-arms + surfaces: ["repo run --arm"] + evidence: + - {id: cjm-guidance-rev-arm, kind: integration, stubs: [Docker (FakeRunner), agent (fake harness executable)]} + AC-J04-008: + delivery: available + enforce: true + capability: guidance-arms + evidence: + - {id: cjm-patch-arm, kind: integration, stubs: [Docker (FakeRunner), agent (fake harness executable)]} + AC-J04-009: + delivery: available + enforce: true + capability: arm-uptake + evidence: + - id: cjm-arm-uptake + kind: cli_scenario + invokes: ["repo run --arm"] + stubs: [Docker (FakeRunner), agent (fake harness executable)] + AC-J04-010: + delivery: available + enforce: true + capability: skill-discovery + evidence: + - {id: cjm-skill-discover, kind: unit, stubs: [harness runs (fixed cell records)]} + gaps: ["no live run states a discovery gap for a real skill"] + AC-J04-011: + delivery: partial + enforce: false + capability: skill-discovery + evidence: + - {id: cjm-skill-listing, kind: unit, stubs: []} + gaps: ["the run.log warning for a cut description is not asserted"] + AC-J04-012: + delivery: partial + enforce: false + gaps: ["manual: wording of the report, not checked by a test"] AC-J04-005: delivery: proposed gaps: ["no adherence measurement exists"] @@ -156,6 +222,33 @@ criteria: invokes: ["repo run --task --arm"] stubs: [Docker (FakeRunner), agent (fake harness executable)] gaps: ["egress tasks cannot run at all until the egress proxy exists"] + AC-J03-006: + delivery: available + enforce: true + capability: task-host-requirements + evidence: + - id: cjm-repo-host-unsupported + kind: cli_scenario + invokes: ["repo run --task"] + stubs: [Docker (FakeRunner with a host probe), agent (fake harness executable)] + AC-J03-007: + delivery: available + enforce: true + capability: repo-inspect + evidence: + - id: cjm-repo-inspect + kind: cli_scenario + invokes: ["repo inspect --out", "repo inspect --json"] + stubs: [] + AC-J03-008: + delivery: available + enforce: true + capability: repo-budget + evidence: + - id: cjm-repo-budget + kind: cli_scenario + invokes: ["repo budget --fail-on-warning", "repo budget --out"] + stubs: [] AC-J03-002: delivery: available enforce: false @@ -193,8 +286,21 @@ criteria: kind: cli_scenario invokes: ["repo tasks check --task"] stubs: [Docker (FakeRunner, with a simulated leaky image)] + - id: cjm-tasks-check-host + kind: cli_scenario + invokes: ["repo tasks check --warm", "repo tasks check --allow-unsupported"] + stubs: [Docker (host probe fake)] gaps: ["isolation is checked by name and mount, against a simulated image, not a real container escape", "the probe's find root (/), its /proc /sys /dev prune and the source mount are not exercised: the shim searches a small tree"] + AC-J07-008: + delivery: available + enforce: true + capability: builtin-review-graders + evidence: + - id: cjm-builtin-review-graders + kind: cli_scenario + invokes: ["repo tasks check --task", "repo validate --json"] + stubs: [Docker (FakeRunner)] AC-J07-005: delivery: partial enforce: false @@ -239,6 +345,12 @@ criteria: stubs: [Docker (FakeRunner), agent (fake harness executable)] # ---------------------------------------------------------------- J14 / J2 (novelty) + AC-J11-004: + delivery: proposed + decision_ref: https://github.com/intel/dokimi/pull/107 + AC-J11-005: + delivery: proposed + decision_ref: https://github.com/intel/dokimi/pull/176 AC-J14-001: delivery: available enforce: true @@ -283,11 +395,34 @@ criteria: stubs: [every model client (scripted transport)] AC-J14-005: delivery: available - enforce: false + enforce: true capability: context-trace surfaces: ["context trace --json"] refs: [docs/context-window.md] - gaps: ["not yet bound to a test id"] + evidence: + - id: cjm-context-trace + kind: cli_scenario + invokes: ["context trace --json"] + stubs: [] + gaps: ["no test asserts that no model is called; trace.py imports no client"] + AC-J14-008: + delivery: available + enforce: true + capability: novelty-gate + evidence: + - {id: cjm-novelty-unmeasured-share, kind: unit, stubs: []} + AC-J14-009: + delivery: proposed + decision_ref: https://github.com/intel/dokimi/pull/164 + AC-J14-010: + delivery: proposed + decision_ref: https://github.com/intel/dokimi/pull/172 + AC-J14-011: + delivery: proposed + decision_ref: https://github.com/intel/dokimi/pull/197 + AC-J14-012: + delivery: proposed + decision_ref: https://github.com/intel/dokimi/pull/173 AC-J14-007: delivery: partial enforce: false @@ -321,6 +456,21 @@ criteria: gaps: ["before/after task runs exist separately (arms run, tasks run) but are not one step"] # ---------------------------------------------------------------- J15 + AC-J02-006: + delivery: proposed + gaps: ["research: no CLI surface yet"] + AC-J02-007: + delivery: proposed + gaps: ["research: no CLI surface yet"] + AC-J02-008: + delivery: proposed + gaps: ["research: no CLI surface yet"] + AC-J02-009: + delivery: proposed + gaps: ["research: no CLI surface yet"] + AC-J02-010: + delivery: proposed + gaps: ["research: no CLI surface yet"] AC-J15-001: delivery: proposed gaps: ["no command drafts tasks from a chat transcript"] @@ -353,7 +503,63 @@ criteria: # ---------------------------------------------------------------- J12 AC-J12-001: delivery: proposed + # baseline compare --strict exits 1 only on an unverified or mismatched model; compare --spec finds CRITICAL and exits 0. gaps: ["baseline compare --strict adds a non-zero exit only on an unverified or mismatched model; a regression beyond a threshold exits 0 and no threshold is defined"] AC-J12-002: + delivery: partial + enforce: true + capability: ci-gate-policy + evidence: + - id: cjm-tasks-check-host + kind: cli_scenario + invokes: ["repo tasks check --allow-unsupported"] + stubs: [Docker (host probe fake)] + - {id: cjm-repo-eval-workflow, kind: unit, stubs: [dokimi (stub executable)]} + gaps: + - the policy is one flag for host-unsupported tasks; budget-stopped and inconclusive judge results have none + AC-J12-003: + delivery: available + enforce: true + capability: ci-task-selection + evidence: + - {id: cjm-repo-eval-selection, kind: unit, stubs: [git (temporary repository)]} + AC-J12-004: + delivery: proposed + decision_ref: https://github.com/intel/dokimi/pull/161 + + # ---------------------------------------------------------------- J5, J6, J8 + AC-J05-003: + delivery: proposed + decision_ref: https://github.com/intel/dokimi/pull/163 + AC-J06-003: + delivery: proposed + decision_ref: https://github.com/intel/dokimi/pull/137 + AC-J08-001: + delivery: partial + enforce: false + gaps: ["report model-compare keeps common questions and warns on a hash mismatch; repo compare compares arms within a model, not models"] + AC-J08-002: + delivery: partial + enforce: false + gaps: ["missing cells are counted and left out; no per-model interval or repetition count is stated in a ranking"] + AC-J08-003: + delivery: available + enforce: true + capability: model-provenance + evidence: + - id: cjm-model-answered + kind: cli_scenario + invokes: ["repo run --model"] + stubs: [Docker (FakeRunner), agent (fake harness executable)] + AC-J08-004: + delivery: proposed + decision_ref: https://github.com/intel/dokimi/pull/116 + AC-J08-005: + delivery: proposed + decision_ref: https://github.com/intel/dokimi/pull/107 + AC-J08-006: + delivery: proposed + decision_ref: https://github.com/intel/dokimi/pull/116 + AC-J08-007: delivery: proposed - gaps: ["no configurable policy decides whether an inconclusive or budget-stopped check blocks"] + decision_ref: https://github.com/intel/dokimi/pull/116 diff --git a/dokimi/tests/test_context_trace.py b/dokimi/tests/test_context_trace.py index 19792263..5620a1fd 100644 --- a/dokimi/tests/test_context_trace.py +++ b/dokimi/tests/test_context_trace.py @@ -65,6 +65,7 @@ def _jsonl(*records): ) +@pytest.mark.outcome("cjm-context-trace") def test_counts_one_call_per_message_id_and_sums_the_three_input_parts(): trace = parse_trace(RUN) main = trace.main_calls() @@ -133,6 +134,7 @@ def _run_cli(argv, capsys): return capsys.readouterr() +@pytest.mark.outcome("cjm-context-trace") def test_cli_table_compares_traces(tmp_path, capsys): a, b = tmp_path / "a.jsonl", tmp_path / "b.jsonl" a.write_text(RUN, encoding="utf-8") @@ -146,6 +148,7 @@ def test_cli_table_compares_traces(tmp_path, capsys): assert f"{a}: largest tool results (chars): Bash 650" in out +@pytest.mark.outcome("cjm-context-trace") def test_cli_json_single_trace(tmp_path, capsys): a = tmp_path / "a.jsonl" a.write_text("garbage\n" + RUN, encoding="utf-8") @@ -156,6 +159,7 @@ def test_cli_json_single_trace(tmp_path, capsys): assert "line 1: not JSON" in got.err +@pytest.mark.outcome("cjm-context-trace") def test_cli_missing_trace_exits_1(tmp_path, capsys): with pytest.raises(SystemExit) as exc: _run_cli(["context", "trace", str(tmp_path / "missing.jsonl")], capsys) diff --git a/dokimi/tests/test_docker_solver_harness.py b/dokimi/tests/test_docker_solver_harness.py index a656a91a..13c10553 100644 --- a/dokimi/tests/test_docker_solver_harness.py +++ b/dokimi/tests/test_docker_solver_harness.py @@ -265,6 +265,7 @@ def test_registry_builds_docker_harnesses(tmp_path): assert "docker-claude" in known_harnesses() +@pytest.mark.outcome("cjm-skill-discover") @pytest.mark.parametrize("prefix", ["docker-claude", "docker-claude-host"]) def test_registry_builds_the_discover_arm_beside_the_skill_arm(tmp_path, prefix): skill_md = tmp_path / "SKILL.md" @@ -293,6 +294,7 @@ def _arm_row(task, harness, passed, *, invoked=None, error=None, repeat=1): "reward": 1.0 if passed else 0.0, "repeat": repeat, "metrics": metrics} +@pytest.mark.outcome("cjm-skill-discover") def test_summary_reports_the_discovery_gap_per_pair(): base, skill, disc = "docker-claude", "docker-claude-skill:s", "docker-claude-skill-discover:s" rows = [] @@ -317,6 +319,7 @@ def test_summary_reports_the_discovery_gap_per_pair(): @pytest.mark.parametrize("skill, disc", [("s/SKILL.md", "s"), ("s", "./s/"), ("s", "{cwd}/s"), ("s/", "s/SKILL.md")]) +@pytest.mark.outcome("cjm-skill-discover") def test_discovery_gap_pairs_spellings_of_one_skill(tmp_path, monkeypatch, skill, disc): monkeypatch.chdir(tmp_path) skill = f"docker-claude-skill:{skill}" diff --git a/dokimi/tests/test_novelty_bundle_guards.py b/dokimi/tests/test_novelty_bundle_guards.py index 939cdb37..a5cd7870 100644 --- a/dokimi/tests/test_novelty_bundle_guards.py +++ b/dokimi/tests/test_novelty_bundle_guards.py @@ -239,6 +239,7 @@ def test_an_entry_point_record_without_a_stored_share_reports_it_from_the_counts } +@pytest.mark.outcome("cjm-novelty-unmeasured-share") @pytest.mark.parametrize("basis", list(POLICY_ROWS_BY_BASIS)) def test_absent_policy_words_are_summed_from_policy_rows_only(basis): record = {"include_resources": False, "entry_point_words": 100, @@ -294,6 +295,7 @@ def test_a_stored_bool_is_not_a_count_and_the_rows_are_recounted(stored): assert _coverage_check(record).value == 0.4945 +@pytest.mark.outcome("cjm-novelty-unmeasured-share") @pytest.mark.parametrize("junk", ["", "Readable", 5, True, ["readable"]]) def test_a_stamp_that_is_not_a_known_basis_is_not_read_as_the_legacy_one(junk): """Only an absent stamp means "written before it existed"; anything else is unknown.""" diff --git a/dokimi/tests/test_novelty_coverage_parity.py b/dokimi/tests/test_novelty_coverage_parity.py index 2b6215b3..7af76980 100644 --- a/dokimi/tests/test_novelty_coverage_parity.py +++ b/dokimi/tests/test_novelty_coverage_parity.py @@ -179,6 +179,7 @@ def _capture(name: str, tmp_path: Path, *, stamped: bool = False) -> dict: } +@pytest.mark.outcome("cjm-novelty-unmeasured-share") @pytest.mark.parametrize("name", [*LIVE, *STORED]) def test_an_unstamped_record_reads_exactly_as_on_main(name, tmp_path): golden = json.loads(GOLDEN.read_text(encoding="utf-8")) @@ -203,6 +204,7 @@ def test_an_unstamped_record_reads_exactly_as_on_main(name, tmp_path): } +@pytest.mark.outcome("cjm-novelty-unmeasured-share") @pytest.mark.parametrize("name", list(LIVE)) def test_a_stamped_record_is_gated_on_readable_words(name, tmp_path): golden = json.loads(GOLDEN.read_text(encoding="utf-8"))[name] diff --git a/dokimi/tests/test_repo_arms.py b/dokimi/tests/test_repo_arms.py index 0aa73872..c155b529 100644 --- a/dokimi/tests/test_repo_arms.py +++ b/dokimi/tests/test_repo_arms.py @@ -143,6 +143,7 @@ def test_rev_arm_copies_no_object_of_the_revision_but_the_guidance_blobs_it_uses assert b"# sub guidance" not in blobs and b"# guidance\n" not in blobs # the base's replaced guidance +@pytest.mark.outcome("cjm-guidance-rev-arm") def test_rev_arm_is_named_by_the_sha_it_resolved_to_once(toy): by_ref = pin_arm(parse_arm("guidance:rev:pr"), toy["git_dir"]) assert by_ref == pin_arm(parse_arm(f"guidance:rev:{toy['pr']}"), toy["git_dir"]) == f"guidance:rev:{toy['pr']}" @@ -162,6 +163,7 @@ def test_a_bad_arm_is_a_prep_error(toy, spec): pin_arm(parse_arm(spec), toy["git_dir"]) +@pytest.mark.outcome("cjm-patch-arm") def test_patch_arm_applies_on_raw_guidance_and_records_the_file(toy): patch = toy["tmp"] / "patches" / "tooling.diff" write(patch.parent, patch.name, TOOLING_PATCH) @@ -179,6 +181,7 @@ def test_patch_arm_applies_on_raw_guidance_and_records_the_file(toy): assert git(ws, "fsck", "--unreachable", "--no-reflogs") == "" +@pytest.mark.outcome("cjm-patch-arm") @pytest.mark.parametrize("text, message", [(STALE_PATCH, "does not apply"), (INTO_EVAL_DIR_PATCH, "eval_dir")]) def test_a_patch_arm_that_cannot_be_applied_is_a_prep_error_for_the_cell(toy, text, message): patch = toy["tmp"] / "bad.diff" @@ -231,6 +234,8 @@ def _run(s, *arms): args.func(args) +@pytest.mark.outcome("cjm-guidance-rev-arm") +@pytest.mark.outcome("cjm-repo-compare-arms") def test_repo_run_names_cells_by_the_resolved_arm_and_compare_reads_them(setup, capsys): patch = setup["tmp"] / "tooling.diff" patch.write_text(TOOLING_PATCH) diff --git a/dokimi/tests/test_repo_budget.py b/dokimi/tests/test_repo_budget.py index 5962f1ef..ec4b5c22 100644 --- a/dokimi/tests/test_repo_budget.py +++ b/dokimi/tests/test_repo_budget.py @@ -56,6 +56,7 @@ def _by_path(payload): return {(e["path"], e["kind"]): e for e in payload["entries"]} +@pytest.mark.outcome("cjm-repo-budget") def test_root_guidance_starts_nested_is_lazy(tmp_path): tree = _tree(tmp_path, {"AGENTS.md": "a" * 100, "src/AGENTS.md": "b" * 40, "README.md": "x"}) got = budget(tree, NAMES) @@ -205,6 +206,7 @@ def test_a_link_to_a_loaded_file_is_not_counted_again(tmp_path): assert got["totals"]["off"] == 0 and got["warnings"] == [] +@pytest.mark.outcome("cjm-repo-budget") def test_skill_listing_starts_body_on_demand(tmp_path): tree = _tree(tmp_path, {".claude/skills/fmt/SKILL.md": _skill("fmt", "Format code.", "x" * 50)}) got = budget(tree, NAMES) @@ -285,6 +287,7 @@ def test_identical_two_word_descriptions_are_duplicates(tmp_path): assert budget(tree, NAMES)["duplicate_skills"] == [{"a": "one", "b": "two", "overlap": 1.0}] +@pytest.mark.outcome("cjm-repo-budget") def test_gate_passes_a_clean_tree(tmp_path, capsys): _tree(tmp_path, {"CLAUDE.md": "c" * 20, ".claude/skills/a/SKILL.md": _skill("a", "Format Python code.")}) out = _run(["repo", "budget", str(tmp_path), "--fail-on-warning"], capsys).out # no SystemExit @@ -298,6 +301,7 @@ def _run(argv, capsys): return capsys.readouterr() +@pytest.mark.outcome("cjm-repo-budget") def test_cli_table_and_gate(tmp_path, capsys): _tree(tmp_path, {"CLAUDE.md": "c" * 20, "AGENTS.md": "a" * 10, ".mcp.json": '{"mcpServers": {"docs": {}}}'}) with pytest.raises(SystemExit) as exc: @@ -320,6 +324,7 @@ def test_cli_json_and_bad_source(tmp_path, capsys): assert exc.value.code == 1 +@pytest.mark.outcome("cjm-repo-budget") def test_cli_out_writes_the_inventory(tmp_path, capsys): _tree(tmp_path, {"AGENTS.md": "a"}) dest = tmp_path / "budget.json" diff --git a/dokimi/tests/test_repo_budget_schema.py b/dokimi/tests/test_repo_budget_schema.py index 3208c9b5..2108ff39 100644 --- a/dokimi/tests/test_repo_budget_schema.py +++ b/dokimi/tests/test_repo_budget_schema.py @@ -40,6 +40,7 @@ def _budget_json(root, capsys, *extra): return json.loads(capsys.readouterr().out) +@pytest.mark.outcome("cjm-repo-budget") def test_json_is_a_stamped_repo_budget_artifact(tmp_path, capsys): _populated_tree(tmp_path) data = _budget_json(tmp_path, capsys) @@ -69,6 +70,7 @@ def test_nothing_to_budget_is_still_valid(tmp_path, capsys): @pytest.mark.parametrize("field", ["schema_version", "tree", "entries", "totals", "skills", "duplicate_skills", "mcp_servers", "warnings"]) +@pytest.mark.outcome("cjm-repo-budget") def test_a_payload_missing_a_field_is_rejected(tmp_path, capsys, field): _populated_tree(tmp_path) data = _budget_json(tmp_path, capsys) diff --git a/dokimi/tests/test_repo_builtin_graders.py b/dokimi/tests/test_repo_builtin_graders.py index c2f3e7a3..ac297edb 100644 --- a/dokimi/tests/test_repo_builtin_graders.py +++ b/dokimi/tests/test_repo_builtin_graders.py @@ -175,6 +175,7 @@ def test_an_infinite_line_is_unreadable_not_a_grader_crash(tmp_path, line): assert review.grade_replay({}, replay, text)["threads_hit"] == [] +@pytest.mark.outcome("cjm-builtin-review-graders") def test_seeds_parse_error_and_clean_control(tmp_path): seeds, clean = _task_dir(tmp_path, "seeds_mul"), _task_dir(tmp_path, "clean_mul") unparsed = {"pass": False, "parse_error": True, "recall": 0.0} @@ -202,6 +203,7 @@ def test_replay_is_one_to_one_nearest_first_within_the_window(tmp_path): "recall_addressed": 0.0} +@pytest.mark.outcome("cjm-builtin-review-graders") def test_matching_rules_as_the_contract_states_them(tmp_path): """§4.5: same file only, kw case-insensitive, replay within ``window`` (default 5) inclusive.""" seeds, replay = _task_dir(tmp_path, "seeds_mul"), _task_dir(tmp_path, "replay_mul") @@ -221,6 +223,7 @@ def test_matching_rules_as_the_contract_states_them(tmp_path): assert review.last_json(review.oracle_replay({}, tmp_path / "c"))[0]["message"] == "x" * 200 +@pytest.mark.outcome("cjm-builtin-review-graders") def test_oracles_reach_full_recall(tmp_path): for task_id, grade, oracle in (("seeds_mul", review.grade_seeds, review.oracle_seeds), ("replay_mul", review.grade_replay, review.oracle_replay)): @@ -322,6 +325,7 @@ def _check(root, out, runner, monkeypatch, *tasks): return {t["task"]: t for t in json.loads((out / "check/check.json").read_text())["tasks"]} +@pytest.mark.outcome("cjm-builtin-review-graders") def test_tasks_check_runs_builtin_graders_not_the_repository_grade(tmp_path, monkeypatch): runner = FakeRunner() res = _check(_repo(tmp_path), tmp_path / "chk", runner, monkeypatch, *TASKS, "fix_add") @@ -347,6 +351,7 @@ def test_tasks_check_runs_builtin_graders_not_the_repository_grade(tmp_path, mon assert all(GRADERS_DIR not in {m.container for m in s.mounts} for s in repo) +@pytest.mark.outcome("cjm-builtin-review-graders") def test_repo_validate_finds_a_builtin_task_without_its_data_file_or_a_wrong_name(tmp_path, capsys): root = _repo(tmp_path, {"nodata": {**REVIEW, "grader": "dokimi:review_seeds"}, # no manifest.json "typo": {**REVIEW, "grader": "dokimi:review_seed"}}) @@ -359,6 +364,7 @@ def test_repo_validate_finds_a_builtin_task_without_its_data_file_or_a_wrong_nam assert any("unknown built-in grader 'dokimi:review_seed'" in e for e in errors), errors +@pytest.mark.outcome("cjm-builtin-review-graders") def test_an_unknown_builtin_grader_is_refused_not_given_to_the_repository(tmp_path, monkeypatch): root = _repo(tmp_path, {"typo": {**REVIEW, "grader": "dokimi:review_seed"}}) res = _check(root, tmp_path / "chk", FakeRunner(), monkeypatch, "typo") diff --git a/dokimi/tests/test_repo_compare.py b/dokimi/tests/test_repo_compare.py index 4282ca7c..9a46eba5 100644 --- a/dokimi/tests/test_repo_compare.py +++ b/dokimi/tests/test_repo_compare.py @@ -427,6 +427,7 @@ def test_a_grader_failure_is_not_ceiling_when_score_max_is_not_above_zero(tmp_pa assert _row(s, "score")["headroom"] is None and "ceiling" not in format_compare(s) +@pytest.mark.outcome("cjm-repo-compare-arms") def test_unpaired_tasks_are_listed_not_compared(tmp_path): b = tmp_path / "b1" _cell(b, "fix_a", "guidance:none", 1) @@ -642,6 +643,7 @@ def test_unreadable_grade_json_leaves_the_metric_out(tmp_path): assert (pair["baseline"], pair["treatment"]) == (1.0, 0.0) # the unreadable cells count for neither +@pytest.mark.outcome("cjm-repo-compare-arms") def test_missing_cells_are_counted_and_left_out(tmp_path): b = tmp_path / "b1" _cell(b, "fix_a", "guidance:none", 1) @@ -665,6 +667,7 @@ def test_parse_selector_keeps_the_arm_whole(): assert parse_selector("r1/guidance:raw") == ("r1", "guidance:raw") +@pytest.mark.outcome("cjm-repo-compare-arms") def test_cli_prints_the_table_and_json(tmp_path, capsys): b = tmp_path / "b1" for arm in ("guidance:none", "guidance:raw", "guidance:bridge"): @@ -785,6 +788,7 @@ def test_each_selector_names_its_batches_and_a_bare_arm_over_several_warns(tmp_p assert compare([b1, b2], "b1/guidance:none", ["b1/guidance:raw"])["warnings"] == [] +@pytest.mark.outcome("cjm-arm-stamp-warn") @pytest.mark.parametrize("key", ["eval_dir", "image"]) def test_arms_from_differently_stamped_batches_warn(tmp_path, key): old, new = tmp_path / "old", tmp_path / "new" @@ -831,6 +835,7 @@ def test_images_built_from_each_tree_follow_the_tree(tmp_path): "t4c2/A vs t4c1/A: the batches ran from different image (dokimi/x:111111111111, my/own:image)"] +@pytest.mark.outcome("cjm-arm-stamp-warn") def test_cells_on_different_harness_versions_warn(tmp_path): b = tmp_path / "b1" _cell(b, "fix_a", "guidance:none", 1, harness_version="2.1.286") @@ -842,6 +847,8 @@ def test_cells_on_different_harness_versions_warn(tmp_path): "model_resolved": []}} +@pytest.mark.outcome("cjm-model-answered") +@pytest.mark.outcome("cjm-arm-stamp-warn") def test_arms_answered_by_different_models_warn(tmp_path): b = tmp_path / "b1" _cell(b, "fix_a", "guidance:none", 1, model_resolved="m-1") @@ -866,6 +873,7 @@ def test_model_warnings_follow_the_selected_model(tmp_path): assert len(compare([b], "guidance:none", models=["m-1"])["warnings"]) == 2 +@pytest.mark.outcome("cjm-model-answered") def test_model_matches_equivalence_does_not_warn(tmp_path): """Equivalent model IDs (Bedrock provider-prefix variants) do not trigger a warning.""" b = tmp_path / "b1" diff --git a/dokimi/tests/test_repo_environment.py b/dokimi/tests/test_repo_environment.py index 3838f1ec..c675251f 100644 --- a/dokimi/tests/test_repo_environment.py +++ b/dokimi/tests/test_repo_environment.py @@ -12,7 +12,7 @@ import pytest -from dokimi.artifacts import load_artifact, validate_artifact +from dokimi.artifacts import validate_artifact from dokimi.cli import build_parser from dokimi.repos.environment import files as F from dokimi.repos.environment import inspect_repo @@ -81,6 +81,7 @@ def _rules(payload: dict) -> dict[str, list]: return out +@pytest.mark.outcome("cjm-repo-inspect") def test_instruction_files_carry_scope_and_activation(report): got = {(i["path"], i["scope"], i["activation"], i["directory"]) for i in report["instructions"]} assert got == { @@ -105,6 +106,7 @@ def test_every_matching_rule_gives_a_record(): assert big.size_bytes == 300_000 and "context" in big.notes[0] +@pytest.mark.outcome("cjm-repo-inspect") def test_checks_are_per_project_root_and_say_how_they_are_known(report): got = {(c["directory"], c["ecosystem"], c["kind"], c["basis"], c["name"]) for c in report["checks"]} assert {(".", "node", "test", "declared", "test"), (".", "node", "typecheck", "declared", "typecheck"), @@ -127,6 +129,7 @@ def test_toolchain_only_tests_without_test_files_are_flagged(tmp_path): assert {c["basis"] for c in payload["checks"]} == {"toolchain"} +@pytest.mark.outcome("cjm-repo-inspect") def test_a_bare_tree_is_blocked_on_tests(tmp_path): _repo(tmp_path, {"src/a.py": "x = 1\n"}) payload = inspect_repo(WorkTree(tmp_path)) @@ -137,6 +140,7 @@ def test_a_bare_tree_is_blocked_on_tests(tmp_path): assert payload["risk"] == "low" +@pytest.mark.outcome("cjm-repo-inspect") def test_launch_risks(report): scripts = {(s["category"], s["source"]) for s in report["scripts"]} assert scripts == {("install-hook", "package.json#scripts.postinstall"), @@ -172,43 +176,6 @@ def test_file_inventory(report): assert {"files.large", "files.minified"} <= set(_rules(report)) -def test_cli_writes_the_artifact(tmp_path, capsys): - root = tmp_path / "toy" - _repo(root, {"src/a.py": "x = 1\n"}) - out = tmp_path / "env.json" - args = build_parser().parse_args(["repo", "inspect", str(root), "--out", str(out), "--fail-on-error"]) - with pytest.raises(SystemExit) as exc: - args.func(args) - assert exc.value.code == 1 - data = load_artifact("repo_environment", out) - assert data["schema_version"] == "repo_environment.v1" - text = capsys.readouterr().out - assert "ERROR checks.test.missing" in text and "verify blocked" in text - - -def test_cli_json_prints_the_stamped_payload(tmp_path, capsys): - _repo(tmp_path, {"AGENTS.md": "x"}) - args = build_parser().parse_args(["repo", "inspect", str(tmp_path), "--json"]) - args.func(args) - data = json.loads(capsys.readouterr().out) - assert data["schema_version"] == "repo_environment.v1" - validate_artifact("repo_environment", data) - - -@pytest.mark.parametrize("case", ["missing source", "unwritable out"]) -def test_cli_exits_1_when_it_cannot_read_or_write(tmp_path, capsys, case): - _repo(tmp_path / "toy", {"AGENTS.md": "x"}) - (tmp_path / "file").write_text("x") - argv = ["repo", "inspect", str(tmp_path / "toy"), "--out", str(tmp_path / "file" / "env.json")] - if case == "missing source": - argv = ["repo", "inspect", str(tmp_path / "nope")] - args = build_parser().parse_args(argv) - with pytest.raises(SystemExit) as exc: - args.func(args) - assert exc.value.code == 1 - assert "✗" in capsys.readouterr().err - - @pytest.mark.parametrize("files, expected", [ ({"CMakeLists.txt": "enable_testing()\nadd_test(NAME a COMMAND a)\n"}, {("cmake", "build", "toolchain"), ("cmake", "test", "declared")}), @@ -242,6 +209,7 @@ def test_unreadable_settings_and_hook_scripts(tmp_path): (".claude/skills/x/SKILL.md", "skill", "low"), (".vscode/settings.json", "editor", "low")} +@pytest.mark.outcome("cjm-repo-inspect") def test_inspect_does_not_run_the_repository_git_config(tmp_path): marker = tmp_path / "ran" root = tmp_path / "repo" diff --git a/dokimi/tests/test_repo_eval_task_selection.py b/dokimi/tests/test_repo_eval_task_selection.py index 6d0790bc..d1de96d3 100644 --- a/dokimi/tests/test_repo_eval_task_selection.py +++ b/dokimi/tests/test_repo_eval_task_selection.py @@ -133,6 +133,7 @@ def _select(repo: Path, tmp_path: Path, **overrides: str) -> list[str]: return json.loads(line.removeprefix("tasks=")) +@pytest.mark.outcome("cjm-repo-eval-selection") def test_a_task_directory_with_a_space_is_selected_whole(repo, tmp_path): _write(repo, f"{TASKS}/my task/grader.py", "changed\n") assert _select(repo, tmp_path) == ["my task"] @@ -167,6 +168,7 @@ def test_a_deleted_task_directory_runs_the_smoke_set(repo, tmp_path): assert _select(repo, tmp_path) == ["t1"] +@pytest.mark.outcome("cjm-repo-eval-selection") def test_a_harness_file_in_the_eval_dir_adds_the_smoke_set_to_the_changed_task(repo, tmp_path): _write(repo, f"{EVAL_DIR}/Dockerfile") _write(repo, f"{TASKS}/my task/grader.py", "changed\n") @@ -188,6 +190,7 @@ def test_the_manifest_is_compared_normalised(repo, tmp_path, manifest): assert _select(repo, tmp_path, MANIFEST=manifest) == ["t1"] +@pytest.mark.outcome("cjm-repo-eval-selection") def test_an_empty_smoke_set_means_every_task(repo, tmp_path): _write(repo, f"{EVAL_DIR}/Dockerfile") assert _select(repo, tmp_path, SMOKE_TASKS="") == ["my task", "t1"] @@ -198,6 +201,7 @@ def test_a_guidance_only_manifest_checks_no_task(repo, tmp_path): assert _select(repo, tmp_path, manifest_tasks=None) == [] +@pytest.mark.outcome("cjm-repo-eval-selection") def test_an_unrelated_file_selects_nothing(repo, tmp_path): _write(repo, "README.md", "changed\n") assert _select(repo, tmp_path) == [] @@ -236,6 +240,7 @@ def test_a_push_diffs_against_the_previous_tip(repo, tmp_path): assert _select(repo, tmp_path, EVENT="push", BEFORE="origin/main") == ["my task"] +@pytest.mark.outcome("cjm-repo-eval-selection") def test_a_push_without_a_previous_tip_checks_every_task(repo, tmp_path): _write(repo, f"{TASKS}/my task/grader.py", "changed\n") assert _select(repo, tmp_path, EVENT="push", BEFORE="0" * 40) == ["my task", "t1"] @@ -312,6 +317,7 @@ def test_a_smoke_directory_without_its_slash_is_a_prefix_as_given(repo, tmp_path assert _select(repo, tmp_path, SMOKE_PATHS="vendor/") == [] +@pytest.mark.outcome("cjm-repo-eval-selection") def test_an_unknown_explicit_task_fails_the_run(repo, tmp_path): result, _ = _run_step(repo, tmp_path, TASKS="nope") assert result.returncode != 0 and "no such task(s): nope" in result.stdout + result.stderr @@ -343,6 +349,7 @@ def _summary(tmp_path: Path, job_status: str) -> tuple[str, Path]: return result.stdout, temp / "repo-check" / "check" / "check.json" +@pytest.mark.outcome("cjm-repo-eval-workflow") def test_a_job_cancelled_before_the_verdict_records_a_timeout(tmp_path): summary, check = _summary(tmp_path, "cancelled") assert json.loads(check.read_text(encoding="utf-8"))["tasks"][0]["verdict"] == "timeout" @@ -389,6 +396,7 @@ def test_allow_unsupported_is_a_boolean_input_that_is_off_by_default(): assert declared["default"] is False +@pytest.mark.outcome("cjm-repo-eval-workflow") @pytest.mark.skipif(sys.platform == "win32", reason="the step is a bash script") @pytest.mark.parametrize("warm", [False, True]) @pytest.mark.parametrize("allow", [False, True]) diff --git a/dokimi/tests/test_repo_host.py b/dokimi/tests/test_repo_host.py index 964c1f3c..9b666ed7 100644 --- a/dokimi/tests/test_repo_host.py +++ b/dokimi/tests/test_repo_host.py @@ -184,6 +184,7 @@ def _check(root, out, runner, monkeypatch, *extra): "--cache-root", str(out / "caches"), *extra], monkeypatch, runner) +@pytest.mark.outcome("cjm-tasks-check-host") def test_tasks_check_reports_a_task_the_host_cannot_show_as_unsupported(tmp_path, monkeypatch, capsys): root = _repo(tmp_path) runner = HostRunner(_probe_out(affinity=2, cpuinfo=AMD_CPUINFO)) @@ -215,6 +216,7 @@ def test_tasks_check_refuses_a_bad_host_shape_before_any_container(tmp_path, mon assert "task fix_race: task.json host.min_cpus must be an integer >= 1, got '4'" in capsys.readouterr().err +@pytest.mark.outcome("cjm-tasks-check-host") def test_allow_unsupported_lets_host_unsupported_tasks_pass_the_exit_code(tmp_path, monkeypatch, capsys): root = _repo(tmp_path) amd = _probe_out(affinity=2, cpuinfo=AMD_CPUINFO) @@ -231,6 +233,7 @@ def test_allow_unsupported_lets_host_unsupported_tasks_pass_the_exit_code(tmp_pa ({"fix_nobug": {"family": "fix", "grader": "calc", "gate": ["tests_pass"], "prompt": "x"}}, "broken"), ({"fix_pip": {"family": "fix", "grader": "calc", "egress": ["pypi.org"], "prompt": "x"}}, "egress"), ]) +@pytest.mark.outcome("cjm-tasks-check-host") def test_allow_unsupported_still_fails_broken_and_egress_tasks(tmp_path, monkeypatch, capsys, extra, why): root, _ = make_repo(tmp_path, {"fix_race": RACE, **extra}) (task,) = extra @@ -255,6 +258,7 @@ def _run(root, out, runner, monkeypatch, *extra): monkeypatch, runner) +@pytest.mark.outcome("cjm-repo-host-unsupported") def test_run_skips_unsupported_cells_and_the_report_shows_them(tmp_path, monkeypatch, capsys): root = _repo(tmp_path) out = tmp_path / "runs" @@ -301,6 +305,7 @@ def test_run_skips_unsupported_cells_and_the_report_shows_them(tmp_path, monkeyp assert len(load_cells(batch)[0]) == 4 and not list(batch.glob("*/unsupported.json")) +@pytest.mark.outcome("cjm-repo-host-unsupported") def test_a_capable_run_that_stops_early_leaves_the_unreached_cells_missing(tmp_path, monkeypatch, capsys): root = _repo(tmp_path) out = tmp_path / "runs" @@ -313,6 +318,7 @@ def test_a_capable_run_that_stops_early_leaves_the_unreached_cells_missing(tmp_p assert summary["unsupported"] == [] and summary["missing"] == ["fix_race__guidance-raw__m-1__r2"] +@pytest.mark.outcome("cjm-repo-host-unsupported") def test_run_refuses_a_bad_host_shape_before_any_agent(tmp_path, monkeypatch, capsys): root = _repo(tmp_path, host={"cpu_flags": "avx512f"}) runner = HostRunner(_probe_out()) @@ -467,6 +473,7 @@ def test_cpu_flags_match_regardless_of_case(): assert unmet(_task({"cpu_flags": ["AVX512F"]}), upper) == (None, None) +@pytest.mark.outcome("cjm-tasks-check-host") def test_probe_failure_is_marked_host_probe_never_hidden_by_allow_unsupported(tmp_path, monkeypatch, capsys): """Regression: probe failure (unsupported_by='host-probe') must not be hidden by --allow-unsupported. @@ -595,6 +602,7 @@ def test_a_host_miss_still_probes_isolation_and_skips_only_null(tmp_path, monkey assert "isolation" in race and "oracle" in race and "null" not in race +@pytest.mark.outcome("cjm-tasks-check-host") def test_warm_judges_the_host_like_an_offline_check(tmp_path, monkeypatch, capsys): root = _repo(tmp_path) runner = HostRunner(_probe_out(affinity=2, cpuinfo=AMD_CPUINFO)) @@ -656,6 +664,7 @@ def test_warm_runs_the_grade_with_the_bridge_network(tmp_path, monkeypatch): assert {s.network for s in plain.specs if (s.name or "").split("-")[-2] == "grade"} == {"none"} +@pytest.mark.outcome("cjm-tasks-check-host") def test_warm_with_allow_unsupported_judges_the_host(tmp_path, monkeypatch, capsys): root = _repo(tmp_path) runner = HostRunner(_probe_out(affinity=2, cpuinfo=AMD_CPUINFO)) diff --git a/dokimi/tests/test_repo_inspect_cli.py b/dokimi/tests/test_repo_inspect_cli.py new file mode 100644 index 00000000..65cfd325 --- /dev/null +++ b/dokimi/tests/test_repo_inspect_cli.py @@ -0,0 +1,54 @@ +# Copyright (C) 2026 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +"""Tests for the ``repo inspect`` command line: artifact, JSON and exit codes.""" + +from __future__ import annotations + +import json + +import pytest + +from dokimi.artifacts import load_artifact, validate_artifact +from dokimi.cli import build_parser +from tests.test_repo_environment import _repo + + +@pytest.mark.outcome("cjm-repo-inspect") +def test_cli_writes_the_artifact(tmp_path, capsys): + root = tmp_path / "toy" + _repo(root, {"src/a.py": "x = 1\n"}) + out = tmp_path / "env.json" + args = build_parser().parse_args(["repo", "inspect", str(root), "--out", str(out), "--fail-on-error"]) + with pytest.raises(SystemExit) as exc: + args.func(args) + assert exc.value.code == 1 + data = load_artifact("repo_environment", out) + assert data["schema_version"] == "repo_environment.v1" + text = capsys.readouterr().out + assert "ERROR checks.test.missing" in text and "verify blocked" in text + + +@pytest.mark.outcome("cjm-repo-inspect") +def test_cli_json_prints_the_stamped_payload(tmp_path, capsys): + _repo(tmp_path, {"AGENTS.md": "x"}) + args = build_parser().parse_args(["repo", "inspect", str(tmp_path), "--json"]) + args.func(args) + data = json.loads(capsys.readouterr().out) + assert data["schema_version"] == "repo_environment.v1" + validate_artifact("repo_environment", data) + + +@pytest.mark.outcome("cjm-repo-inspect") +@pytest.mark.parametrize("case", ["missing source", "unwritable out"]) +def test_cli_exits_1_when_it_cannot_read_or_write(tmp_path, capsys, case): + _repo(tmp_path / "toy", {"AGENTS.md": "x"}) + (tmp_path / "file").write_text("x") + argv = ["repo", "inspect", str(tmp_path / "toy"), "--out", str(tmp_path / "file" / "env.json")] + if case == "missing source": + argv = ["repo", "inspect", str(tmp_path / "nope")] + args = build_parser().parse_args(argv) + with pytest.raises(SystemExit) as exc: + args.func(args) + assert exc.value.code == 1 + assert "✗" in capsys.readouterr().err diff --git a/dokimi/tests/test_repo_run.py b/dokimi/tests/test_repo_run.py index d5828406..0b30a87a 100644 --- a/dokimi/tests/test_repo_run.py +++ b/dokimi/tests/test_repo_run.py @@ -89,6 +89,7 @@ def test_run_isolates_the_agent_and_grades_each_cell(setup, capsys): assert raw["model_resolved"] == ["m-1"] and summary["model_mismatch"] == {} +@pytest.mark.outcome("cjm-model-answered") def test_a_run_answered_by_another_model_is_recorded_and_reported(setup, monkeypatch, capsys): # The record must carry what the harness reported, not the request, or the mismatch warning never fires. monkeypatch.setenv("FAKE_ANSWER_MODEL", "m-0") diff --git a/dokimi/tests/test_repo_uptake.py b/dokimi/tests/test_repo_uptake.py index b9f13aa8..cdde1fcc 100644 --- a/dokimi/tests/test_repo_uptake.py +++ b/dokimi/tests/test_repo_uptake.py @@ -79,6 +79,7 @@ def test_reads_and_calls_of_the_arms_mcp_servers_count_others_do_not(): assert unmeasured["measured"] is False and unmeasured["used"] is False +@pytest.mark.outcome("cjm-arm-uptake") def test_a_call_the_harness_denied_is_not_a_use(): def use(i, name, args): return {"type": "assistant", "message": {"content": [{"type": "tool_use", "id": i, "name": name, "input": args}]}} @@ -176,6 +177,7 @@ def _run(s, batch, *arms, model="m-1"): args.func(args) +@pytest.mark.outcome("cjm-arm-uptake") def test_repo_run_records_uptake_and_compare_warns_when_nothing_was_used(setup, monkeypatch): # noqa: F811 patch = setup["tmp"] / "kit.diff" patch.write_text(KIT_PATCH) @@ -222,6 +224,7 @@ def _used_none(summary): return [w for w in summary["warnings"] if "used none" in w] +@pytest.mark.outcome("cjm-arm-uptake") def test_an_agent_that_left_no_trace_is_unmeasured_one_that_called_no_tool_is_measured(setup, monkeypatch): # noqa: F811 patch = setup["tmp"] / "kit.diff" patch.write_text(KIT_PATCH) diff --git a/dokimi/tests/test_skill_treatment.py b/dokimi/tests/test_skill_treatment.py index 45f35cb9..9fc625bc 100644 --- a/dokimi/tests/test_skill_treatment.py +++ b/dokimi/tests/test_skill_treatment.py @@ -483,6 +483,7 @@ def test_a_malformed_token_count_reads_as_zero(): assert usage is not None and (usage.prompt_tokens, usage.completion_tokens) == (0, 3) +@pytest.mark.outcome("cjm-skill-listing") def test_skill_listing_counts_what_the_listing_shows(tmp_path): short = _skill(tmp_path, name="a") assert skill_listing(short) == {"description_chars": len("Use for a things."), @@ -494,6 +495,7 @@ def test_skill_listing_counts_what_the_listing_shows(tmp_path): assert listing["truncated"] is True +@pytest.mark.outcome("cjm-skill-listing") @pytest.mark.parametrize("chars, truncated", [(1536, False), (1537, True)]) def test_skill_listing_cuts_only_past_the_cap(tmp_path, chars, truncated): skill = tmp_path / "s" diff --git a/dokimi/tests/test_task_runs_report.py b/dokimi/tests/test_task_runs_report.py index 91cf6314..86b002b3 100644 --- a/dokimi/tests/test_task_runs_report.py +++ b/dokimi/tests/test_task_runs_report.py @@ -270,6 +270,7 @@ def test_pseudo_replication_is_disclosed_when_repeats_are_paired_as_cells(): assert "anti-conservative" not in render_task_runs_report(_with_mcnemar()) +@pytest.mark.outcome("cjm-skill-discover") def test_report_puts_the_discovery_arm_beside_no_skill_and_with_skill(): arms = ["docker-claude", "docker-claude-skill:s", "docker-claude-skill-discover:s"] rows = [] @@ -325,6 +326,7 @@ def test_truncated_discovery_row_is_noted_without_its_pair(notice, truncated, n_ assert ("discovery row(s) ran with a description longer" in md) is bool(n_cut) +@pytest.mark.outcome("cjm-skill-discover") def test_discovery_gap_withheld_when_arms_attempted_different_cells(): """Gap is None when discovery arm ran fewer tasks than the with-skill arm.""" arms = ["docker-claude", "docker-claude-skill:s", "docker-claude-skill-discover:s"] From a7f650d336eae2fb1f7986a3f7858cce152411af Mon Sep 17 00:00:00 2001 From: Alexander Rybkin Date: Sat, 24 Oct 2026 23:22:39 -0700 Subject: [PATCH 2/4] docs(journeys): mark AC-J10-008 partial in the catalog as in the registry The registry moved AC-J10-008 to partial, but the catalog line still ended "*Proposed.*" and J10 Today did not mention the repo-arm warnings. A test now fails when a criterion marked Proposed in the catalog has another delivery in the registry. Signed-off-by: Alexander Rybkin --- dokimi/docs/user-journeys/journeys.md | 9 ++++++--- dokimi/tests/test_journeys_trace.py | 10 ++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/dokimi/docs/user-journeys/journeys.md b/dokimi/docs/user-journeys/journeys.md index cdcc4682..430a7c3e 100644 --- a/dokimi/docs/user-journeys/journeys.md +++ b/dokimi/docs/user-journeys/journeys.md @@ -610,8 +610,11 @@ differs between them, model, repetitions, judge, spend. **Today.** Two-run and multi-run comparison on a pinned question set is available and traced. More than two arms run with `python cli.py arms run`. -Declaring an arbitrary change (OS, dependency, subagents) as a variant of one -experiment is **proposed**, as is adaptive allocation across many variants. +For repository arms, `repo compare` warns when the compared cells ran from a +different eval directory, image, harness version or resolved model +(AC-J10-008, partial). Declaring an arbitrary change (OS, dependency, +subagents) as a variant of one experiment is **proposed**, as is adaptive +allocation across many variants. **Connections.** Underlies J2, J4 (effect), J6, J8, J9, J12, J13. @@ -637,7 +640,7 @@ cost of both runs. #### UC-J10-003 Declare an arbitrary change as the variant -- **AC-J10-008** An experiment declares what differs between its variants, records the resolved settings of each, and reports differences it did not declare as confounds. *Proposed.* +- **AC-J10-008** An experiment declares what differs between its variants, records the resolved settings of each, and reports differences it did not declare as confounds. *Partial: repository arms only; `repo compare` warns in free text, not as structured confounds.* #### UC-J10-004 Know whether a result is stable diff --git a/dokimi/tests/test_journeys_trace.py b/dokimi/tests/test_journeys_trace.py index ed001006..3dfc99a8 100644 --- a/dokimi/tests/test_journeys_trace.py +++ b/dokimi/tests/test_journeys_trace.py @@ -12,6 +12,7 @@ from __future__ import annotations import json +import re import pytest import yaml @@ -95,6 +96,15 @@ def test_every_enforced_criterion_names_evidence_some_test_claims(): assert sorted(missing) == [] +def test_a_criterion_the_catalog_marks_proposed_is_proposed_in_the_registry(): + """The catalog page and the generated report must not disagree on what is delivered.""" + catalog, _ = journeys.parse_catalog(plugin.REPO_ROOT / journeys.CATALOG) + registry = yaml.safe_load((plugin.CONTRACT_DIR / "journeys.yaml").read_text(encoding="utf-8")) + marked = {ac for ac, c in catalog["criteria"].items() if re.search(r"\*Proposed[^*]*\*$", c["text"])} + wrong = {ac: e.get("delivery") for ac, e in registry["criteria"].items() if ac in marked and e.get("delivery") != "proposed"} + assert wrong == {} + + # ------------------------------------------------------------------ catalog problems From a5274ed6c674b4557a7d7758bd20002328f3cefd Mon Sep 17 00:00:00 2001 From: Alexander Rybkin Date: Sun, 25 Oct 2026 02:25:17 -0700 Subject: [PATCH 3/4] docs(journeys): AC-J14-011 is available: #197 merged, its report line is tested Signed-off-by: Alexander Rybkin --- dokimi/docs/user-journeys/journeys.md | 2 +- dokimi/tests/outcomes/journeys.yaml | 7 +++++-- dokimi/tests/test_novelty_report.py | 1 + 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/dokimi/docs/user-journeys/journeys.md b/dokimi/docs/user-journeys/journeys.md index 430a7c3e..a1f49604 100644 --- a/dokimi/docs/user-journeys/journeys.md +++ b/dokimi/docs/user-journeys/journeys.md @@ -814,7 +814,7 @@ task outcomes, and differences inside the judge band are not quoted. - **AC-J14-003** The documented authoring path (routing, estimate, extraction, probing, shortening, view) runs end to end on stub models through the CLI and writes its intermediate artifacts. - **AC-J14-004** Probing an inventory against an artifact that changed since extraction is refused. - **AC-J14-008** A novelty record states the share of the skill's readable words it did not measure and the basis of that share; the gate judges a stored record on its own stamped basis, and a record with an unknown basis is reported unmeasured. -- **AC-J14-011** The novelty report shows a conflict count the gate did not clear as not measured, never as a clean 0. *Proposed.* +- **AC-J14-011** The novelty report shows a conflict count the gate did not clear as not measured, never as a clean 0. - **AC-J14-012** Analyze states on its output the share of readable words it measured, and warns when it is half or less. *Proposed.* #### UC-J14-002 Inspect a run's context diff --git a/dokimi/tests/outcomes/journeys.yaml b/dokimi/tests/outcomes/journeys.yaml index c89424a6..1a435da4 100644 --- a/dokimi/tests/outcomes/journeys.yaml +++ b/dokimi/tests/outcomes/journeys.yaml @@ -418,8 +418,11 @@ criteria: delivery: proposed decision_ref: https://github.com/intel/dokimi/pull/172 AC-J14-011: - delivery: proposed - decision_ref: https://github.com/intel/dokimi/pull/197 + delivery: available + enforce: true + capability: novelty + evidence: + - {id: cjm-novelty-uncleared-conflicts, kind: unit, stubs: []} AC-J14-012: delivery: proposed decision_ref: https://github.com/intel/dokimi/pull/173 diff --git a/dokimi/tests/test_novelty_report.py b/dokimi/tests/test_novelty_report.py index ecb7d1ea..b360b3cc 100644 --- a/dokimi/tests/test_novelty_report.py +++ b/dokimi/tests/test_novelty_report.py @@ -44,6 +44,7 @@ def test_a_zero_the_gate_clears_reads_as_zero(artifact): ({"spans": {"capped_atom_set": True, "n_atoms_indexed": 300}}, "The atom set was capped (300 indexed)"), ({"conflict_coverage": {"pairs_total": 1770}}, "The conflict_coverage record is malformed"), ]) +@pytest.mark.outcome("cjm-novelty-uncleared-conflicts") def test_a_zero_the_gate_does_not_clear_is_not_shown_as_zero(overrides, reason): markdown = render_novelty_markdown(_artifact(**overrides)) assert f"> **Conflicts not cleared.** {reason}" in markdown From a3930f4c07141c4e26945c98278f8ab7e96483e0 Mon Sep 17 00:00:00 2001 From: Alexander Rybkin Date: Sun, 25 Oct 2026 02:35:34 -0700 Subject: [PATCH 4/4] docs(journeys): AC-J12-002 reads as the registry's partial in J12 Today and its line Signed-off-by: Alexander Rybkin --- dokimi/docs/user-journeys/journeys.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/dokimi/docs/user-journeys/journeys.md b/dokimi/docs/user-journeys/journeys.md index a1f49604..1f26b9c5 100644 --- a/dokimi/docs/user-journeys/journeys.md +++ b/dokimi/docs/user-journeys/journeys.md @@ -718,15 +718,16 @@ cost. (`python cli.py repo scan --fail-on-fail`). `baseline compare --strict` adds a non-zero exit only when a model cannot be verified or differs from the baseline; a regression in the scores is printed and exits 0. Failing on a regression beyond -a threshold is **proposed** (AC-J12-001), and so is configurable blocking policy -across check kinds (AC-J12-002). +a threshold is **proposed** (AC-J12-001). Blocking policy is **partial** +(AC-J12-002): `repo tasks check --allow-unsupported` decides host-unsupported +tasks; budget-stopped and inconclusive judge results have no policy. ### Use cases and acceptance #### UC-J12-001 Gate a PR on a budgeted check - **AC-J12-001** A strict baseline comparison exits non-zero on a regression beyond the threshold. -- **AC-J12-002** An inconclusive or budget-stopped check is reported as such, and whether it blocks is the configured policy's decision. +- **AC-J12-002** An inconclusive or budget-stopped check is reported as such, and whether it blocks is the configured policy's decision. *Partial: one flag, for host-unsupported tasks; budget-stopped and inconclusive judge results have none.* - **AC-J12-003** The reusable CI check runs only the tasks a change touches, adds a smoke set when shared harness files change, and fails on an unknown task name instead of checking nothing. - **AC-J12-004** A gate on a run whose result lacks planned cells reports it incomplete and names the missing cells; it never passes on the cells that finished. *Proposed.*