Skip to content

SD004 false positive: a2ml files in canonical location flagged as 'wrong location' #426

Closed

Description

@hyperpolymath

Symptom

Hypatia's SD004 rule flags all .machine_readable/*.a2ml files as "6a2ml file outside canonical location -- must be in .machine_readable/6a2/".

Reproduces on every repo whose A2ML files live at .machine_readable/ directly. Recent example: gitbot-fleet PR #249 hypatia scan — 6 critical SD004 findings, all false positives, mirrored by 6 high CSA001 alerts.

Root cause

The rule enforces the retired .machine_readable/6a2/ subdir layout. Per the current standards CLAUDE.md and per hypatia's own CLAUDE.md:

"the canonical seven A2ML files (`STATE`, `META`, `ECOSYSTEM`, `AGENTIC`, `NEUROSYM`, `PLAYBOOK`, `ANCHOR`) live directly under `.machine_readable/`, per the `A2ML-REPO-TEMPLATE` in `hyperpolymath/standards`. (Earlier versions of this CLAUDE.md referenced a `.machine_readable/6scm/` subdir; that layout has been retired.)"

So the rule is enforcing the old layout that the standards explicitly retired.

Blast radius

Every estate repo with A2ML files at the canonical .machine_readable/ location gets 6 critical + ~6 high false-positive findings on every Hypatia scan. CSA002 aggregate counts get inflated as a result.

Fix direction

Update the SD004 rule definition to accept .machine_readable/<file>.a2ml as the canonical location (and reject the retired .machine_readable/6a2/<file>.a2ml, or leave both passing during a transition).

Related

  • gitbot-fleet#249 (where this was surfaced)
  • gitbot-fleet#254 (workflow-hygiene fix PR; SD004 listed as out-of-scope deferred to this issue)

Activity

  1. hyperpolymath commented on Jun 20, 2026

    @hyperpolymath
    OwnerAuthor

    This is blocked on the #485 canonical-layout decision, not a clean FP fix

    Investigated the fix and it turns out to be a policy call in disguise. The premise here — "flat .machine_readable/*.a2ml is canonical, so SD004 is wrong" — is contradicted by hypatia's own rules, which uniformly encode the opposite (6a2/-canonical) convention:

    Source Stance on .machine_readable/STATE.a2ml (flat)
    lib/rules/structural_drift.ex SD004 CRITICAL — "must be in .machine_readable/6a2/"
    SD001 "must be .a2ml in .machine_readable/6a2/"
    SD007 stale-ref patterns flat path is a stale reference → "should be .machine_readable/6a2/STATE.a2ml"
    SD023 doc comment "estate v2 convention puts STATE at .machine_readable/6a2/STATE.a2ml. Some repos retain a legacy top-level .machine_readable/STATE.a2ml."
    hypatia's own live .machine_readable/ canonical files kept in 6a2/ (so SD004 does not self-FP)

    …versus the premise this issue is built on:

    Source Stance
    gitbot-fleet/.claude/CLAUDE.md invariant #1 "seven canonical A2ML files live directly under .machine_readable/ … that [subdir] layout has been retired."

    So the estate genuinely holds two contradictory conventions, and hypatia's scanner encodes the 6a2/ side. SD004 only false-positives on flat-layout repos (gitbot-fleet et al.); it's correct for any repo following the 6a2/ convention. That makes this a concrete instance of #485 (audit, needs-owner: rsr-template ↔ standards divergence) — it can't be resolved correctly until the owner rules which layout is canonical.

    Why I didn't just patch the rule

    Flipping SD004 to accept flat-as-canonical would (a) take a side in the #485 dispute and (b) desync it from SD001/SD007/SD023 in the same file, which would then need reconciling too. That's an architectural decision, not an FP cleanup.

    Resolution paths for the owner (pick one under #485)

    1. Flat is canonical → in structural_drift.ex: drop Path.join([repo_path, ".machine_readable", a2ml_file]) from SD004's wrong_locations (keep repo-root flagged), update SD004's reason/target, and reconcile SD001's reason + SD007's @stale_reference_patterns + SD023's framing to match. Also retire the 6a2/ references in hypatia's own .claude/CLAUDE.md.
    2. 6a2/ is canonical → SD004 is already correct; the real fix is to move the flat-layout repos' files into .machine_readable/6a2/ and correct gitbot-fleet/.claude/CLAUDE.md invariant Review SCM files and security updates #1.
    3. Interim, policy-neutral (if the noise needs muting before the decision): accept files anywhere under .machine_readable/ (flat or 6a2/), flag only repo-root placement, and downgrade severity from :critical. Stops the 6-critical-per-scan bleed without declaring a winner.

    Leaving open and linking to #485; this shouldn't be actioned until that decision lands.


    Generated by Claude Code

  2. added
    bugSomething is broken or behaves incorrectly
    cx/L2Scoped — one repo, needs code reading + tests, no new design
    on Aug 3, 2026
  3. hyperpolymath commented on Aug 12, 2026

    @hyperpolymath
    OwnerAuthor

    RESOLVED via PR #701. Fixes applied to lib/rules/structural_drift.ex:

    1. Added ANCHOR.a2ml to @a2ml_state_files (completes the canonical seven: STATE, META, ECOSYSTEM, AGENTIC, NEUROSYM, PLAYBOOK, ANCHOR)

    2. Updated SD004 rule to accept canonical .machine_readable/ layout:

      • Changed wrong_locations to flag .machine_readable/6a2/ (retired) as wrong
      • Removed .machine_readable/ from wrong_locations (now canonical)
      • Updated reason: '6a2ml file in retired location -- must be in .machine_readable/ directly'
      • Updated target: '.machine_readable/{file}.a2ml' (not '.machine_readable/6a2/{file}.a2ml')
      • Updated docstring to reflect current standards
    3. Eliminates false positives for repos using canonical layout (e.g., gitbot-fleet#249 which had 6 critical SD004 + 6 high CSA001 false positives).

    Root cause: rule was enforcing retired .machine_readable/6a2/ layout while standards explicitly retired that layout in favor of .machine_readable/ directly.

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

    bugSomething is broken or behaves incorrectlycx/L2Scoped — one repo, needs code reading + tests, no new design

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions