Skip to content

[Bug]: a Markdown heading inside the Plan Approval section silently ends it — the [Approval Fingerprint] / [Answer] below are ignored and the error blames the fingerprint #1022

Description

@leeyoseph-dev

Summary

latestPlanApproval in aidlc-testing-posture.ts delimits the Plan Approval section by Markdown headings only, and any heading — at any level — closes it. A heading written inside that section (e.g. an explanatory ### Why re-approval is needed) makes the parser drop the [Approval Fingerprint]: and/or [Answer]: lines below it. The resulting failure blames the fingerprint or the answer and never mentions the heading, so the actual cause is effectively invisible.

Nothing in code-generation.md Step 3 or in stage-protocol.md states that the Plan Approval section is heading-delimited, so authoring a heading there is not a documented violation.

Verified against v2 @ a277af2 (AIDLC_VERSION = 2.7.1).

Mechanism (read from source)

Line numbers are from the shipped .claude/tools/ copy at 2.7.1; the functions are named so they can be located if the numbers drift.

Location What it does
aidlc-testing-posture.ts:150 MARKDOWN_HEADING_RE /^(#{1,6})[ \t]+(.+?)[ \t]*#*[ \t]*$/ — matches every heading level
aidlc-testing-posture.ts:855-902 latestPlanApproval the only reader of the section
same, lines 866-881 on every heading line it reassigns inPlanApproval = isPlanApprovalLabel(headingText…). There is no "still inside the section" branch, and no comparison of heading levels — a ### under a ## Plan Approval sets inPlanApproval = false.
same, line 891 if (!inPlanApproval) continue; — from that point on, ANSWER_TAG_RE (line 151) and FINGERPRINT_TAG_RE (lines 152-153) are never evaluated
aidlc-testing-posture.ts:922 questionsFileApprovalFingerprint:1055 codeGenerationApprovalArtifacts (recordedFingerprint, line 1110) → :1533 codeGenerationPlanApprovalQuestionEvidence consumer chain
same, lines 1568-1572 if (artifacts.recordedFingerprint !== artifacts.expectedFingerprint) throw new Error("Plan Approval fingerprint does not match the active intent, target, directive epoch, plan, instructions, and Testing Contract")
same, lines 1573-1578 throw new Error("Plan Approval questions file must contain exactly [Answer]: …")
aidlc-log.ts:262-268 (decision), aidlc-log.ts:614-618 (answer) both call codeGenerationPlanApprovalQuestionEvidence, so both prescribed commands hit this

So a heading in the section makes recordedFingerprint come back null, which fails the equality at line 1568 and produces the fingerprint error — while the [Approval Fingerprint]: line is present in the file and equals what aidlc-testing-posture.ts fingerprint just printed.

Two different symptoms, depending on where the heading sits

Measured by calling the exported readers directly (script below):

Questions file content questionsFileApprovalFingerprint questionsFileApproved questionsFileHasPendingPlanApproval
## Plan Approval + fingerprint + [Answer]: A. Approve Plan fingerprint true false
same, with ### Why re-approval is needed above the fingerprint null false false
same, with #### Options between the fingerprint and the answer fingerprint false false
same, with the heading replaced by **Why re-approval is needed** fingerprint true false
same, with a > blockquote note fingerprint true false
same, with a - bullet note fingerprint true false

Row 2 fails at line 1568 (fingerprint error). Row 3 fails at line 1573 (must contain exactly [Answer]: Approve Plan) even though the answer is literally in the file. Only headings do this; blockquotes and lists are fine.

Isolated reproduction (no workflow state required)

// probe.ts — run with: bun run probe.ts
import {
  questionsFileApproved,
  questionsFileApprovalFingerprint,
} from "./.claude/tools/aidlc-testing-posture.ts";

const FP = "sha256:" + "a".repeat(64);
const tail = `\n[Approval Fingerprint]: ${FP}\n\nA. Approve Plan\nB. Request Changes\n\n[Answer]: A. Approve Plan\n`;

const clean   = `## Plan Approval\n\nApprove this exact Code Generation plan?\n${tail}`;
const heading = `## Plan Approval\n\nApprove this exact Code Generation plan?\n\n### Why re-approval is needed\n\nThe plan changed after the previous approval.\n${tail}`;
const bold    = `## Plan Approval\n\nApprove this exact Code Generation plan?\n\n**Why re-approval is needed**\n\nThe plan changed after the previous approval.\n${tail}`;

for (const [name, body] of [["clean", clean], ["heading", heading], ["bold", bold]] as const) {
  console.log(name, {
    fingerprint: questionsFileApprovalFingerprint(body),
    approved: questionsFileApproved(body),
  });
}

Output on a277af2 / bun 1.4.0:

clean   { fingerprint: "sha256:aaaa…", approved: true }
heading { fingerprint: null,           approved: false }
bold    { fingerprint: "sha256:aaaa…", approved: true }

In-workflow reproduction

  1. Run any scope to CONSTRUCTION code-generation Step 3, up to the point where the stage file says to write the hash "into the Plan Approval section".
  2. In <record>/…/code-generation/code-generation-questions.md, add one explanatory Markdown heading inside the Plan Approval section, above [Approval Fingerprint]: — for example ### Why re-approval is needed.
  3. Run the prescribed bun .claude/tools/aidlc-log.ts decision --stage code-generation --checkpoint plan-approval --questions-file … --session … --decision … --options "Approve Plan,Request Changes" --unit ….
  4. It fails with Plan Approval fingerprint does not match the active intent, target, directive epoch, plan, instructions, and Testing Contract, although the fingerprint line is present and matches the value aidlc-testing-posture.ts fingerprint printed a moment earlier.
  5. Change nothing except the heading — make it a bold paragraph, **Why re-approval is needed** — and the same command succeeds.

What we observed vs. what we read. Steps 4 and 5 are what we hit in a real run: a heading added to explain a re-approval broke the checkpoint, and converting it to a bold paragraph fixed it. The causal chain in the Mechanism section, the two-symptom table, and the isolated script are from reading and executing the shipped sources afterwards — not inferred from the run.

Why this is expensive to diagnose

Internal inconsistency worth noting

aidlc-unit.ts reads the same questions file with whole-file, non-section-scoped regexes: /^\[Approval Fingerprint\]:\s*(sha256:[0-9a-f]{64})\s*$/m (line 1617) and /^\[Answer\]:\s*A\.\s*Approve Plan\s*$/m (line 1664). Those match regardless of the heading. So one reader can consider the questions file well-formed while latestPlanApproval sees an empty section — the two readers do not agree on what "the Plan Approval section" is.

Suggested directions (not a patch)

  1. Do not let a nested heading close the section. Track the opening heading's level and close only on a heading of the same or shallower level (or only on another top-level question heading). A ### under ## Plan Approval would then stay inside, which is what a Markdown author reasonably expects.
  2. Make the failure name its cause. When found is true but fingerprint / answer is null, a whole-file scan can cheaply tell whether the tag exists further down; if it does, say so — e.g. Plan Approval fingerprint found outside the Plan Approval section: the heading on line N ends the section. This alone converts the failure from an unguided hunt into a one-line fix, and it also disambiguates this cause from 2.6.124: code-generation plan-approval livelock — /aidlc --resume resets the approval epoch it demands #975 / [Bug]: Code Generation pre-planning source floor can become permanently unsatisfiable #981 / code-generation: the Plan Approval fingerprint is unsatisfiable after any compliant execute+review cycle #987, which share the current wording.
  3. At minimum, document the constraint in code-generation.md Step 3 and in the questions-file rules of stage-protocol.md, and consider aligning aidlc-unit.ts with whichever scoping rule is chosen.

(1) and (2) are complementary; (2) is valuable independently of whether (1) is adopted.

Environment

  • v2 @ a277af2, AIDLC_VERSION = 2.7.1, Claude Code harness, dist/claude
  • macOS (darwin 25.5.0), bun 1.4.0
  • Parser behaviour measured by calling questionsFileApproved / questionsFileApprovalFingerprint / questionsFileHasPendingPlanApproval directly; the wiring from those readers to the two error messages was read from source, not executed end to end.

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions