fix(validate): honest scoped PASS — define the disclosure homes instead of paying to delete caveats - #1027
Merged
Merged
Conversation
…aying to delete caveats
A fresh-install smoke proved a contradiction: validate.py rejects any PASS
whose not_checked is nonempty, silently rewriting the verdict to NOT_PROVEN,
while SKILL.md, AGENTS.md Closeout, and docs/architecture/operating-loop.md all
instruct disclosing checked and not_checked. An identical honest draft passed
only after its scope caveats were deleted.
Decision: PASS stays strict-empty (option b), and every doc surface now defines
where honest scope limits go instead. not_checked has exactly one meaning —
in-scope acceptance surface that went unverified — so a PASS has none by
construction; a bounded proof of a criterion goes in criteria[].reason, a
declared non-goal stays in the intent source (optionally restated as an
evidence-backed boundary criterion), and residual risk goes in the report. The
integrity finding now names those homes instead of failing silently, so the
honest draft is told where to move a caveat rather than paid to delete it.
Also fixes two verified SKILL.md defects and one error message:
- checkout-relative `python3 skills/validate/scripts/...` paths are now
install-agnostic ($SKILL_DIR = this SKILL.md's own directory).
- the `manifest` subcommand was documented flagless; a Helper commands table
now documents the real flags for all five subcommands.
- store-verdict's criteria-field error names the allowed set
{id, result, evidence_refs, reason} and the offending keys.
boshu2
enabled auto-merge (squash)
July 31, 2026 13:05
boshu2
force-pushed
the
fix/validate-honest-scoped-pass
branch
from
July 31, 2026 13:05
2a2573f to
466cc1b
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The contradiction (empirically proven)
A fresh-install smoke found that
skills/validate/scripts/validate.pyrejects anyPASSwhosenot_checkedis nonempty and silently rewrites the verdict toNOT_PROVENvia avalidate.integrityfinding — whileskills/validate/SKILL.md(output contract + step 5),AGENTS.mdCloseout, anddocs/architecture/operating-loop.mdall instruct disclosingcheckedandnot_checked.Identical honest draft →
NOT_PROVEN. Same draft withnot_checkedemptied →PASS. The tool paid users to delete their scope caveats — the inverse of the product thesis.Decision: option (b), with the disclosure homes actually defined
PASS stays strict-empty. Every doc surface now defines exactly where each kind of honest scope limit goes, and the tool names those homes in its error instead of failing silently.
Why not (a) — the recommended option
Two independent reasons, one semantic and one structural.
Semantic (the primary reason). The bug is not an over-strict PASS rule; it is a semantic overload of
not_checked.not_checkedmeans in-scope acceptance surface this validation did not verify. A verdict is a judgment over acceptance, and the operating contract already says unverified acceptance isNOT_PROVEN. SoPASS ∧ not_checked ≠ ∅is a genuine contradiction, and relaxing it would destroy the only guarantee that makes PASS worth anything: PASS means the whole declared acceptance surface was verified.What the smoke actually caught is that honest validators had nowhere else to put a declared non-goal — a thing that was never acceptance in the first place — so they dumped it in the acceptance-only field and got punished for it. Fix the overload, not the strictness.
Structural.
verdict.v2is a tri-implementation contract: the Python writer (skills/validate/scripts/validate.py), the Go reader (cli/internal/verdictcheck), andschemas/verdict.v2.schema.json, kept in agreement by the golden corpus intests/fixtures/verdict-contract/cases/(23 cases, includinginvalid-pass-not-checked-nonempty). Option (a) —not_checkedentries as{scope, reason}objects, or a parallelnot_checked_reasonsfield — changes the persisted wire shape and would require:cli/internal/verdictcheck/verdictcheck.go:NotChecked []string,DisallowUnknownFields(), andvalidatePass'slen(verdict.NotChecked) != 0(forbidden write scope),cli/internal/statusapp/statusapp.go(forbidden),schemas/verdict.v2.schema.json(additionalProperties: false,not_checked: {maxItems: 0}on PASS),schemas/rpi-report.v1.schema.jsonandskills/rpi/scripts/run_once.py(forbidden),That is a
verdict.v3wire-format change, not a fix — and shipping the Python leg alone would fork the contract and turn every stored PASS into somethingao statusreports as invalid.The four homes (now documented everywhere)
criteria[].reasonon that criterioncriteriacli/**is a declared non-goal; the diff proves it untouched"not_checked— and the result isNOT_PROVEN, not PASSNote the second row: converting a non-goal into an evidence-backed boundary criterion is stronger than an unexplained
not_checkedentry — the caveat becomes a proven boundary claim that stays visible in the stored artifact. Nothing is deleted to earn a PASS.Proof of the incentive fix (the smoke's exact scenario)
Both drafts carry the same honest content. Driven through the real
store-verdictCLI:Before: silent rewrite, and the only way to a PASS was deleting the caveats. After: an actionable message naming where each caveat belongs, and a PASS artifact that still contains all three caveats.
Regression test
Added to the validate skill's existing test surface (
skills/validate/scripts/test_validate.py, run byskills/validate/scripts/validate.sh→tests/skills/run-all.sh):test_honest_scoped_pass_round_trips_through_documented_homes— draft A (non-goals innot_checked) storesNOT_PROVENwith a finding namingcriteria[].reason, non-goals, and the report; draft B (same content, documented homes) storesPASS, schema-valid, with both caveat strings re-read off disk from the persisted artifact.test_criteria_field_error_names_the_allowed_set— the store-verdict error names{id, result, evidence_refs, reason}and the offending key.Also in scope (three verified defects)
python3 skills/validate/scripts/validate.pyonly resolves in a repository checkout; the installed path is.agents/skills/validate/scripts/validate.py. SKILL.md now uses$SKILL_DIR— this SKILL.md's own directory — and names both layouts.manifestdocumented flagless but requires--rootand at least one repeatable--include. A new Helper commands table documents the real required/optional flags for all five subcommands (manifest,verify-manifest,snapshot-intent,digest,store-verdict).verdict.v2 criteria[0] has invalid fields (unknown confidence); allowed fields are {id, result, evidence_refs, reason}, of which id, result, evidence_refs are required.Surfaces changed (all agree)
skills/validate/scripts/validate.py—NOT_CHECKED_HOMESconstant + contract comment; both PASS/not_checked messages; criteria-field error.skills/validate/SKILL.md— new Scope disclosure and Helper commands sections; steps 1, 4, 5, 6 corrected.AGENTS.mdCloseout (CLAUDE.mdsymlinks to it — edited AGENTS.md, not the symlink).docs/architecture/operating-loop.md— Fresh Validate section.skills-codex/validate/**,images/gemini/skills/validate/SKILL.md,skills-codex/.agentops-manifest.json, skill mesh.cli/**,skills/plan/**,skills/rpi/**,schemas/verdict.v2.schema.json,tests/fixtures/verdict-contract/**,packs/agentops-executor/**(frozen historical bytes perscripts/check-gc-maintainer-ops.sh).Gates
Residual risk
not_checkedremains a flat string array. If a future caller genuinely needs machine-readable justified non-goals inside a verdict, that is a deliberateverdict.v3with all three implementations moved together — not something to smuggle in through the Python writer.