Skip to content

docs(spec-loop): add spec-gap staleness verification spec - #926

Merged
potiuk merged 2 commits into
apache:mainfrom
justinmclean:spec-gap-staleness
Aug 1, 2026
Merged

docs(spec-loop): add spec-gap staleness verification spec#926
potiuk merged 2 commits into
apache:mainfrom
justinmclean:spec-gap-staleness

Conversation

@justinmclean

Copy link
Copy Markdown
Member

Summary

Adds tools/spec-loop/specs/spec-gap-staleness.md, a proposed spec for
stopping a ## Known gaps bullet from outliving the gap it describes.

Spec-only. Nothing is implemented by this PR.

Why now

Nine Known-gap bullets across eight specs currently describe capabilities
that have already shipped. Examples verified against main:

  • adapters.md says adapter authoring smoke validation is missing;
    validate_adapter_authoring is called at __init__.py:3710.
  • privacy-llm-gate.md says branch-name confidentiality validation is
    missing; validate_branch_name_confidentiality is called twice.
  • issue-management-family.md and reviewer-routing.md both say the
    reviewer-routing row is missing from docs/modes.md; it is at
    docs/modes.md:85.
  • release-management-lifecycle.md says the audit record schema is
    prose-only; skills/release-audit-report/audit-record-schema.md exists.

This matters beyond tidiness: the plan beat reads Known gaps to select
work, so a stale bullet sends the loop at finished work. There is also a
straight contradiction between triage-mode.md (repo-health is "a
five-skill family") and repo-health-family.md ("six skills deep").

What the spec proposes

  1. An evidence rule on the plan and update beats. A Known-gap bullet
    may only become a work item if the beat cites a call site, current
    file content, or a counted value. The presence of a name is
    explicitly not admissible, since a function defined but never called
    satisfies a name match while the gap stays open.
  2. Optional gap-closes-when: predicates on gap bullets, with a
    three-verb vocabulary (symbol-called, path-exists,
    skill-count) that covers all nine observed cases. A satisfied or
    malformed predicate is HARD; an unannotated bullet is SOFT, so
    nothing breaks on the day this lands.
  3. validate_cross_spec_counts, HARD. Counted claims checked
    against live family: frontmatter, with cross-spec disagreement a
    violation regardless of which side is right.

Bullets whose gap is inherently narrative declare
gap-closes-when: none (narrative). That is the correct annotation for
the eleven "experimental, no adopter pilot has run" bullets, whose truth
depends on facts outside the repository and which should never be
machine-checked.

Reviewer questions

  • validate_cross_spec_counts is proposed HARD and the unannotated-bullet
    advisory SOFT. SOFT is what allowed nine bullets to accumulate, so
    there is a case for making it HARD, at the cost of a blocking backfill
    across all 25 specs.

Generated-by: Claude (claude-opus-5)

Type of change

  • Skill change (.claude/skills/<name>/) — eval fixtures updated below
  • Tool / bridge contract (tools/<system>/*.md)
  • Python package (tools/*/ with pyproject.toml)
  • Groovy reference impl
  • Cross-cutting (RFC, AGENTS.md, sandbox, privacy-LLM)
  • Documentation (docs/, README.md, CONTRIBUTING.md)
  • Project template (projects/_template/)
  • CI / dev loop (prek, workflows, validators)
  • Other:

Test plan

  • prek run --all-files passes
  • For Python packages touched: uv run pytest / ruff check / mypy passes
  • For Groovy bridges touched: command-line invocation tested end-to-end
  • For skill changes: eval suite passes for the affected skill
    (PYTHONPATH=tools/skill-evals/src python3 -m skill_evals.runner tools/skill-evals/evals/<skill>/)
  • For skill behaviour changes: a new or updated eval fixture is included in this PR
    (a regression test for the bug fixed / the behaviour added — see CONTRIBUTING.md)
  • Other:

@justinmclean justinmclean self-assigned this Jul 27, 2026

@potiuk potiuk left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The spec itself is good — clear problem statement, crisp acceptance criteria, and the "evidence must be a call site, current file content, or a counted value; the presence of a name is not admissible" rule is exactly the right shape for something a model has to apply consistently. One correction wanted before it lands, and one hunk that has since become redundant.

source: claims an implementation that does not exist

The frontmatter reads:

Implemented by tools/spec-loop/PROMPT_plan.md, tools/spec-loop/PROMPT_update.md, and new checks in tools/spec-validator.

None of that exists yet. I checked the tree:

  • no validate_gap_predicates or validate_cross_spec_counts anywhere under tools/
  • neither PROMPT_plan.md nor PROMPT_update.md mentions an evidence rule

status: proposed is correct and says so. The source: line contradicts it, and it does so in the exact tense the shipped specs use — the convention across the tree is:

status wording
experimental (code exists) "Implemented in tools/gmail/, tools/jira/, …"
proposed (code does not) maintainer-education.md: "Referenced but not yet delivered by docs/rfcs/RFC-AI-0004.md"

So a reader — or the plan beat, which reads these fields — sees a shipped-code phrasing on unshipped work.

This is worth fixing rather than waving through, because it is precisely the failure this spec exists to prevent. The document opens with "Stops a ## Known gaps bullet outliving the gap it describes" and its own source: asserts an implementation that has not been written. Same drift, one field up.

source: >
  Observed drift: nine "Known gaps" bullets across eight specs described
  capabilities that had already shipped, and the plan beat queued work
  from them. To be implemented by tools/spec-loop/PROMPT_plan.md,
  tools/spec-loop/PROMPT_update.md, and new checks in
  tools/spec-validator.

For what it is worth, the observed drift is real and I ran into it from the other side today: #1034 corrected one of exactly those bullets — "Release audit record schema is prose-only" — where the schema and its eval fixtures had shipped months earlier. So the motivating case is well grounded.

The uv.lock hunk is now redundant

The uv.lock change here is byte-identical to the one in #965 — same eight mypy specifier lines, same blob hashes. #965 has now merged, so this hunk is already on main; rebasing will make it disappear and leave this PR as the single spec file it wants to be.

Worth doing before merge simply so the PR says what it is: uv.lock +8/-8 on a spec-only change is the kind of thing that makes a future reader wonder what a lockfile has to do with spec staleness.


This review was drafted by an AI-assisted tool and
confirmed by an Apache Magpie maintainer. The findings
below are observations, not blockers; an Apache Magpie
maintainer — a real person — will take the next look at the
PR. If you think a finding is mis-applied, please reply on
the PR and a maintainer will weigh in.

More on how Apache Magpie handles maintainer review:
CONTRIBUTING.md § Opening a pull request.

Two review fixes on the spec text. The uv.lock change in the parent
commit is left alone: it corrects genuine stale-lock drift (the lock at
the base commit had 26 mypy specifiers at >=2.1.0 against 18 in the
pyprojects; regenerating brings both to 18/9).

- Correct acceptance criterion 7. The discrepancy is three-way, not
  five-versus-six: triage-mode.md claims five skills,
  repo-health-family.md claims six, and seven carry family: repo-health
  in frontmatter. The seventh, audit-finding-fix, is in neither prose
  list — plausibly deliberate, since it fixes findings rather than
  producing them. The check must report all three numbers and name the
  disputed skill rather than presume frontmatter is authoritative.

- Note that validate_cross_spec_counts compares against the live count
  independently, so the case where every spec agrees and all are stale
  is caught too. Agreement reads as confirmation, which makes it the
  more dangerous shape.

- Record the landing order in Known gaps. The check is HARD and
  criterion 7 requires it to fire on a violation present in the tree
  today, so landing it before the counts are reconciled turns main red.
  Only the SOFT advisory is safe to land first.

Generated-by: Claude Code (Opus 5)

@potiuk potiuk left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving with two fixups pushed to the branch.

Spec text. Acceptance criterion 7 called the repo-health discrepancy "five-versus-six". It is three-way: triage-mode.md claims five, repo-health-family.md claims six, and seven skills carry family: repo-health. The seventh, audit-finding-fix, appears in neither prose list — plausibly deliberate, since it fixes findings rather than producing them. So the check has to report all three numbers and name the disputed skill rather than presume frontmatter is authoritative; a HARD check that presumes an answer gets argued with instead of fixed. Also noted that comparing against the live count catches the case where every spec agrees and all are stale, which is the more dangerous shape since agreement reads as confirmation.

Landing order, recorded in Known gaps. validate_cross_spec_counts is HARD and criterion 7 requires it to fire on a violation present in the tree today, so landing it before the counts are reconciled turns main red. Only the SOFT advisory is safe to land first.

On uv.lock — I was wrong and have left your change alone. I first read it as churn in a spec-only PR and reverted it; the pre-commit hook regenerated it and the numbers explain why. At your base commit the lock had 26 mypy specifiers at >=2.1.0 against 18 in the pyprojects; regenerating brings both to 18/9. It was correcting genuine stale-lock drift, not adding noise.

The refuted-by-measurement note in Out of scope is the best part of this — recording that git-recency scoring does not separate the populations, with the numbers, is exactly what stops it being re-proposed.

@potiuk
potiuk merged commit bc54c92 into apache:main Aug 1, 2026
39 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants