Skip to content

v0.3.0

Choose a tag to compare

@ghchinoy ghchinoy released this 16 Aug 15:30
7187882

Upgrade notes (v0.2.1 → v0.3.0)

binder never rejects input, and no emitted output changes: convert bundles are byte-identical. Almost every change below is additive or a diagnostic-label change. There is one exit-code exception, and it is a correctness fix rather than an arbitrary break: lint --strict can now exit 1 on a corpus that exited 0 under v0.2.1 — but only when that corpus contains a heading anchor that is genuinely dead on GitHub and that v0.2.1 wrongly accepted. The link was already broken; binder simply was not saying so. If you gate CI on binder lint --strict, read the heading-anchor bullet below before upgrading. Nothing that was correct under v0.2.1 now fails.

  • Entrypoints are now reported separately from orphans. A corpus-root README.md with no inbound links was reported as an orphan in v0.2.1; it is now reported as an entrypoint and is not counted as a finding. Only a root README.md is recognised by name — index.md and INDEX.md are not, and remain orphans exactly as in v0.2.1.
  • Report section labels changed. lint and review now print entrypoints (no inbound links): (new section) and orphans (no inbound or outbound links): (v0.2.1 printed orphans:). If you grep binder's prose output, update your patterns.
  • --strict may now exit 0 where it exited 1 — but only where the root-README orphan was the last remaining finding. Other findings still gate.
  • Heading-anchor checking is now correct, and it moves findings in BOTH directions. binder's slug function claimed GitHub parity but collapsed runs of hyphens and dropped underscores; GitHub does neither. So #agent-skill--plugin (valid on GitHub) was wrongly reported broken, and #agent-skill-plugin (dead on GitHub) was wrongly accepted. Both are fixed.
    • On this repo's own docs that removes 50 false reports and adds none (85 → 35 findings; every one of the 35 remaining is a deliberate test fixture).
    • But if your corpus contains an anchor that was silently dead before, lint now reports it — and lint --strict can go from exit 0 to exit 1. Confirmed, not theoretical. A CI job gating on binder lint --strict can newly fail. That is the fix working, not a regression: the link was already broken on GitHub, binder just wasn't saying so.
    • No emitted bytes change. convert output is byte-identical; this affects lint diagnostics only.
    • Unchanged and carried forward, NOT introduced: headings with accented or non-Latin characters (## Café, ## 配置) still produce false "broken anchor" reports, exactly as in v0.2.1. binder's kept character set is ASCII where GitHub's is Unicode. Now documented in code; a follow-up issue is filed.
  • JSON additions, all additive, under an unchanged binder.report/v1 schema tag. review --json gains entrypoints; infer --json gains warnings, and its mappings field is now [] instead of null when empty; validate --json gains reserved_structure_checked (boolean, always false in v0.3.0). The schema tag did not change, so consumers pinning on the tag will not see these coming.
  • validate now discloses what it does not check. When a bundle contains reserved files (index.md, log.md), the prose report gains one line: scope: reserved-file structure (index.md, log.md) not validated; verdict covers concept files only. Nothing about the verdict changed — v0.2.1 did not check reserved-file structure either. What changed is that it now says so, rather than letting a clean verdict imply a coverage it never had. Bundles with no reserved files print byte-identical output to v0.2.1.
  • Usage errors now exit 2, not 3. A malformed --type-map and an unknown subcommand both returned 3 in v0.2.1 and return 2 in v0.3.0 (ExitUsage). Scripts branching on exit 3 for usage errors need updating; genuine I/O errors still exit 3.
  • infer's "no mappings inferred" diagnostic moved from stdout to stderr, so it no longer pollutes piped stdout.
  • If you installed with go install, your provenance stamp loses its v. v0.2.1 installed that way stamped by: binder/v0.2.1; v0.3.0 stamps by: binder/0.3.0, matching what the downloaded release binaries have always written. This appears inside bundles you have already generated, so a re-run will show a by: diff on otherwise unchanged concepts. Binaries downloaded from the releases page are unaffected — they were never v-prefixed.

0.3.0 (2026-08-16)

Features

  • convert: --external-root suppresses advisories for known sibling workspaces (#25) (7c3174f)
  • enrich: --overwrite-keys refreshes named keys in place, refusing trust keys (#22) (8fcf727)
  • graph: read-only query_graph MCP tool with five traversal operations (#33) (bb383ae)
  • mcp: expose external_root on convert and name all seven tools in help (#62) (8ea12b0)
  • review: reclassify entrypoints vs orphans in review and lint (#24) (3546fda)
  • status: validate --status-map vocabulary; opt-in canonicalization (#23) (7f4ca6b)

Bug Fixes

  • build: canonicalize the version stamp to a single no-v form across all install paths (e9df155)
  • cli: stop claiming root index.md is a recognized entrypoint (b98f502), closes #73
  • cli: usage errors exit 2 and infer emits stable empty arrays (8b2083a)
  • infer: write the zero-mapping diagnostic to stderr so stdout stays machine-consumable (#67) (bcba1fd)
  • keep underscores and hyphen runs in okf heading slugs (#84) (9b34a0f)
  • lint: stop electing a root index.md as an entrypoint (#75) (34c9483), closes #71 #72
  • make validate disclose unchecked reserved-file scope (#83) (6208383)

Documentation

  • clijson: envelope keys are struct-order, not sorted (948260e)
  • correct entrypoints label, tutorial claims, MCP parity (#78) (69cd81f)
  • correct README, tutorial and RELEASING against the shipped v0.3.0 binary (#65) (9df8eaf)
  • graph: document the graph surface and ship a graph-format sample (#36) (1fb9284)
  • lint: correct the divergence example in a doc comment (#81) (5bdc3e1)
  • readme: correct lint/review agreement and root entrypoint claims (#69) (7bad872)
  • user-guide: correct and complete user guide for v0.3.0 (#70) (2293734)

Known issues — present in v0.3.0 and v0.3.1

None of the issues below is fixed in v0.3.1. Fixes are landing in v0.3.2.

These were found by an audit of every guarantee binder states in its help text and documentation: 45
claims were checked by running the binary, and the ones below did not hold. We are listing all of
them, including the ones that reflect badly on us, because binder's central promise is that it never
asserts something it has not verified — and that promise has to bind our release notes too.


1. binder enrich can write frontmatter that binder itself cannot read back

This is the most serious issue here: it can leave a file unusable.

When frontmatter contains a multi-line flow sequence — a [] collection spread over several
lines — and binder appends an entry to it, the collection is re-encoded to block style but its
original closing ] is left orphaned on its own line. The result is not valid YAML.

Given demo/metric.md:

---
type: Metric
verified: [
  { by: human:x, at: 2024-02-01T09:30:00Z },
  { by: human:y, at: 2024-03-01T09:30:00Z },
]
---

# Body

running binder enrich demo --verified-by human:ghchinoy reports success:

1 file(s): 1 enriched, 0 unchanged, 0 skipped
  enriched metric.md (added: generated, title, verified)

but produces frontmatter with a stray ] on its own line:

---
type: Metric
verified:
  - at: "2024-02-01T09:30:00Z"
    by: human:x
  - at: "2024-03-01T09:30:00Z"
    by: human:y
  - at: "2026-08-16T21:53:51Z"
    by: human:ghchinoy
]
title: Body
generated:
  at: "2026-08-16T21:53:51Z"
  by: binder/dev
---

A subsequent run cannot read the file it just wrote:

1 file(s): 0 enriched, 0 unchanged, 1 skipped
  skipped metric.md (unparseable frontmatter: invalid frontmatter: yaml: line 8: did not find expected key)

This transcript is real output, captured from a source build at the v0.3.1 commit — which is why the
provenance line reads binder/dev. The two 2026 timestamps are wall-clock values from that run; your
timestamps will differ, the corruption will not.

If you have run binder enrich over a corpus containing multi-line flow sequences, we recommend
checking that those files still parse.
Single-line flow sequences are not affected. A fix is
landing in v0.3.2.

2. binder enrich inserts a blank line between the frontmatter and the body

If a document's body starts immediately after the closing --- with no blank line, binder inserts
one when it rewrites the file. The body's content is preserved exactly, but its first byte
changes
.

This happens on the default enrich path — it is the common case, not an edge case. It also
happens when only an existing value changes and no key is added at all.

This is a defect, and v0.3.2 is landing a change that makes the byte-faithfulness guarantee true
rather than narrowing it.

Two further deviations are deliberate, documented, and long-standing — design bounds rather than
defects, but listed here because anyone relying on byte-faithfulness needs to predict every
difference between their input bytes and their output bytes:

  • CRLF line endings are normalised to LF.
  • A file that does not end in a newline gains one.

3. Several stated guarantees are broader than the behaviour

v0.3.2 addresses all of these.

  • enrich is described as byte-faithful, without qualification, in the command's help text, in
    README.md, in the packaged skill documentation, and in the ingestion-workflow and JSON-contract
    reference docs. Issue 2 above is why that is currently wrong, everywhere it is stated.
  • convert is described as deterministic, without qualification. Two runs over identical input
    differ in the generated provenance timestamp, which is deliberately a live record of when the run
    happened. Output is byte-identical when the clock is pinned via SOURCE_DATE_EPOCH. The
    behaviour is correct; the description is missing the qualifier
    — which the same user guide
    already applies correctly to enrich.
  • enrich is described as idempotent, without qualification. It is idempotent on the default
    path, including across a live clock. It is not idempotent when --verified-by is used: a
    second run appends a second attestation.
  • enrich is described as adding only absent keys. A trust key that is already present is
    appended to rather than skipped. binder still never clobbers or removes an existing value.

For the avoidance of doubt, the fourth guarantee in that same description — that writes are
atomic — was tested and holds. binder writes to a temporary file in the same directory and
renames it over the target, so an interrupted run leaves either the original file or the complete
replacement, never a partial one.

4. A verified-by value from the environment stamps without an explicit flag

If BINDER_VERIFIED_BY is set, binder writes a verified attestation without --verified-by being
given on the command line. An environment variable can arrive by means other than a deliberate
decision to attest. v0.3.2 is landing a change so that binder refuses to stamp from the
environment alone, and says so. An explicit --verified-by, or a verified-by value in your own
global config
, continues to stamp as before.

5. The JSON report classifies a modified trust key as added

When a trust key that was already present is appended to, the binder.report/v1 envelope lists it
under added. Consumers reading that field programmatically get a wrong classification for the trust
key. The file content is correct; the report's description of what happened to it is not.


The link-resolution bug reported in #99 is fixed and ships in v0.3.1.