Skip to content

fix(yaml): an empty link target fails validate on both paths, not only --direct - #1080

Merged
avrabe merged 2 commits into
mainfrom
fix/empty-link-target
Oct 10, 2026
Merged

avrabe merged 2 commits into
mainfrom
fix/empty-link-target

Conversation

@avrabe

@avrabe avrabe commented Oct 10, 2026

Copy link
Copy Markdown
Contributor

I found this by reading why the scheduled fuzz.yml has failed on every run (12 of 12): the cli_argv and yaml_footguns jobs.

1. Real defect (yaml_footguns): the default validate falsely passed.

links:
  - type: derives-from
    target: ""
before after
rivet validate (salsa) PASS ERROR link 'derives-from' targets '' which does not exist
rivet validate --direct ERROR (same) ERROR (same)

The rowan extraction dropped empty targets in both the block and the flow form. You chose contract A: both paths error. The rowan path now keeps a link whose target: key is present even when it is empty. Shorthand losses: null and ~ still mean "no link", and those tests are unchanged.

The fuzz oracle follows the same rule. An empty target the source literally writes (target: "" / '') is preserved, not synthesised, and validation rejects it. Only an empty target the source never wrote is still a phantom-link finding.

2. Harness false positive (cli_argv). The argv list --format json -he-h-eln --format json contains a short-flag cluster with h, so clap prints help text and exits 0. The harness skipped -h but not clusters; it now treats any short cluster containing h or V as help or version. This is conservative: it may also skip a cluster where h is a flag's value.

Tests

  • explicit_empty_link_target_is_kept_for_validation (yaml_hir, block and flow forms). Negative controls: reverting either change gives left: [].
  • an_empty_link_target_fails_validate_on_both_paths (CLI, both paths, with the exact message).
  • The fuzz harnesses type-check on the pinned nightly. They cannot link on macOS (SDK ld issue), so the scheduled fuzz.yml run after merge is the evidence.

Gates run locally on Rust 1.99.0: fmt, clippy --all-targets -D warnings, workspace tests (2668), rivet validate (this repository still passes), docs check and yamllint.

🤖 Generated with Claude Code

https://claude.ai/code/session_015HMQUV3u86jN2hmCtXNTc9

…y --direct

An explicit `target: ""` was dropped by the rowan extraction (block and flow
form), so the default incremental `rivet validate` passed while
`validate --direct` reported "link 'derives-from' targets '' which does not
exist". Maintainer decision: both paths report it. The rowan path keeps a
link whose `target:` key is present even when empty; shorthand
`losses: null` / `~` still means no link. The yaml_footguns fuzzer had
failed on this on every scheduled run.

The fuzz oracles follow the decision: an empty target the source literally
writes is preserved, not synthesised, and validation rejects it; only an
empty target the source never wrote is a phantom. cli_argv also failed on
every run, on a harness false positive: `-he-h-eln` is a short-flag cluster
that requests help, which clap prints as text with exit 0; the short-circuit
detector now covers clusters.

Tests: a yaml_hir unit test for block and flow forms (each fails when its
change is reverted) and a CLI test over both validate paths.

Fixes: REQ-028

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015HMQUV3u86jN2hmCtXNTc9
@avrabe
avrabe force-pushed the fix/empty-link-target branch from 207214e to 5ddb5af Compare October 10, 2026 03:20
@github-actions

Copy link
Copy Markdown

📐 Rivet artifact delta

No artifact changes in this PR. Code-only changes (renderer, CLI wiring, tests) don't touch the artifact graph.

@codecov

codecov Bot commented Oct 10, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

…get check

The PR-diff mutation gate left two mutants alive on the new condition. One
showed the `!target.is_empty() ||` half was redundant (target is only ever
set when the `target` key is present), so the condition is now
`!link_type.is_empty() && target_present`. The other showed no test covered
an entry missing `type:`; the unit test now checks that a typeless entry and
a targetless entry are dropped while a complete one is kept. Applied by hand,
`||` for `&&`, a dropped `!` and a negated `target_present` each fail it.

Fixes: REQ-028

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015HMQUV3u86jN2hmCtXNTc9
@avrabe
avrabe merged commit 81dfc30 into main Oct 10, 2026
32 checks passed
@avrabe
avrabe deleted the fix/empty-link-target branch October 10, 2026 05:31
avrabe added a commit that referenced this pull request Oct 10, 2026
… input (#1081)

With the empty-target crash fixed (#1080), yaml_footguns now stops on
`target: null`: the HIR reads it as the text `null`. That is not a silent
accept. The serde path cannot parse a null target, and the parse error fails
`rivet validate` on both paths (the default path also reports the HIR link
to `null` as broken); confirmed with a fixture for `null` and `~`. The HIR
null oracle now fires only when serde accepted the document, which is when
the coercion would go unreported. This narrows the oracle; it changes no
product behaviour.

Refs: REQ-028


Claude-Session: https://claude.ai/code/session_015HMQUV3u86jN2hmCtXNTc9

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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.

1 participant