Skip to content

[architect] unit-test trigger scope is narrower than the workflows it guards: 4 release reusables can regress with pytest never scheduled #522

Description

@hivecommons-hive

Architecture Finding

Type: interface-violation / tech-debt (gate scope narrower than guarded surface)
Affected area: .github/workflows/unit-tests.yml on.push.paths / on.pull_request.paths

Four of the release-critical reusable workflows are guarded by pytest drift
gates, but editing one of them alone never runs the gate that guards it,
because unit-tests.yml filters on paths: and those four files are not in the
filter.

Guarded workflow files (derived from what tests/*.py actually reads):

workflow guarded by in unit-tests.yml paths:?
reusable-build.yml test_check_consumer_contract.py, test_validate_thin_caller.py ✅
reusable-renovate.yml test_reusable_renovate_scope.py ✅
reusable-release-gate.yml test_release_gate_single_source.py, test_trust_policy_single_source.py, test_release_registry_normalization.py ❌ blind
reusable-promote-squash.yml test_promote_gate_order.py, test_release_registry_normalization.py ❌ blind
reusable-execute-release.yml test_trust_policy_single_source.py, test_release_registry_normalization.py ❌ blind
reusable-release.yml test_trust_policy_single_source.py ❌ blind

The guarded set and the trigger set are two separate hand-maintained lists with
nothing keeping them in agreement. reusable-build.yml and
reusable-renovate.yml were added to paths: when their gates landed; the four
release workflows were not.

Impact

tests/test_release_gate_single_source.py exists precisely because "nothing
invokes the scripts at runtime, so without this gate the BATS suite can stay
green while the shipped gate regresses"
(module docstring). The same reasoning
applies one level up: without the trigger entry, pytest itself can stay green
— by never running — while the shipped release gate regresses.

Reproduced on main (4d3b97e). Injecting a one-character drift into the
resolve step body of reusable-release-gate.yml:

-          DIGEST="$(skopeo inspect --format '{{.Digest}}' ...
+          DIGEST="$(skopeo  inspect --format '{{.Digest}}' ...

makes test_inline_step_matches_extracted_script[resolve] fail locally — the
gate works. But that diff touches only .github/workflows/reusable-release-gate.yml,
which matches no entry in either paths: list, so the Unit Tests workflow is
never scheduled and the PR shows no failing check.

The blind surface is the digest-resolution / cosign-verification / promotion-
ordering logic — the supply-chain trust boundary for every consumer repo that
calls these reusables.

Recommendation

Two parts; the second is not pushable by this agent (see below).

Part 1 — machine-enforce the invariant (PR follows). Add
tests/test_unit_test_gate_scope.py, which derives the guarded set by scanning
tests/*.py for workflow filenames that exist on disk and asserts each one is
matched by unit-tests.yml's paths: filter on both push and
pull_request. This makes the trigger scope a derived consequence of the suite
rather than a parallel list: adding a gate over a new workflow forces the
trigger entry in the same change. It also asserts unit-tests.yml triggers on
itself, so narrowing the filter re-runs this gate.

Part 2 — widen the trigger filter. Apply verbatim to
.github/workflows/unit-tests.yml, in both the on.push.paths and
on.pull_request.paths lists (the two lists are identical today; keep them so):

       - ".github/workflows/reusable-build.yml"
+      - ".github/workflows/reusable-execute-release.yml"
+      - ".github/workflows/reusable-promote-squash.yml"
+      - ".github/workflows/reusable-release-gate.yml"
+      - ".github/workflows/reusable-release.yml"
       - ".github/workflows/reusable-renovate.yml"
       - ".github/workflows/unit-tests.yml"

Verified locally: with Part 2 applied, tests/test_unit_test_gate_scope.py
passes 5/5 and the full suite is 388 passed. Without it, the full suite is
383 passed, 2 failed — and the only failures are the two new assertions
naming exactly the four blind workflows.

Why Part 2 is filed here and not in the PR

This agent runs at a token tier without the GitHub App Workflows
permission, so any push whose diff touches .github/workflows/** is rejected
server-side. Part 2 must be applied by a human maintainer or an agent with
workflow-write scope. Part 1's PR is therefore red until Part 2 lands — the
two new assertions fail by design, reporting the four unguarded workflows. They
go green the moment the diff above is applied. Please land both together.


Filed by architect agent (ACMM L5 — hold-gated mode)

— hive: agent=architect backend=copilot model=claude-opus-5

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    agent/architectFiled or owned by the architect agent.architectureStructural or interface design work.hive/already-doneHive contributor found this issue already resolved; remove if work remainstech-debtAccumulated debt to pay down.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions