Releases: ghchinoy/binder
Release list
v0.5.2
0.5.2 (2026-08-25)
Bug Fixes
- #106: correct plugins/ JSON drift + add the missing derived-doc gate (#166) (06b1b71)
- #89: plugin-validate parses frontmatter with a real YAML parser and fails closed (#165) (481a58b)
- bundle: recover & disclose unparseable concepts and stop scraping root okf_version (#161, #163) (#168) (85205b8)
- stop citing/deriving from frontmatter that never parsed (#162, #164) (#167) (f7dfc31)
v0.5.1
v0.5.0
0.5.0 (2026-08-21)
Features
- docs: generate command reference from Cobra with drift gate (#145) (f7d6d01)
- site: brand theme, homepage, and command reference nav (#147) (fe0ae1c)
- site: build-time version display and guardrail tests (85c5e08)
- site: full docs IA, concept pages, and asset sync (#146) (c110cc4)
- site: scaffold Starlight site with one-doc vertical slice (#143) (ed91e68)
Bug Fixes
- site: resolve cross-page anchors and tighten fabrication gate (4d22250)
Continuous Integration
- docs: self-enable Pages via configure-pages enablement (81e07db)
v0.4.0
v0.3.3
0.3.3 (2026-08-17)
Bug Fixes
- correct block-scalar span undercount in frontmatter splice (188372a)
- preserve all keys when frontmatter contains a lone CR (#129) (c6d1241), closes #123
binder v0.3.3
v0.3.3 is a correctness release. It closes the two defects that made v0.3.1 and v0.3.2 unsafe for any
corpus containing multi-line attestation notes or a stray carriage return in frontmatter, and it
removes an absolute claim from binder's own help text.
binder's goal is to operate only on the YAML. That is what it aims at, and it is the honest form
of the sentence this release deletes — a goal stays true where a bug defeats it, and a guarantee does
not. These notes state what binder aims to do, what it now does, what it does not yet do, and where
that is tracked. They avoid promising results, including about the fixes below.
Two changes since v0.3.2:
- fix: correct block-scalar span undercount in frontmatter splice (
188372a) — the trust
fabrication and attestation-content loss tracked as #132 - fix: preserve all keys when frontmatter contains a lone CR (
c6d1241, PR #129) — closes #123
⚠️ Most serious: binder could destroy attestation text and assert a trust claim no one made — fixed (issue #132)
This is the most serious defect in the v0.3.x line, and it is tracked as issue
#132. On v0.3.1 and v0.3.2, adding an attestation
with enrich --verified-by to a file that already held an attestation whose note (or any field) was
written as a multi-line block scalar would:
- destroy the existing notes' text irrecoverably — with more than one block-scalar attestation,
every one but the last loses its content; and - fold the orphaned lines into the new stamp's actor identity, producing an actor no human ever
wrote.
Measured on three binaries — v0.3.1 (8090ea8), v0.3.2 (bceba9f) and the v0.3.3 candidate
(188372a) — each given its own copy of the same LF-only file, with no unusual characters, and each
run with the same command (enrich --verified-by human:probe). A fourth copy was kept untouched. All
four were then read by a short program that parses with the stock YAML library rather than binder's
codec and prints the byte count, the top-level keys and each attestation entry. Its output, verbatim:
=== pristine/doc.md ===
bytes=233 CR=0 LF=16 preprocessing=false
keys=[title type verified]
entry[0] by="human:person1" note="note1_a\nnote1_b\n"
entry[1] by="human:person2" note="note2_a\nnote2_b\n"
=== v031/doc.md ===
bytes=316 CR=0 LF=20 preprocessing=false
keys=[title type verified generated]
entry[0] by="human:person1" note=""
entry[1] by="human:person2" note=""
entry[2] by="human:probe note2_a note2_b" note=<absent>
=== v032/doc.md ===
bytes=315 CR=0 LF=19 preprocessing=false
keys=[title type verified generated]
entry[0] by="human:person1" note=""
entry[1] by="human:person2" note=""
entry[2] by="human:probe note2_a note2_b" note=<absent>
=== cut/doc.md ===
bytes=373 CR=0 LF=21 preprocessing=false
keys=[generated title type verified]
entry[0] by="human:person1" note="note1_a\nnote1_b\n"
entry[1] by="human:person2" note="note2_a\nnote2_b\n"
entry[2] by="human:probe" note=<absent>
On the candidate both notes come back matching the untouched copy, and the new stamp carries only the
actor that was passed. It needed no carriage return, no unusual character and no special flag to go
wrong, and neither binder validate nor binder review reported anything: that is why it is listed
first.
One thing about that evidence block, because it is the most persuasive thing in these notes. It
reads as though a second YAML parser detected the damage. It did not, on its own. It could only show
you note="" — and an empty note is what a destroyed note and an always-empty note both look
like. What makes the block discriminating is the pristine row — the untouched copy the harness
kept, which is the only line that establishes what the notes said before. If you are checking your own
corpus, the re-parse is not the instrument that will tell you; the diff against an untouched copy is.
If you stamped a corpus with v0.3.1 or v0.3.2, upgrading does not restore what was lost. The
deleted note text is gone from the file and has no second copy. Recover it from version control or
from a copy binder never touched; a damaged file re-parses cleanly and reads back note: "", and
an empty note reads identically whether it was destroyed or was always empty, so the file itself
cannot tell you which happened. Only a diff against an untouched copy can.
Why the changelog does not link this fix to #132. The squash subject for
188372awas
overridden at merge, which suppressed GitHub's automatic(#N)append, and the commit body carries
noClosesfooter. The commit is onmainand is immutable, so the generated changelog entry can
only ever carry a commit link — it can never link this fix to the issue describing it. That is
why the issue number is written out here in prose: for the most serious defect in this release line,
these notes are the only route from the fix back to #132.
Rewriting a commit already on a shared base for a cosmetic suffix was judged the worse trade, so
this is disclosed rather than repaired.
Fixed: a stray carriage return in frontmatter no longer corrupts or refuses your file (#123)
A single lone \r inside frontmatter — the kind an editor or a copy-paste leaves behind — had two
distinct bad outcomes on v0.3.1 and v0.3.2. Both are fixed:
- A duplicated key, and a file no YAML parser will read. On a file whose frontmatter contained
both a lone\rbetween two keys and an existingverified:list, v0.3.1 and v0.3.2 appended
the new stamp as a secondverified:key rather than merging into the existing one. The result
parses in binder and is rejected outright by a standard YAML reader:
yaml: unmarshal errors: line 4: mapping key "verified" already defined at line 3. On the candidate
the same file comes back with oneverified:holding both entries. - The whole file refused. A lone
\rimmediately before the closing---made v0.3.2 report
unparseable frontmatter: invalid frontmatter: unterminated '---' blockand skip the file
(1 skipped; exit 1 under--strict). Measured on a v0.3.1 binary too: same message, same skip.
On the candidate the same file is read, enriched and written correctly.
And the bound on that, measured, because the scarier version of this section would be wrong. A
lone \r between two ordinary keys, in a file with no verified: list, was not damaged by v0.3.1
or v0.3.2: the CR line was passed through untouched and the new keys appended after it. status did
not disappear. The two outcomes above needed the CR plus something else — a verified: list to
append into, or the closing fence immediately after. If you are auditing a corpus, that narrows what
you have to look at.
Files that earlier releases refused as malformed are back in scope
This is a behaviour change, and it is the one to read twice if you keep a set-aside pile. Shapes
that v0.3.1 and v0.3.2 refused — a lone CR before the closing fence, with or without a block-scalar
field inside a verified: entry — are now read and written. What that write did in the cases
measured, each stated with its bound rather than as one combined claim:
- Values. Every pre-existing attestation value came back intact when read by a YAML parser that is
not binder's. No block-scalar note shrank or vanished. - Additions. The added keys were the ones
enrichwas asked to add, and nothing else was added. - On an LF-only file, the diff against an untouched copy is additions only — no line removed,
none altered. - On the file carrying the stray CR, the diff is additions plus one change, and that change is
inside a pre-existing entry: the CR is re-emitted as a newline. It is a re-serialisation of the
same value rather than the loss of one — the parser reads back the identical string either way — but
it is not byte-identical, and printing it as byte-identical would be the over-claim this release
exists to remove. The bound is stated below.
These are the cases that were run. They are not a guarantee about cases that were not.
The practical consequence is a change in what a silent run means to you:
- On v0.3.2, these files produced a loud skip and were left untouched on disk. The refusal was
the boundary of the risk. It still cost you the file — you had to set it aside — but you could
see it happen, and it meant nothing had been written. - On v0.3.3 the same files are written. There is no skip line to read any more, so "binder said
nothing, therefore nothing was touched" is no longer true of this shape. What protects you now is
that the write is correct on the shapes listed above — which is what the fixes are for, and it is a
narrower kind of protection than a refusal you could see.
If you set files aside on v0.3.1 or v0.3.2 because binder refused them, you can bring them back —
and, per the note above, if you stamped anything in that period, diff it against a copy binder never
touched before you trust it.
One bound worth knowing, measured: a stray CR inside frontmatter is written back as a newline when
binder rewrites that file. Body bytes are untouched (a \r in the body survives), and a file that
needs no...
v0.3.2
0.3.2 (2026-08-17)
Bug Fixes
- preserve flow/map append entries; fix multi-line flow (#119) (a8ba6f8)
- require explicit verifier to write verified stamp (#118) (fc3c8c9)
Documentation
v0.3.1 MADE FORWARD PROMISES ABOUT v0.3.2
v0.3.1's release notes did not merely list defects. They promised specific fixes in v0.3.2, and
they closed with a standard we are now bound by:
"binder's central promise is that it never asserts something it has not verified — and that promise
has to bind our release notes too."
A release note that promises a fix is an assertion. Shipping v0.3.2 without reconciling those
promises, item by item, would leave the previous release asserting something false — the exact
failure this release exists to end. Every v0.3.1 promise is reconciled below, including the one we
did not fully keep.
RECONCILIATION OF v0.3.1's KNOWN ISSUES — measured, not assumed
| v0.3.1 item | Promise made | Status in v0.3.2 | How verified |
|---|---|---|---|
1. enrich writes frontmatter binder cannot read back (multi-line flow sequence, orphaned ]) |
"A fix is landing in v0.3.2." | FIXED | Re-ran v0.3.1's own published input against the released v0.3.2 binary. No orphaned bracket; binder reads back the file it wrote — a plain re-run reports unchanged. (Repeating the transcript's --verified-by run appends a further attestation under a moving clock, by design — see 3c.) Better than promised: pre-existing entries kept their exact bytes — flow-mapping style, unquoted timestamps, {by,at} order. (#119) |
| 2. Blank line inserted between frontmatter and body | "v0.3.2 is landing a change that makes the byte-faithfulness guarantee true" | FIXED | od -c before/after on a body abutting the closing fence. Body still abuts; first byte unchanged. (#119) |
3a. byte-faithful stated unqualified across help text, README, skill docs, references |
"v0.3.2 addresses all of these." | #118 corrected the help text, skill and reference docs; #125 corrected the README and user-guide headline and trust claims. Other sites remain. | |
3b. convert described as deterministic, unqualified |
"v0.3.2 addresses all of these." | FIXED | #118 |
3c. enrich described as idempotent, unqualified |
"v0.3.2 addresses all of these." | FIXED | #118 |
3d. enrich described as adding only absent keys |
"v0.3.2 addresses all of these." | DESCRIPTION FIXED, DEFECT OPEN | #118 corrected the wording; the behaviour is #120, deliberately still open and named. |
| 3-atomic. atomic writes | stated as holding | STILL HOLDS | Re-confirmed; the guarantee was not dropped in the edits. |
4. BINDER_VERIFIED_BY stamps without an explicit flag |
"v0.3.2 is landing a change so that binder refuses… and says so." | FIXED | #118. On the CLI binder refuses and discloses, in prose and in the JSON envelope; the MCP surface never reads the variable at all. Four origin cases re-derived by execution with HOME and XDG_CONFIG_HOME controlled. |
5. JSON report classifies a modified trust key as added |
(no promise made) | STILL OPEN | #120, carried forward. |
The promise we did not fully keep — stated plainly
v0.3.1 said v0.3.2 would address the unqualified byte-faithful claims "everywhere it is stated."
It has not. The most prominent statements are corrected — the README's opening trust sentence, the
README and user-guide headline guarantees, the user-guide trust-vocabulary claims, the command help
text, the packaged skill and reference docs. Others remain, and we are naming them rather than
letting the earlier promise quietly lapse:
README.md— the "How it works" codec descriptionplugins/okf-convert/skills/okf-convert/references/trust-discipline.mdinternal/enrich/enrich.go— package doc comment- and further occurrences; this list is a floor, not a total
Why they were left: the audits that produced the earlier list were keyed on the spelling
byte-faithful, and a second vocabulary — byte-for-byte — was never enumerated at all. Every count
we have published for this has later been found to be a floor. We are not going to publish another
total. The remaining work is a read-for-meaning pass, not a search-and-replace, and it is tracked
rather than rushed into a patch release.
KNOWN ISSUES — NEW IN THIS LIST
None of the issues below is fixed in v0.3.2. They were found during this release, by probing
the guarantee we were in the middle of correcting.
An earlier draft of this section called them pre-existing and not regressions. That was wrong, and
we are correcting it here rather than quietly. We went and measured it against the released
binaries instead of reasoning from the code:
#123 is a regression, and we introduced it in v0.3.1. Run the same file through the released
v0.3.0 binary and it comes out intact — the following keys survive, the file still parses, a re-run
reports unchanged. Run it through v0.3.1 or v0.3.2 and the keys are gone. The same is true of the
unreadable-file outcome, and of the block-scalar value corruption. All three arrived in v0.3.1, in
the very commit that was meant to make binder more byte-faithful. They are not long-standing defects
you have been living with; if you were on v0.3.0, this is something we did to you in v0.3.1.
One narrow part of #123 is genuinely older: the case where binder refuses a file as an
unterminated --- block behaves the same way in v0.3.0. Refusing is not the part that hurts you,
and it is not what the rest of this section is about.
#124 we have not dated, and we are not going to guess. Its mechanism is different, we have not
run that measurement, and an unmeasured reassurance is what got this section rewritten in the first
place.
MOST SERIOUS: binder can silently fabricate a trust claim (#132)
This is the most serious defect in this release line. It is listed first deliberately: it needs no
unusual characters, no special flag, and no carriage return. An ordinary Unix-LF file and an ordinary
command are enough.
On a file with Unix (LF) line endings and no unusual characters, adding a new attestation with plain
enrich --verified-by can splice the new entry into the middle of an existing attestation whose
note — or any field — is written as a multi-line block scalar. The existing attestation is severed
from its content, and the orphaned lines are folded into the new attestation's actor identity. The
file then asserts an identity that belongs to no one: for example an actor that parses as
human:alice inner1 inner2, assembled from alice's stamp and bob's note.
binder exits 0, prints no warning, binder validate reports the file conformant, and binder review shows output byte-identical to a healthy file. Neither of binder's own checks reveals it.
It is not only lost bytes — but bytes are lost, and they do not come back. Where more than one
attestation carries a block value, every one but the last loses its text irrecoverably: those
words are gone from the file and gone from the disk. What disappears is the attestation's
rationale — the note recording why something was signed off, which is exactly what people write
in a | block and exactly what has no second copy anywhere.
And on top of that, binder asserts a trust claim no human made — the one failure a provenance tool
exists to prevent.
It is not only the CLI: the MCP convert tool reaches this same defect, and that is measured, not
inferred. The tool was driven to disk against the released v0.3.2 binary and the result diffed
byte-for-byte against untouched copies of the inputs. Both existing attestations came back with an
empty note. One of them lost its text outright — those lines are gone from the file, and an
emptied note is indistinguishable from a note nobody wrote. The other's lines were not discarded:
they were folded into the identity of the stamp the tool had just written. Read back with a YAML
parser that is not binder's, the damaged file does not fail to parse — it yields three
structurally valid attestations, and the third one's actor is the literal string human:probe note2_a note2_b: the stamp's own actor with another attestation's note welded onto it. That is
the same assembly as human:alice inner1 inner2 above, measured on the other surface. The same
byte-diff comes back empty against the fixed build, so the instrument was reading. On MCP that
stamp can only have come from the verified_by input to the tool call, because that surface has
no other route to a stamp — it loads no config and reads no environment. So an agent can
destroy the note and fabricate the claim with no human watching an exit code. There is no MCP
enrich at v0.3.2; convert is the reached surface, and it is enough. The guidance below is
unchanged and covers both surfaces: stamping is the trigger, whatever does the stamping.
It is a regression: it was introduced in v0.3.1 by the same change that added byte-faithful
preservation, and it is present in v0.3.1 and v0.3.2.
Until a fixed release is available (tracked at #132):
- **If a
verified:entry in...
v0.3.1
0.3.1 (2026-08-16)
Bug Fixes
- key docs-impact gate on option text and declare bound (#116) (2548cf7)
- preserve unchanged pre-existing keys byte-for-byte (427503e)
- quote SKILL.md description so frontmatter is valid YAML (#90) (568b3c6), closes #88
- stop code-region stray bracket from swallowing next link (#117) (8d47111)
Continuous Integration
Documentation
- add in-memory labeled property graph primer (#110) (31769de)
- add okf cross-check step to tutorial, de-jargon pass (#105) (57579cb)
- correct anchor slug rules and name which slug is which (#91) (62ea325)
- editorial pass on the LPG primer and link it from the guide (#114) (80a8eb6)
- fix false trust and contract claims in okf-convert skill (#112) (84116dc)
- focus the README on using binder, not building it (#98) (7e2eb3c)
- record validate's reserved-file scope in guide and tutorial (#102) (23a82a3), closes #97
- tidy download v-note and mark JSON output agent-ready (#107) (8ddc8a0)
- trim README to quickstart, rehome MCP parity to guide (#111) (b10b9ab)
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.
enrichis 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.convertis described as deterministic, without qualification. Two runs over identical input
differ in thegeneratedprovenance timestamp, which is deliberately a live record of when the run
happened. Output is byte-identical when the clock is pinned viaSOURCE_DATE_EPOCH. The
behaviour is correct; the description is missing the qualifier — which the same user guide
already applies correctly toenrich.enrichis described as idempotent, without qualification. It is idempotent on the default
path, including across a live clock. It is not idempotent when--verified-byis used: a
second run appends a second attestation.enrichis 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.
v0.3.0
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.mdwith 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 rootREADME.mdis recognised by name —index.mdandINDEX.mdare not, and remain orphans exactly as in v0.2.1. - Report section labels changed.
lintandreviewnow printentrypoints (no inbound links):(new section) andorphans (no inbound or outbound links):(v0.2.1 printedorphans:). If you grep binder's prose output, update your patterns. --strictmay 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,
lintnow reports it — andlint --strictcan go from exit 0 to exit 1. Confirmed, not theoretical. A CI job gating onbinder lint --strictcan 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.
convertoutput is byte-identical; this affectslintdiagnostics 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/v1schema tag.review --jsongainsentrypoints;infer --jsongainswarnings, and itsmappingsfield is now[]instead ofnullwhen empty;validate --jsongainsreserved_structure_checked(boolean, alwaysfalsein v0.3.0). The schema tag did not change, so consumers pinning on the tag will not see these coming. validatenow 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-mapand 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 itsv. v0.2.1 installed that way stampedby: binder/v0.2.1; v0.3.0 stampsby: 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 aby:diff on otherwise unchanged concepts. Binaries downloaded from the releases page are unaffected — they were neverv-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
v...