Skip to content

ci(roadmap): fail validation when a change adds required work to a completed epic #161

Closed

Description

@mashkovd

Reframed on 2026-10-16 as one implementable CI task. Verified against main at 04a2816: nothing of this exists yet. roadmap/scripts/validate.py, health.py and completion.py never read lifecycle; .github/workflows/roadmap-validate.yml checks out the PR head only (no fetch-depth, no merge base) and runs validate.py roadmap/epics on the final tree. The trigger case #152 was closed unmerged and split into #162 (open, no FinOps change). roadmap/epics/agent-finops.yaml is still lifecycle: completed.

Invariant

A roadmap change must not add a new required work item to an epic whose lifecycle is completed while leaving that lifecycle completed. To extend completed work, the same change must add the item as optional or move the epic back to active (or paused).

Work

1. Diff-aware check script

Add roadmap/scripts/lifecycle_guard.py (name indicative) that takes a base ref (or a base directory of manifests) and the head manifests and, for every epic whose head lifecycle is completed:

  • fails if the set of required: true work-item ids grew relative to base;
  • fails if an existing item flipped from required: false to required: true;
  • fails for a manifest that is new in head, born completed, and holds a required item that is not bound and closed;
  • passes if the same change moves the lifecycle from completed to active or paused (that is the explicit, reviewed reopening);
  • treats an unreadable base as an error, never a pass: a missing merge base, a shallow checkout without the base commit, an unreadable or malformed base manifest each exit non-zero with a distinct message. Per AGENTS.md "could not observe" is never "observed absent".

A manifest that is deleted in head is not a violation of this check.

2. Wire it into roadmap-validate.yml

  • On pull_request: check out with enough history to resolve the merge base (fetch-depth: 0 or a targeted fetch of github.event.pull_request.base.sha), compute git merge-base, and run the guard between that commit and the head tree. A failed merge-base lookup fails the job.
  • On push to main: run the guard against the parent commit (github.event.before) or skip with an explicit "no base" notice. Do not silently pass.
  • Keep permissions: {}.

3. Tests

  • Unit tests in roadmap/tests/test_lifecycle_guard.py using in-memory or fixture manifests for every bullet in section 1, including the error cases for an unreadable base.
  • Add the mutation to roadmap/tests/mutations.py so the existing mutation harness exercises it.
  • Prove it both ways, as AGENTS.md requires: green on the current roadmap/epics against its own parent, red on a branch that reapplies the feat(roadmap): add adaptive agent runtime assurance #152 FinOps hunk (add a required trajectory-economics item to agent-finops.yaml while it stays completed), and red again on that mutation when the guard step is removed from the workflow. Paste the three run links in the PR.

4. Document

One paragraph in roadmap/README.md under the validation section: what the guard checks, how to reopen a completed epic legitimately.

Out of scope (separate issue if wanted)

A state-based check that flags an epic whose manifest says completed while completion.py computes incomplete. It catches drift without a manifest diff (a required issue reopened on GitHub) and is complementary, not a substitute. Open it as its own issue if the implementer thinks it is cheap; do not fold it into this PR.

Acceptance

Refs #152, #155, #162

History

Original body (2026-10-16, before reframing):

Invariant

A roadmap change MUST NOT add a new required work item to an epic whose lifecycle is completed while leaving that lifecycle completed. To extend completed work, the same governed change must either add the item as optional or explicitly transition the epic back to active.

Why

#152 adds a required trajectory-economics item to agent-finops.yaml. That epic became completed in #155 after #152 was opened. The merge is textually clean, because #155 touched only the lifecycle line, and on the rebased branch every current check passes: validate.py over all 18 epics and the 318 roadmap tests. The result would be a manifest that says completed while holding an open, unbound required item. Nothing reports that:

  • lifecycle appears only in the schema enum and in publish.py, which copies it into the publication.
  • validate.py, completion.py, health.py, reconcile.py, plan.py and ready.py never read it.
  • completion.py would compute incomplete, and no check compares that with lifecycle: completed. The reconciler would only note BindingUnbound.

Both Claude approvals on #152 were given against a base where FinOps was still active, so no reviewer saw the conflict either.

Shape of the check

It must be diff-aware against the base. A validator that reads only the final YAML cannot tell "a required item was added after completion" from "this item was always there". Proposed:

  • In roadmap-validate.yml on pull_request, compare every manifest under roadmap/epics/ against the merge base.
  • For each epic whose head lifecycle is completed: fail if the set of required: true work-item ids grew relative to base, or if an existing item flipped from required: false to true. The same applies to a new manifest that is born completed with an item that is not bound and closed.
  • Pass if the same change moves the lifecycle from completed to active or paused. That is the explicit, reviewed reopening.

Possible second, state-based check

As a separate check: health or publication flags an epic whose manifest says completed while the computed completion is not complete. This catches drift that arrives without a manifest diff, such as a required issue reopened on GitHub. It is complementary to the diff check, not a substitute.

Acceptance

  • The guard fails on feat(roadmap): add adaptive agent runtime assurance #152 as it is today (the deliberate mutation) and passes on the split without the FinOps commit.
  • Per AGENTS.md, it is proven both ways: green on a converged input, red on the mutation, and red again when the fix is reverted.
  • An unreadable base, such as a shallow checkout or a missing merge base, is a failed check, never a pass.

Refs #152, #155

Activity

  1. changed the title [-]roadmap: fail CI when a change adds required work to a completed epic[/-] [+]ci(roadmap): fail validation when a change adds required work to a completed epic[/+] on Oct 15, 2026
  2. mctl-agents commented on Oct 15, 2026

    @mctl-agents
    Contributor

    mctl-agents issue-investigator has analyzed this issue and created a proposal:

    https://github.com/mctlhq/mctl-gitops/tree/main/platform-gitops/agents-state/.github/proposals/issue-161-ci-roadmap-fail-validation-when-a-change/

    Status: proposed — pending human approval. Review requirements.md, design.md and tasks.md, then approve: signal this issue's DevLoopWorkflow (mctl-api POST /api/v1/agents/dev-loop/dev-loop-mctlhq-.github-161/approve, or python -m orchestrator.temporal.cli approve dev-loop-mctlhq-.github-161 --approver <your-identity>) — the workflow flips .status.yaml to accepted via the mctl-agents-approve operation and runs the Tier 2 implementer. If no DevLoopWorkflow is running for this issue (pre-Temporal proposal), run the mctl-agents-approve operation directly with service=.github slug=issue-161-ci-roadmap-fail-validation-when-a-change.

    The approver is recorded in .status.yaml and in the gitops commit, and the implementer refuses a proposal whose control.requires_human_approval carries no named approver — so prefer the mctl-api endpoint, which takes the approver from the authenticated caller rather than from what the caller types.

  3. added a commit that references this issue on Oct 15, 2026
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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions