add appended .md unconditionally, so openapi.json became openapi.json.md. Reported
from use against a repository that publishes an OpenAPI document beside its prose.
It is now supplied only when the name carries no extension of its own.
Two consequences came with it that the report could not have seen. A facet's name
was built by stripping the extension, so openapi.json would have taken
openapi.usages.md and collided with the facet for a sibling openapi.md; only a
trailing .md is stripped now. And the unlocked walk matched *.md only, so a
hand-fetched .json under .loom/externals was adopted, unlocked, and invisible to
check; it now walks everything that is not the lock file or a dotfile.
The assumption was never decided, it was typed. externals says a repository
publishes documents and publication says prose in whatever form its readers need.
Neither says markdown and three places in this tool did.
Also records that the wrong diagnosis cost more than the bug: the repository is
private, a scratch config with no token made every fetch look broken, and loomctl
config would have said so in one line.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018UTxuSizozEA8yDitPuris
Reported from use in another repository, reproduced before diagnosing, and the
report was right and understated: two staging sites built a lock record without
visibility, not one, and one of them was in add where the value was already
computed three lines above.
Fixed twice over, because the two fixes answer different questions. A staged copy
is new bytes, so its visibility is evaluated afresh rather than inherited — a
source that has gone private since adoption is a new exposure created at the moment
of applying, and inheriting the old value would record a fact about bytes nobody
holds any more. And apply now never lets an absent value overwrite a known one,
which covers polads staged by an older build, the state the reporter is in.
Pinned by a regression test. No test we had would have caught it: both sites
round-trip correctly in isolation, and the field only vanishes across the handoff
between them.
The lock header says visibility is recorded because access is checked once and the
copy is durable. A field that documents its own reason for existing, and is then
dropped by the one operation that updates it, reads as verified and is blank.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018UTxuSizozEA8yDitPuris
Restores something the specimen said and the round that discarded the specimen
lost with it: a changed external becomes a polad in the cart, and somebody
decides. check now stages what moved into .loom/cart/current/polad/ with the ETag
that was served alongside the bytes, and prints the .usages.md beside it, because
reconciliation runs the other way — the facets usually survive and what moves is
the code a usage named. It says so when there is no usages file, which is its own
finding.
With no cart open, check reports and stages nothing. The tool does not open a
round: a cart is a bounded exchange between two presences and starting one is
somebody's act, not a side effect of asking about freshness.
add now adopts what is not here and refuses what is already adopted, superseding
the entry that had it announce an overwrite — it no longer overwrites at all. The
one exception is the only way out of a dead end: a document present but unlocked
was fetched by hand, nothing records its origin, and the path does not round-trip,
so check cannot ask about it and a refusal would strand it forever. add accepts it
and the bytes decide — identical locks it without rewriting anything, which makes
the lock's assertion verified rather than assumed, and different stages a polad and
leaves the local copy alone because it is the only evidence anything moved.
apply exists because the lock is the half a person forgets: moving a polad by hand
leaves a lock describing the copy you just replaced. Recorded with its limit —
for an external, discard does not mean the change goes away, so discarding is
really knowingly stale and nothing yet records that choice.
Measured end to end on this repository: eight hand-fetched documents, all eight
locked, nothing rewritten.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018UTxuSizozEA8yDitPuris