Skip to content

devcontract: a blank status: reads as "none", so it is not in RECONCILABLE_FROM #358

Description

@pbean

Surfaced while validating the reviewer findings on #355. Latent — no reported failure — but the code and the comment next to it disagree, so one of them is wrong.

The disagreement

devcontract.RECONCILABLE_FROM is the allowlist of frontmatter statuses a half-finalized generic spec may be reconciled from when its prose ## Auto Run Result says done. Its comment says:

"" covers a blank or missing frontmatter status:reset_spec_status fills/inserts the line in that case.

A missing status: does read as "". A blank one does not. YAML parses status: with no value as None, and frontmatter.status_of normalizes with str(fm.get("status", "")), so None becomes the string "none":

>>> fm = read_frontmatter(spec)          # spec contains a bare `status:` line
{'status': None, 'baseline_revision': 'base'}
>>> status_of(fm)
'none'
>>> status_of(fm) in devcontract.RECONCILABLE_FROM
False

So a spec with a blank status: — which the comment says is covered — falls into the "unknown custom token" arm and is left untouched, and the reconcile the comment promises never runs.

Why it matters

"none" is not a token anything in the project writes, so nothing distinguishes it from a genuine unknown custom status a skill set on purpose. The allowlist's whole design point is "never override a status the skill set deliberately" — and here it declines to reconcile a spec that carries no deliberate status at all.

A bmad-dev-auto template can leave the status blank; devcontract._FM_STATUS_RE says so in its own comment ("the value is * (not +) so a present-but-empty status (status: / status: "") is matched and filled — a bmad-dev-auto template can leave it blank"). So the writer explicitly supports the shape the reader silently excludes.

Fix shape (needs a decision)

Either normalize None to "" in status_of — which is the reading every gate goes through, so it touches more than this allowlist and needs its own characterization — or add "none" to RECONCILABLE_FROM, which is narrower but leaves a stringified None as a load-bearing token, which is worse. My read is the first, but the blast radius wants measuring before anyone writes it.

Note status_of's docstring calls itself "the single point all spec-frontmatter status gates read through", so a change there is deliberately wide by design.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingneeds-designAwaiting a maintainer design decision before code

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions