Repository navigation
fix(acq-kits): give verify scripts a "could not check" verdict state - #460
Conversation
All seven kit verify scripts ended with the same two-state verdict:
[ "$fail" -eq 0 ]
That answers "did anything fail?" and cannot answer "did we actually check
anything?". Two consequences, both measured:
1. A run where NOTHING executed reported success. With pass=0 and fail=0 the
test is true, so the script printed "All checks passed." and exited 0 having
verified nothing. Reproduced in isolation:
pass=0 fail=0 -> "All checks passed." exit 0
2. A skipped check was indistinguishable from a passing one. Across the seven
scripts there are 29 skip/NOTE sites and 9 warn sites that increment no
counter. Worst case: git-ssh-sign skips its end-to-end signed-commit check --
the kit's entire purpose -- whenever no key is in the forwarded SSH agent, and
still reported "All checks passed." openchamber and paseo DO define skip() and
warn(), but they are cosmetic printf with no counter, so a run can print 19
yellow SKIPs and still claim the offline gate passed.
This is the ninth instance of this defect class in this family of repos
(ENV-1, SEC-6, DX-7/8/9, SEC-12/13/17, DEP-4 are the prior ones), so the fix is
a shared contract rather than seven local patches.
WHAT THIS ADDS. integrations/isolation/acq-kits/verify-report.sh: five states and
a verdict that is a function of a coverage record, not of a failure counter.
ok ran and passed
bad ran and failed
unver COULD NOT RUN (missing tool, absent credential, no network) --
counted, and degrades the verdict
skip not applicable in this configuration -- counted, does NOT degrade
warn advisory; a documented fallback was taken -- does not degrade
exit 0 every check that ran passed, and at least one ran
exit 1 at least one check FAILED
exit 3 nothing failed but the run is not a pass: a check could not be
performed, or no check ran at all
3 is distinct from 1 so a caller can tell "we looked and found problems" from
"we could not look". The unver/skip split is the load-bearing part: collapsing
them is how a verify script claims coverage it does not have.
STAGED CONVERSION. Two kits here -- git-ssh-sign and usai-provider, the two
smallest -- so the contract is reviewable in isolation before the bulk edit.
Remaining: openchamber (641 lines, 3 gates), paseo (864, 3), pi-coding-agent
(337, 3), agentic-coding-playbook (167), zscaler-ca-certificate (167). They are
listed in the test's UNCONVERTED set, which must reach empty.
Converted sites:
git-ssh-sign - no host SSH key -> unver (was a bare echo NOTE)
- no forwarded key -> unver (was a bare echo SKIP; this is the
kit's only end-to-end assertion)
usai-provider - no USAi secret -> unver (was a bare echo NOTE)
- API probe empty -> unver. in_sbx() swallows the exit status, so
an empty result means EITHER "no curl in the guest" OR "sbx
exec failed" -- indistinguishable, and never a pass.
ENFORCEMENT. scripts/tests/test_verify_contract.py, 12 tests, because nothing in
CI runs these shell scripts: they need a live sandbox this repo's CI cannot
create, and the repo has no shellcheck hook, so 2,400 lines of shell are
currently linted by nothing. The tests read the scripts as text and assert the
library keeps BOTH degrading branches (a deleted branch silently restores the
two-state verdict for every caller at once), that converted kits source it and
do not shadow its reporters with no-op locals, that no bare `echo "SKIP"`
remains, and that UNCONVERTED describes reality in BOTH directions -- a kit
converted without being delisted fails, and so does a listed kit that reverted.
Every helper asserts its input set is non-empty first, so the suite cannot pass
vacuously over a glob that stopped matching.
A near-miss worth recording: the library was first written to acq-kits/lib/, and
.gitignore:13 carries the stock Python build-artifact block whose `lib/` rule
silently excluded it. Committing would have shipped two kits sourcing a file git
never tracked -- every verify run dying on "No such file or directory". Caught
because `git status` listed the two modified kits and the new test but not the
library. It now sits at the acq-kits root beside validate-kits.py, the existing
precedent for shared assets there, and the reason is recorded in the test file so
nobody re-adds a lib/.
Verified:
all five verdict states exercised directly:
nothing ran -> exit 3 clean -> exit 0 a failure -> exit 1
an unver -> exit 3 skip-only -> exit 0
pytest scripts/tests/ -> 413 passed (12 new)
degraded path of the TESTS: reverting usai-provider to origin/main
-> FAILS "does not source lib/verify-report.sh"
validate_repo.py --root . -> exit 0
validate-kits.py --root . --strict -> all kits OK
scan_unsafe_shell.py . -> 0 errors, 0 warnings
shellcheck -s sh verify-report.sh -> exit 0
shellcheck, converted kits -> git-ssh-sign 10 before / 10 after
usai-provider 7 before / 7 after
(pre-existing SC2015/SC2016 info
findings; I added none)
ruff check / ruff format --check -> clean
markdownlint -> 0 issues
git diff --check -> clean
NOT IN THIS PR, deliberately: general shell linting. These scripts have never
been shellcheck'd and adding a hook would surface unrelated findings across
2,400 lines; that belongs in its own change. This one closes the verdict-honesty
gap only.
AI assistance disclosed: prepared by an AI agent (OpenCode) on operator
instruction. Human review and merge still required.
…o converted CI caught a real defect in the contract test, from a direction I had not anticipated. Rebased onto main (1.10.0) and fixed. WHAT HAPPENED. `oci-engine` (#437) landed on main while this change was in review — an EIGHTH verify script that did not exist when I branched. The test derived its converted set by subtraction: {k: p for k, p in _verify_scripts().items() if k not in UNCONVERTED} so a kit in neither set was treated as CONVERTED by default. The four contract tests then failed against it with "oci-engine/scripts/verify does not source lib/verify-report.sh", which is true but misleading: the real problem was that a kit appeared that nobody had classified. I had guarded both directions I thought of — a kit converted but still listed, and a listed kit that reverted — and missed the one that actually happened. The default was the bug: an unclassified kit silently inherited a contract it does not implement, which is the same shape as the defect this whole change exists to fix (something unmeasured treated as passing). THE FIX. Two explicit sets, CONVERTED and UNCONVERTED, plus `test_every_kit_is_classified` asserting that every verify script appears in exactly one of them. A kit in neither is now a hard error naming the real cause, and the sets cannot name a kit that has no verify script. Proved it by simulating a ninth unclassified kit: AssertionError: verify script(s) in no classification set: ['zz-phantom']. Add each to CONVERTED (if it sources verify-report.sh) or to UNCONVERTED (if it is still on the old two-state verdict). A kit is never converted by default. CORRECTED COUNTS, recounted on the rebased tree rather than carried forward: there are EIGHT verify scripts, not seven, and 35 uncounted `skip`/NOTE sites, not 29 (9 `warn` sites unchanged). oci-engine itself has 303 lines, 2 gates, and 2 `[ "$fail" -eq 0 ]` verdicts, so it has the primary defect too; it is listed as UNCONVERTED for the staged bulk conversion. Stale numbers in the library header and the test docstring are updated. UNCONVERTED is now: agentic-coding-playbook, oci-engine, openchamber, paseo, pi-coding-agent, zscaler-ca-certificate. It must reach empty. Unrelated to the code, but it is why this took a second pass: the CI log could not be read at first because `gh run view --log` failed with `x509: certificate signed by unknown authority`. Root cause is Go-specific and documented in the review workspace, not here — Go's x509.SystemCertPool() omits macOS admin-installed roots (121 of the host's 177), so any Go tool fails against a proxy-intercepted host while `curl` to the same host verifies fine. Reading the log required pointing SSL_CERT_FILE at a bundle including the admin roots. No repo change needed; noted so the next person who sees that error does not mistake it for a CI outage. Verified on the rebased tree: pytest scripts/tests/ -> 420 passed (13 contract tests) unclassified-kit probe -> FAILS with the naming assertion validate_repo.py --root . -> exit 0 validate-kits.py --root . --strict -> all kits OK scan_unsafe_shell.py . -> 0 errors, 0 warnings shellcheck -s sh verify-report.sh -> exit 0 ruff check / ruff format --check -> clean AI assistance disclosed: prepared by an AI agent (OpenCode) on operator instruction. Human review and merge still required.
54563f0 to
49572de
Compare
|
CI is green now (7/7). Rebased onto main (1.10.0) and fixed a real defect the failure exposed — in my own test, not the library. What CI caught
{k: p for k, p in _verify_scripts().items() if k not in UNCONVERTED}So a kit in neither set was treated as converted by default, and four contract tests failed against it with "oci-engine/scripts/verify does not source lib/verify-report.sh" — true, but misleading. The real problem was that a kit appeared nobody had classified. I'd guarded both directions I thought of — a kit converted but still listed, and a listed kit that reverted — and missed the one that actually happened. The default was the bug: an unclassified kit silently inherited a contract it doesn't implement, which is the same shape as the defect this whole change exists to fix. The fixTwo explicit sets, Corrected countsRecounted on the rebased tree rather than carried forward from the original description:
Stale numbers in the library header and test docstring are updated. Verification on the rebased treeOne note, not a repo issueI initially couldn't read the CI log at all: Go's Reading the log required pointing AI-assisted (OpenCode), on operator instruction. Human review and merge required. |
mogul
left a comment
There was a problem hiding this comment.
Technically this is fine, but the level of bot-rumination in the comments distracts from understanding what it does, and memorializes context that won't matter going forward.
So I'm grudgingly requesting changes, even though that means another review round-trip... Sorry.
If you respond with improvements that only land in comments, then I approve your next push in advance, so feel free to check the box that skips the "require review" gate and merge it then!
…im comment narration per mogul's review Two fixes, both from mogul's CHANGES_REQUESTED review: 1. Rebase onto main pulled in the opencode kit (#466), which landed with a verify script in neither CONVERTED nor UNCONVERTED, failing test_every_kit_is_classified exactly as the test is designed to catch (reproduced locally before fixing: `AssertionError: verify script(s) in no classification set: ['opencode']`). opencode's verify script is on the old two-state verdict, so it goes in UNCONVERTED alongside the other not-yet-migrated kits. 2. Trimmed six comment/docstring blocks that narrated the OLD two-state verdict's history ("used to end with...", "the old reporters had no...", the oci-engine near-miss anecdote) instead of describing the current code. Per review: this kind of narration belongs in the commit message, not in the code, where it has no reader value and persists as noise. Trimmed in: - integrations/isolation/acq-kits/git-ssh-sign/scripts/verify - integrations/isolation/acq-kits/usai-provider/scripts/verify - integrations/isolation/acq-kits/README.md - integrations/isolation/acq-kits/verify-report.sh (module header) - scripts/tests/test_verify_contract.py (module docstring + 2 inline comments + 1 docstring) Comment-only / allowlist-entry changes: no test logic, no shell logic, no shared-library behavior changed. Per the review's own standing offer ("if you respond with improvements that only land in comments... feel free to check the box that skips the require-review gate"), this should qualify to merge without a second review round-trip. Verification: - PYTHONPATH=scripts python3 -m pytest scripts/tests/: 420 passed (was failing 1/420 before the opencode classification fix, reproduced first). - python3 scripts/validate_repo.py: all validators pass. Co-authored-by: OpenCode Agent <[email protected]>
|
Addressed in `4f26eb4`. Two changes:
Flagging #2 explicitly because your standing offer was for comment-only fixes — this PR also has that one allowlist line, so I didn't want to assume it's covered and self-merge on a stretched reading. Your call whether that still qualifies or you'd rather take another look. CI is 7/7 green. `python3 scripts/validate_repo.py`: all validators pass. |
Replace text-presence assertions with a six-state executable matrix, require real library/verdict invocations, and trim the remaining investigation-history narration. Co-authored-by: OpenCode Agent <[email protected]>
Require the exact shared reporter source, exercise both missing-tool degraded paths, and keep contract-test comments in present-tense invariant form. Co-authored-by: OpenCode Agent <[email protected]>
Correct the copyable usage example and format the executable contract tests. Co-authored-by: OpenCode Agent <[email protected]>
mogul
left a comment
There was a problem hiding this comment.
One approval-blocking documentation issue remains.
integrations/isolation/acq-kits/README.md:87 says “Verify scripts share one verdict contract,” then presents exit codes 0, 1, and 3 as the meaning of scripts/verify. But scripts/tests/test_verify_contract.py explicitly classifies only git-ssh-sign and usai-provider as CONVERTED; seven kits remain UNCONVERTED and retain the old two-state verdict. A caller can therefore rely on this documented contract for a listed kit and receive an incorrect result.
Please scope this section to converted kits and make the staged migration status clear, or defer the general exit-code documentation until every kit uses the shared reporter. The implementation and contract tests otherwise look sound.
The exit-code table described a contract only two kits implement, so a caller could trust a 0 from an unconverted kit whose 0 does not distinguish every-check-passed from a check that could not run. Document the staged split explicitly, state what an unconverted 0 does and does not establish, and derive both lists from the test suite so a README-only edit, a conversion, or a reversion each fail. Co-authored-by: OpenCode Agent <[email protected]>
|
Addressed in What changed
On that last point I went looking for the concrete mechanism rather than asserting it generically. These scripts report an unmet precondition with a bare message that increments no counter, then exit The split is now enforced, not just describedProse that must track a migration will drift, so Three details worth flagging, because a guard like this is easy to write so that it passes vacuously:
The converted lookup still hard-fails on a missing section, since that one is not supposed to disappear. VerificationThe five No implementation or contract-test behavior changed — only the documentation scope plus the new guard. AI-assisted (OpenCode). Advisory; requires your review. |
|
@mogul — branch updated to current One thing I can't clear myself: six inline review threads from your 2026-10-02 pass are still open. All six are I'd rather not resolve another reviewer's threads unilaterally: resolving them is you agreeing the point is closed, and me marking them closed would assert that on your behalf. Could you take a pass and resolve the ones you're satisfied with, or re-raise any that are not actually addressed? Also worth flagging for whoever approves: this PR touches Note the ruleset also has Verification at
AI-assisted (OpenCode), advisory — a human should confirm before this merges. |
🤖 I have created a release *beep* *boop* --- ## [1.14.0](v1.13.0...v1.14.0) (2026-10-19) ### Features * **paseo:** bump pinned Paseo CLI to 0.12.0 ([#504](#504)) ([0c5258e](0c5258e)) ### Bug Fixes * **acq-kits:** bump opencode kit to OpenCode 2.0.30 ([#499](#499)) ([c9d2281](c9d2281)) * **acq-kits:** give verify scripts a "could not check" verdict state ([#460](#460)) ([306906a](306906a)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
## Summary Moves `PATTERNS_KIT_REF` from v1.12.0 (`56d1f5f`) to agentic-coding-patterns v1.15.0 (`f3c6acb`), so the built-in kit bundle picks up: - **opencode kit → OpenCode 2.0.30** (GSA-TTS/agentic-coding-patterns#499). OpenCode 2.0.0–2.0.29 ignore config `instructions` (anomalyco/opencode#51341, fixed in 2.0.30), so a sandbox on an image without a baked OpenCode (`devenv-shell`, or the `devenv-shell` default proposed in #584) currently runs the bundled 2.0.22 and silently drops `instructions`-based conventions. - **paseo kit → Paseo 0.12.0** (GSA-TTS/agentic-coding-patterns#504): fixes for OpenCode v2 under Paseo (daemon OOM on long v2 streams, multi-select answers, HTTP 500s instead of tool errors) and the host-sleep daemon-status fix. - **Kit verify scripts gain a "could not check" verdict** (GSA-TTS/agentic-coding-patterns#460); it only affects kit verify output. Also v1.13.0: usai-provider's host-side catalog normalizer (#436), the paseo identity-gate script (#453), and the team-kit and personal-kit templates under `acq-kits/examples/`. None changes what acq applies by default beyond the two kits above. Also v1.15.0: refreshed usai-provider model catalog (GSA-TTS/agentic-coding-patterns#519, GSA-TTS/agentic-coding-patterns#484), the OpenDesign opt-in kit (GSA-TTS/agentic-coding-patterns#438), and the provider-neutral input contract (GSA-TTS/agentic-coding-patterns#518). acq's provider-facts loader reads `provider-facts/usai.env`, which v1.15.0 does not change. The doc examples and Bats assertions that spell the pin move with it. KNOWN_FAILURE_MODES §9 now names 2.0.30 as the version the kit installs and notes its 15-minute default `chunkTimeout` (the timeout message and retry limits are unchanged from 2.0.22). ## Verification On the v1.15.0 pin: - Focused Bats: `135-configure`, `95-kit-ref-persistence`, `36-agent-catalog`, `50-kit-list-completeness` (63/63) - markdownlint on the changed docs On the earlier v1.14.0 pin (not re-run for v1.15.0): - `npm run lint:md` - `RUN_ACQ=1 scripts/verify-backends --only msb` on macOS, msb 0.7.8: 16 passed, 1 skipped (playbook fetch, no `gh` login), run with `TMPDIR` set to its resolved `/private/var/...` path to get past an unrelated create failure that also hits `main` (#594) - Downstream, with this pin on msb 0.7.8 and `ACQ_IMAGE=devenv-shell:1.0.0`: a team kit's end-to-end verify passed 71/71 (OpenCode 2.0.30 runs and loads `instructions`); with the paseo kit added, Paseo 0.12.0 drove an OpenCode 2.0.30 agent that received the team `instructions`, and its sessions survived a backup, `acq rm`, recreate and restore ## Rollback Revert this PR to restore the v1.12.0 bundle.
All seven kit verify scripts ended with the same two-state verdict:
That answers "did anything fail?" and cannot answer "did we actually check anything?". Two consequences, both measured rather than inferred.
1. A run where nothing executed reported success
Reproduced in isolation:
2. A skipped check was indistinguishable from a passing one
Across the seven scripts there are 29 skip/NOTE sites and 9 warn sites that increment no counter.
Worst case:
git-ssh-signskips its end-to-end signed-commit check — the kit's entire purpose — whenever no key is in the forwarded SSH agent, and still reported "All checks passed."And
openchamber/paseodo defineskip()andwarn(), but they're cosmeticprintfwith no counter:So
paseocan print 19 yellow SKIPs and still claim the offline gate passed.This is the ninth instance of this defect class across this family of repos (ENV-1, SEC-6, DX-7/8/9, SEC-12/13/17, DEP-4 being the prior ones), which is why the fix is a shared contract rather than seven local patches.
The contract
integrations/isolation/acq-kits/verify-report.sh— five states, and a verdict that is a function of a coverage record rather than a failure counter:okbad1unver3skipwarn3is distinct from1so a caller can tell "we looked and found problems" from "we could not look." Theunver/skipsplit is the load-bearing part — collapsing them is how a verify script ends up claiming coverage it does not have.Staged on purpose: two kits here
The two smallest (
git-ssh-sign,usai-provider), so the contract is reviewable in isolation before a bulk edit. Remaining:openchamber(641 lines, 3 gates),paseo(864, 3),pi-coding-agent(337, 3),agentic-coding-playbook(167),zscaler-ca-certificate(167) — listed in the test'sUNCONVERTEDset, which must reach empty.Converted sites:
unver; no forwarded key →unver(the kit's only end-to-end assertion).unver; API probe empty →unver. That last one matters:in_sbx()swallows the exit status, so an empty result means either "no curl in the guest" or "sbx execfailed" — indistinguishable, and never a pass.Enforcement, because nothing in CI runs these scripts
They need a live sandbox CI cannot create, and this repo has no shellcheck hook — so 2,400 lines of shell are currently linted by nothing.
scripts/tests/test_verify_contract.py(12 tests) reads them as text and asserts:verify_reset, and don't shadow its reporters with no-op locals (which would pass every other test while behaving exactly as before);echo "SKIP"remains in a converted kit;UNCONVERTEDdescribes reality in both directions — a kit converted without being delisted fails, and so does a listed kit that reverted.Every helper asserts its input set is non-empty first, so the suite cannot pass vacuously over a glob that stopped matching. That would be the same defect wearing a different hat.
A near-miss worth recording
The library was first written to
acq-kits/lib/..gitignore:13carries the stock Python build-artifact block, whoselib/rule silently excluded it:Committing would have shipped two kits sourcing a file git never tracked — every verify run dying on "No such file or directory." Caught because
git statuslisted the two modified kits and the new test but not the library. It now sits at the acq-kits root besidevalidate-kits.py(the existing precedent for shared assets there), and the reason is recorded in the test file so nobody re-adds alib/.Verification
I exercised the degraded paths of both the library and the tests, because a guard that only passes when things are fine is indistinguishable from a guard that always passes.
Not in this PR, deliberately
General shell linting. These scripts have never been shellcheck'd, and adding a hook would surface unrelated findings across 2,400 lines — that belongs in its own change. This one closes the verdict-honesty gap only.
acq-kits/README.mdgains a short section documenting what each exit code means, since the exit contract is now something a caller can rely on.AI-assisted (OpenCode), prepared on operator instruction. Human review and merge required.