Skip to content

fix: IsNull resolves the uppercase YAML nulls Null and NULL - #294

Open
ethanhawkes-gif wants to merge 1 commit into
Partnermedia:mainfrom
ethanhawkes-gif:fix/isnull-uppercase-yaml-nulls
Open

fix: IsNull resolves the uppercase YAML nulls Null and NULL#294
ethanhawkes-gif wants to merge 1 commit into
Partnermedia:mainfrom
ethanhawkes-gif:fix/isnull-uppercase-yaml-nulls

Conversation

@ethanhawkes-gif

Copy link
Copy Markdown

Summary

frontmatter.IsNull (and its byte-identical lint-side copy isNull) treated null as only ""/"null"/"~", missing the YAML 1.1 (!!null) / YAML 1.2 core-schema (§10.2.1.1) spellings Null and NULL — the same set the memory scalar parser (internal/core/memory/yaml.go) already resolves. Closes #290.

Why it matters

On the lifeboat pack path gvSupersededADRs unquotes superseded_by and then asks IsNull, so a live (status: accepted) ADR carrying superseded_by: NULL in a foreign target repo was silently emitted as a superseded-adr finding into graveyard/abandoned.json, quoting the null literal as its evidence — a false "this decision was abandoned" claim in a machine-read artefact that graveyard_lessons.go and the graveyard-interpreter agent later cite.

Fix

Tests (fail before, pass after)

Verification (go1.25.6, matching go.mod)

go test ./... and go test -race ./... green; go vet ./... clean; gofmt -l clean. A CHANGELOG ### Fixed entry accompanies the change.


Disclosure: this change is AI-assisted; the commit carries Assisted-by: Claude:claude-opus-4-8 per CONTRIBUTING, with a human Signed-off-by (DCO). Happy to adjust anything to fit your conventions.

frontmatter.IsNull and its byte-identical lint-side copy isNull treated
null as only ""/"null"/"~", missing the YAML 1.1 (!!null) / YAML 1.2
core-schema (§10.2.1.1) spellings Null and NULL that the memory scalar
parser (internal/core/memory/yaml.go) already resolves.

On the lifeboat pack path gvSupersededADRs unquotes superseded_by and
then asks IsNull, so a live (status: accepted) ADR carrying
`superseded_by: NULL` in a foreign target repo was silently emitted as a
superseded-adr finding into graveyard/abandoned.json, quoting the null
literal as its evidence -- a false "this decision was abandoned" claim in
a machine-read artefact, later cited by graveyard_lessons.go and the
graveyard-interpreter agent.

Widen both predicate copies together to the four YAML nulls plus the
empty scalar so the two gates capture/validate.go depends on cannot
drift. Fields does not strip quotes, so a quoted "NULL" still reaches
IsNull as a string in the general path and stays non-null; only the
pre-existing lifeboat unquote path reads a bare NULL as null, the
intended reading for a handle-or-null field.

Regression tests: TestIsNull in both packages asserts the four spellings
null and rejects near-misses and quoted forms; isAbsentValue inherits
the widened set; and an end-to-end lifeboat test walks the full iss Partnermedia#290
matrix (null | Null | NULL | ~ | "NULL" | 'Null' -> no finding) with a
positive control (superseded_by: adr-9 -> still reported).

Closes Partnermedia#290

Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Ethan Hawkes <ethanhawkes-gif@users.noreply.github.com>

@REPPL REPPL left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thank you for this — the widening itself is correct, the tests are green, and as our first outside contribution it also pressure-tested our conventions in useful ways. We commissioned a deep review of the diff; the full evidence trail now lives in the repo under .abcd/work/reviews/2026-08-19-pr-294-null-predicate/. The verdict is sound in scope, with a handful of changes we'd like before merge — all small, and all inside what this PR already touches:

  1. Delegate instead of duplicating (internal/core/lint/lint.go, isNull): the widened copy is byte-identical to frontmatter.IsNull, and lint.go already imports frontmatter (it delegates its scanner for exactly this reason). Please replace the body with return frontmatter.IsNull(v) and drop the duplicated table test — that removes the very drift class this fix exists to prevent, and makes the "mirrored tests" question moot.

  2. Doc comment count (internal/core/frontmatter/frontmatter.go, the IsNull comment): "an empty value and the four YAML nulls ""/"null"/"Null"/"NULL"/"~"" lists the empty scalar twice and five items under "four". The lint-side phrasing ("four YAML nulls (null | Null | NULL | ~) plus the empty scalar") is the correct one.

  3. t.Errorf in the spelling matrix (internal/core/lifeboat/graveyard_abandoned_test.go): t.Fatalf inside the loop stops at the first failing spelling and the positive control never runs; t.Errorf (or a subtest per spelling) reports the whole matrix.

  4. Widen the capture-side agreement test (internal/core/capture/parse_test.go, TestValidateStrictImpact): it still iterates only {"", "null", "~"} — please add the uppercase spellings so the package whose acceptance changed also pins it.

  5. CHANGELOG citation: our changelog cites ledger handles rather than GitHub numbers. We've retroactively captured the bug as iss-287 — please cite (iss-287) instead of (#290).

One thing we are deliberately not asking you to fix here: the review found that quoted nulls (impact: "NULL") are judged differently by capture (which unquotes before the predicate) and record-lint (which reads the raw value). That predates your change, needs a maintainer design decision, and is now tracked as iss-285 with a follow-up intent (itd-128) to consolidate the scalar decoders — your PR's evidence made that class visible, which is genuinely valuable.

Happy to merge once the five points above land.

Assisted-by: Claude:claude-fable-5

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.

frontmatter.IsNull misses the YAML nulls Null/NULL, so disembark pack reports a live ADR as superseded — quoting superseded_by: NULL as its evidence

2 participants