Access is verified once, at fetch, and the copy is durable — so whether an
adoption is still legitimate rests on the relative visibility of two
repositories, which somebody can change with a checkbox a year later without ever
seeing the adoption. The lock gains an optional fourth field and check turns that
from a silent permanent hazard into something that runs.
It costs nothing at add time, because the anonymous request already happened to
decide whether to warn and the answer was being thrown away, and one request per
run at check time rather than one per document, because only our own visibility
has to be current. The stored value decays in both directions, so a source
recorded not-public is re-probed only when the alarm would fire, and a source that
has since gone public updates the lock and says nothing.
Fixes a bug found while testing the alarm rather than after shipping it.
sourceVisibility returned public whenever no credential was configured, which is
sound at add time — the fetch had just succeeded anonymously — and wrong in the
audit, where it is a probe and not a fetch: it would have silently cleared real
alarms. Probing is now its own function that always asks with no credential,
because what matters is what a stranger can read and not what we can.
The value is recorded as public or not-public and never private: an anonymous
request tells those apart and nothing finer, so it cannot see two repositories
private to different people, which is the case that genuinely widens access.
Three-field locks still load, and unknown visibility round-trips as absent rather
than as a value.
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
Go, standard library only, with git shelled out for list alone. list enumerates a
publisher's .loom/published by partial clone and ls-tree; add fetches one document,
writes it under .loom/externals and records the resolved origin and the publisher's
ETag in .loom/externals/.locks; check asks conditionally and reports.
The first real run did what the tool exists for. All eight documents adopted by
hand before it existed reported unlocked — the tool refuses to invent a lock by
adopting whatever the remote currently serves, since that would assert the local
copy is the one being served, which is the thing it was about to check. Locking
them fetched two that had moved: bedrock/starting.md, which now says the worked
example is private and will not link to something you cannot fetch, and cart.md,
which went to v1.
cart v1 changes a role we cast: a cart is not committed, because a committed cart
grows a third file by itself — version control does not require anybody to ask, so
the two-file rule is never invoked — and because ignored, gone means gone. Adds
.loom/cart/ to .gitignore and supersedes the isolation entry rather than editing
it. osprey and marmalade are already in history and are left there: rewriting to
honour a rule adopted afterwards costs more than it buys.
Records the conflict this creates rather than settling it: the annotation protocol
here says commit before dissolving because git is the only archive, and an ignored
cart has no archive, so dissolving would destroy the annotations outright.
Credentials are read-only, per host, and passed to git through the environment
rather than argv, because argv is visible to every process on the machine.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018UTxuSizozEA8yDitPuris