Publishes what the tool guarantees, on a condition that fired rather than a decision reversed. Records the narrower promise that replaced promising the command surface — a change to it lands on the page consumers already lock, so a rename reports 200 the day it happens. And records a limit found by accident that follows from a guarantee rather than from anybody's code: an ETag is opaque, we promise never to interpret one, so two of them can be compared and never ordered. A consumer can know two versions disagree and can never know which is behind. Anybody building freshness on this inherits it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018UTxuSizozEA8yDitPuris
1154 lines
57 KiB
Markdown
1154 lines
57 KiB
Markdown
# Event log
|
||
|
||
**Appended, newest last, never revised.** *A later entry supersedes an earlier one
|
||
by saying so.*
|
||
|
||
*Entries tagged `osprey` were all considered in a single sphere of understanding.*
|
||
|
||
---
|
||
|
||
## 2026-09-07 — what this tool is `osprey`
|
||
|
||
**Decided:** *`loom-cli` is a fetcher and a comparator.* **It fetches a document
|
||
you depend on and tells you whether the publisher's copy has moved.** *It reports
|
||
and never repairs.*
|
||
|
||
**Believed to advance:** *every act in the loom model is a file in somebody's
|
||
repository — publishing is writing one, adopting is fetching a URL, homing a gap
|
||
is an `mv`.* **Almost nothing needs a program**, so the program should be the
|
||
part that cannot be done by hand: an HTTP request, repeated.
|
||
|
||
**Belief that could be shown wrong:** *that reporting is enough.* **If every
|
||
report is followed by the same manual act, we have moved the work rather than
|
||
removed it**, and the missing command will be obvious.
|
||
|
||
## 2026-09-07 — the design lives here and not in a spec `osprey`
|
||
|
||
**Decided:** *this log is the artifact of `osprey`.* **No specification document
|
||
is written, and the specimen that opened the round is discarded.**
|
||
|
||
**Believed to advance:** *every entry here carries a belief that could be shown
|
||
wrong, so a reader can see which parts are load-bearing and which were guesses.*
|
||
**A spec flattens all of it to equal confidence** — *which is how the specimen
|
||
managed to be wrong, with a straight face, within a day of being written.*
|
||
|
||
**Belief that could be shown wrong:** *that a reader can build from a log.* **It
|
||
is ordered by time and not by topic**, so somebody arriving reads chronologically
|
||
and holds the corrections in their head. *Fine at fifteen entries and not at
|
||
eighty. When a newcomer cannot build from it without a guide, the guide is the
|
||
next thing to write — and not before.*
|
||
|
||
## 2026-09-07 — freshness is a conditional request `osprey`
|
||
|
||
**Decided:** *the tool records the publisher's `ETag`, verbatim and opaque, and
|
||
sends it back as `If-None-Match`.* **It never computes a hash of anything.**
|
||
|
||
**Believed to advance:** *`externals` requires it, and we now have the empirical
|
||
reason as well as the stated one.* **On gitea a raw file's `ETag` is its git blob
|
||
hash; on GitHub it is not.** *A design that compares a local hash to a remote
|
||
`ETag` works on exactly one host by coincidence.*
|
||
|
||
**Belief that could be shown wrong:** *that `ETag` is stable enough to lock on.*
|
||
**A proxy, a CDN, or a host that regenerates the header per request would produce
|
||
a change report where nothing changed.** *Untested against anything but gitea and
|
||
GitHub.*
|
||
|
||
## 2026-09-07 — supersedes nothing: the path is for a person `osprey`
|
||
|
||
**Decided:** *the origin URL is recorded in the lock, not derived from the stored
|
||
path.*
|
||
|
||
**Believed to advance:** *the path `\.loom/externals/<host>/<path>.md` does not
|
||
round-trip.* **Measured:** *the document we hold as
|
||
`git.hypertheory-labs.dev/loom/externals/externals.md` is served from
|
||
`/loom/externals/raw/branch/main/.loom/published/externals.md`* — **the stored
|
||
path has dropped the route, the branch, and `.loom/published/`.**
|
||
|
||
*The third is not routing.* **`publication.md` makes `.loom/published/` the whole
|
||
contract**, and a tree that erases it cannot say whether a copy came from
|
||
somebody's published surface or from a file they may rename at will.
|
||
|
||
**Belief that could be shown wrong:** *that people will still read the path as a
|
||
location.* **If anybody writes code that parses it back into a URL, the path
|
||
should stop looking like one.**
|
||
|
||
## 2026-09-07 — the lock, and what it holds `osprey`
|
||
|
||
**Decided:** *one record per adopted document in `.loom/externals/.locks`: the
|
||
**resolved** origin URL, and the `ETag` it came with.*
|
||
|
||
**Believed to advance:** *the specimen said there would be no lock file, on the
|
||
strength of the `ETag`-as-blob-hash claim.* **That claim failed twice**, so the
|
||
file exists and is named for what it is. *It was briefly called `.etags`, from
|
||
when we believed there would be one field.*
|
||
|
||
**Resolved, and not the short form.** *Measured: gitea `303`s
|
||
`/loom/externals/raw/externals.md` to `/raw/branch/main/…`* — **so a lock holding a
|
||
short URL is locked to whatever the default branch is at the time you ask**, and a
|
||
branch rename reports as a change in the document.
|
||
|
||
**Belief that could be shown wrong:** *that one file is cheap.* **Two fetches in
|
||
one round conflict inside it, and the conflict is in a file no human can resolve
|
||
by reading.** *If that bites, it becomes one record per document and the tree is
|
||
mirrored twice.*
|
||
|
||
## 2026-09-07 — unlocked is a state `osprey`
|
||
|
||
**Decided:** *a document with no lock is reported as unlocked.* **`check` never
|
||
adopts whatever the remote is currently serving as the lock.**
|
||
|
||
**Believed to advance:** *adopting it would assert the local copy is the one being
|
||
served, which is the thing you were about to check.* **It is the hash assumption
|
||
in a different coat, and it fails silently in the one case that matters — a copy
|
||
somebody edited.**
|
||
|
||
**Belief that could be shown wrong:** *that anybody will run `pull` again to fix
|
||
it.* **If unlocked documents simply accumulate, the report is noise and something
|
||
has to lock them.**
|
||
|
||
## 2026-09-07 — `404` is unresolved, and that is no longer provisional `osprey`
|
||
|
||
**Decided:** *`check` reports a `404` as unresolvable — the document was withdrawn
|
||
or we have lost access — and names both readings without picking one.*
|
||
|
||
**Believed to advance:** *over HTTP they are the same response, because a host
|
||
that distinguished them would leak the existence of things you may not see.*
|
||
|
||
**This entry exists because the status of the behaviour changed and the code did
|
||
not.** *It began as a local workaround recorded in a `.gaps.md` against
|
||
`externals`, which listed `410` and not `404`.* **`externals` has since added the
|
||
`404` row, so the gap is closed and the facet is deleted at this reconciliation.**
|
||
|
||
> **The workaround is not retired. It is now the specified behaviour.** *Unchanged
|
||
> in the code and entirely changed in status — and this log is the only place that
|
||
> says so.* **Somebody inheriting this will find the behaviour and go looking for
|
||
> the gap that justified it, and find nothing.**
|
||
|
||
**Belief that could be shown wrong:** *that reporting both readings is useful.*
|
||
**If in practice it is always one of them, the report is a ritual** — *and that is
|
||
worth knowing.*
|
||
|
||
## 2026-09-07 — declined: `check` does not resolve the `404` over ssh `osprey`
|
||
|
||
**Considered:** *the two readings **are** distinguishable over ssh — permission
|
||
denied against repository not found — and `externals` now says so.* **Demonstrated
|
||
here by accident:** *`loom/settled` returned `404` over HTTP while an ssh clone of
|
||
it succeeded.*
|
||
|
||
**Not done, because:** *it needs a key, a non-default port, and a second
|
||
transport* — **and it would be fixing rather than reporting**, which is the rule
|
||
this design held all round.
|
||
|
||
**Belief that could be shown wrong:** *that a person will do it.* **The ssh
|
||
disambiguation is one command and it is in the document; if nobody ever runs it,
|
||
the ambiguity was never actionable and reporting it was theatre.**
|
||
|
||
## 2026-09-07 — declined: `init` `osprey`
|
||
|
||
**Considered and rejected**, not deferred.
|
||
|
||
**Because:** *its temptation is to scaffold, and four empty directories assert four
|
||
things nobody has decided.* **A file that carries no delta should not exist.**
|
||
|
||
**Belief that could be shown wrong:** *that starting is easy without it.* **If
|
||
people repeatedly fail to start**, *the useful version creates one file and asks
|
||
one question* — **and creating the tree is still not the feature.**
|
||
|
||
## 2026-09-07 — declined: worktree, clone; and where a round lives `osprey`
|
||
|
||
**Decided:** *the cart lives on the default branch; the work is isolated on a
|
||
branch named for the cart.*
|
||
|
||
**Believed to advance:** *a cart is a venue and the work is an artifact.* **`cart`
|
||
says three states are visible in a listing, and a branch makes all three
|
||
invisible** — *a venue only visible if you know which branch to check out is not a
|
||
venue.* **Both dailies land on the default branch, one commit per exchange.**
|
||
|
||
**Declined: a worktree, and a separate clone.** *Both isolate concurrent working
|
||
directories, and `cart` says there is one live cart, ever.* **They solve a problem
|
||
the agreement says cannot exist.**
|
||
|
||
**Belief that could be shown wrong:** *that the dailies and the work have
|
||
different audiences.* **If a daily needs to quote a file that only exists on the
|
||
branch, the split makes the daily unreadable from where it lives**, and the answer
|
||
collapses to one branch for both.
|
||
|
||
## 2026-09-07 — `publish` is renamed `reachable`, and it earned its place `osprey`
|
||
|
||
**Decided:** *the command is `loom reachable <path>` — one anonymous request
|
||
against a document in `.loom/published/`.*
|
||
|
||
**Believed to advance:** *the old name said the tool performs publication, which
|
||
is the one thing the model says it never does.* **It performs a fetch with a
|
||
different subject**, which keeps it inside a fetcher and a comparator.
|
||
|
||
**It was nearly cut for being a third kind of thing. Then it was validated three
|
||
times in one afternoon**, against loom's own repositories:
|
||
|
||
```
|
||
loom/settled 404 anon the page written to fix "settled is private"
|
||
jeffry/homelab-cluster 404 anon and bedrock's public starting page named it
|
||
as the better answer
|
||
loom/.loom 200 anon fetchable — but not where a reader lands
|
||
```
|
||
|
||
**All three are now fixed.** *The third was not a `reachable` failure and is
|
||
recorded as the tool's boundary:* **`reachable` answers *can anybody fetch this*
|
||
and does not answer *will anybody find it*.** *The second is not testable by a
|
||
fetcher and a comparator, and trying is the first step in the tool becoming
|
||
something else.*
|
||
|
||
**Belief that could be shown wrong:** *that publication failures are common enough
|
||
to need a command.* **Three for three is a small sample from one publisher in one
|
||
week.**
|
||
|
||
## 2026-09-07 — the `ETag`/blob coincidence is a migration aid `osprey`
|
||
|
||
**Recorded so it is not rediscovered as a good idea.**
|
||
|
||
*With eight adopted documents and no locks, `check` was run by hand by hashing the
|
||
local copies and comparing to remote `ETag`s* — **the exact thing the convention
|
||
forbids** — **and it worked**, because on gitea the two coincide. *It found
|
||
exactly one drifted document, correctly.*
|
||
|
||
**It is worth doing once, on gitea, to lock what was fetched by hand before the
|
||
tool existed. It is not a mechanism.**
|
||
|
||
> **The coincidence has now been found twice and rejected twice.** *The next person
|
||
> to notice it will think they have found the good idea again.*
|
||
|
||
## 2026-09-07 — annotate to ask; write in your own file to assert `osprey`
|
||
|
||
**Decided:** *responses between two presences are correspondence — each writes in
|
||
their own file — and annotation is reserved for asking or challenging.*
|
||
|
||
**Believed to advance:** *the freeze is not "you cannot edit", it is "you cannot
|
||
edit without resolving what was said".* **An annotation creates an obligation**,
|
||
which makes it the blocking form and correspondence the non-blocking one.
|
||
|
||
*And the cost is practical: **dissolving a multi-pass file is expensive**, so the
|
||
cost of annotating rises with the length of the thing annotated.* **Annotate
|
||
early, or accept the cost when the question is worth blocking on.**
|
||
|
||
**Belief that could be shown wrong:** *this rule is in neither `cart` nor
|
||
`annotating`.* **It is our reading of their text, and `cart` is loom's** — *they
|
||
may say we have it wrong.*
|
||
|
||
## 2026-09-07 — why this tool exists, in one story `osprey`
|
||
|
||
**Gitea's ssh is on port `2222`.** *I did not know that. I tried to clone, got
|
||
`Permission denied (publickey)`, and spent three tool calls and a guess chasing it
|
||
as an authentication problem before finding the port.*
|
||
|
||
**The fact was published.** *`jeffry/homelab-cluster`, in `.loom/published/gitea.md`,
|
||
says:*
|
||
|
||
> **A clone URL without the port will not work**, and the failure looks like an
|
||
> authentication problem rather than a wrong port.
|
||
|
||
**It named my failure before I had it, and I could not read it** — *the repository
|
||
was not fetchable by anybody who is not its owner.*
|
||
|
||
**This happened inside the round that was designing the fix**, *and it failed for
|
||
the reason that round had just spent two passes identifying.* **A document that is
|
||
correct, published, and unreachable is worth exactly as much as one that was never
|
||
written.**
|
||
|
||
**Believed to advance:** *nothing in this log is a stronger reason for the tool to
|
||
exist.* **`reachable` is why the document can be fetched. `pull` and `check` are
|
||
why it is still true when you read it.**
|
||
|
||
**Belief that could be shown wrong:** *that the tool would have helped.* **I would
|
||
have had to already depend on that document to have pulled it** — *and the thing I
|
||
needed was the one I did not know I was missing.* **A fetcher does not solve
|
||
discovery, and this story is partly a discovery problem wearing a freshness
|
||
problem's clothes.**
|
||
|
||
## 2026-09-07 — supersedes the `reachable` entry: publication is not publicness `marmalade`
|
||
|
||
**The entry above records three publication failures found by `reachable` and says
|
||
all three are fixed. One of them was not a failure.**
|
||
|
||
**`jeffry/homelab-cluster` is private on purpose** — *it describes a running
|
||
cluster, and its owner does not want outsiders reading what is on it.* **It
|
||
publishes six documents to an audience that is not the public**, *which
|
||
`publication.md` permits in the word we misread:*
|
||
|
||
> **What a repository has handed over for **others** to depend on.**
|
||
|
||
***Others*, not *everyone*.**
|
||
|
||
**So the tool as logged would have reported a correct repository as broken.** *An
|
||
anonymous fetch returning `404` is the intended outcome there*, **and a check that
|
||
treats anonymous reachability as success produces a false positive on every
|
||
deliberately-private publisher.**
|
||
|
||
**What survives:** *the defect that round found in `bedrock/starting.md` was real,
|
||
and it was never about `homelab-cluster`.* **A public document promised that a
|
||
private repository was the better answer**, *and the promise was the defect.*
|
||
**The link was removed; naming it as private, the way `.loom` names `settled`,
|
||
would also have answered it and would have kept the better document findable.**
|
||
*That call is loom's and it is already made.*
|
||
|
||
**Believed to advance:** *a check that cannot fail teaches nothing, and one that
|
||
fails on correct repositories is worse* — **it trains people to ignore it.**
|
||
|
||
**Belief that could be shown wrong:** *that anonymous is still the right default.*
|
||
**If most publication here is to private audiences, the default is wrong** and the
|
||
command should refuse to run without being told who to ask as.
|
||
|
||
## 2026-09-07 — measured: a read token, and what a host will tell you `marmalade`
|
||
|
||
**A gitea read token was issued for one session and destroyed after.** *Recorded
|
||
because these are facts about the host that nothing else here writes down.*
|
||
|
||
**A read token authenticates raw HTTP on a private repository.** *`404`
|
||
anonymously, `200` with `Authorization: token` or `Bearer`, and `If-None-Match`
|
||
returns `304` authenticated.* **So `add` and `check` work against private
|
||
repositories** — *the assumption the specimen made and nobody had run.*
|
||
|
||
**`/api/v1/repos/{o}/{r}` reports `admin, push, pull: true` for a token that
|
||
cannot write.** *Those are the account's rights.*
|
||
|
||
**But a refused write names the token's scopes in the error body.** *So:*
|
||
|
||
> **A token cannot be asked what it may do. It can only be told, by being
|
||
> refused.**
|
||
|
||
*Supersedes the specimen's "the name is the only record", which was too strong.*
|
||
**For this tool the rule is unchanged and the reason is different:** *`loomctl`
|
||
never writes, so it never provokes the only response that would tell it anything.*
|
||
|
||
**Belief that could be shown wrong:** *that gitea keeps doing this.* **The scope
|
||
list in an error body is not a documented interface** — *it is a message, and
|
||
messages change.*
|
||
|
||
## 2026-09-07 — the CLI has nouns, and one of them is not built `marmalade`
|
||
|
||
**Decided:** *`loomctl`, with subcommands.* **`loomctl external …` now; `loomctl
|
||
beads …` when it exists.**
|
||
|
||
**Believed to advance:** *`external` is what somebody else published that we depend
|
||
on.* **The verb `check` means the same thing wherever it appears — ask somebody
|
||
else whether what we believe is still true.**
|
||
|
||
**Declined: a plugin boundary.** *`kubectl` and `gh` grew plugins after somebody
|
||
outside wanted in.* **The seam gets built when it is a reported gap, not a designed
|
||
one** — *which is how loom says that arrives, with a name attached and a workaround
|
||
recorded beside it.*
|
||
|
||
**Declined: a field where a repository declares who it published for.** *It would
|
||
let a tool compare intent to result, and it asserts a category nobody decided.*
|
||
**The better version needs no declaration: derive it from the other side, by
|
||
reading everybody's `externals/` to see who actually depends on you.** *That is
|
||
`registry`'s shape.*
|
||
|
||
**Belief that could be shown wrong:** *that one noun justifies the structure.*
|
||
**Until `beads` exists, `loomctl external` is a subcommand with nothing to be
|
||
distinguished from.**
|
||
|
||
## 2026-09-07 — declined: `published check` `marmalade`
|
||
|
||
**Considered and rejected.** *The command that would ask whether somebody who is
|
||
not you can fetch what you published.*
|
||
|
||
**Because it simulates a consumer, and `registry` will have real ones.** *An
|
||
observed fetch beats a synthetic probe — the probe says a stranger **could** have
|
||
fetched it; a registry says somebody **did**.* **Building the probe first builds a
|
||
worse version of the thing that is coming.**
|
||
|
||
**And it removes the only thing complicating the credential story:** *`--as
|
||
<context>` existed almost entirely to serve this command.*
|
||
|
||
**Belief that could be shown wrong, two ways:**
|
||
|
||
- **`registry` never materialises**, *and nothing ever asks the question.*
|
||
- **`registry` records *declared* consumers rather than *observed* fetches.** *"Who
|
||
says they depend on us" is not "who can actually read us"* — **then this comes
|
||
back.**
|
||
|
||
*Cost of deciding: the command found three real problems in one afternoon, one of
|
||
which was a false positive it would have produced forever.* **The workaround is one
|
||
`curl`, and a real need with a cheap workaround is a gap rather than a missing
|
||
feature.**
|
||
|
||
## 2026-09-07 — kept, though nothing implements it `marmalade`
|
||
|
||
> **A check performed with your own credential proves nothing.** *It helps you
|
||
> complete alone an act that, by definition, you cannot* — **and then tells you it
|
||
> was fine.**
|
||
|
||
**Believed to advance:** *whoever builds `registry`'s consumer view needs this
|
||
before they design it*, **because the tempting implementation is for the publisher
|
||
to verify their own publication** — *the same mistake with a database behind it.*
|
||
|
||
**And it is cheap to detect:** *`/api/v1/user` gives the acting login, the
|
||
repository gives its owner.* **One request**, *and nobody thinks to make it.*
|
||
|
||
## 2026-09-07 — `external list` talks to git, measured `marmalade`
|
||
|
||
**Decided:** *`list` enumerates a publisher's `.loom/published/` by shelling out to
|
||
`git`* — **`clone --filter=blob:none --depth=1 --no-checkout`, then `ls-tree`.**
|
||
|
||
**Believed to advance, and measured across three hosts:**
|
||
|
||
```
|
||
git archive --remote gitea ok · github 422 · gitlab 404
|
||
partial clone gitea ok · github ok · gitlab ok 124–144KB
|
||
REST /api/v1/repos/{o}/{r}/contents/{p}
|
||
/repos/{o}/{r}/contents/{p} (api.github.com)
|
||
/api/v4/projects/{url-encoded}/repository/tree
|
||
```
|
||
|
||
**The argument is consistency, not thrift.** *One mechanism against three hosts
|
||
instead of three adapters* — **and REST `404`s on a private repository, which is
|
||
the case that matters.**
|
||
|
||
**The failure mode to build for:** *`--filter` is a server capability and git's
|
||
fallback is **silent***, warning `filtering not recognized by server` and
|
||
downloading everything. **`--depth=1` bounds it. Detect the warning and say so.**
|
||
|
||
**Belief that could be shown wrong:** *that shelling out stays simple.* **The first
|
||
time we need output `git` does not print stably, the library trade looks
|
||
different.**
|
||
|
||
## 2026-09-07 — nothing needs ssh, and nothing needs write access `marmalade`
|
||
|
||
**Decided:** *every transport is HTTPS on 443, and no token this tool is given
|
||
should carry `write:repository`.*
|
||
|
||
**Measured:** *partial clone with the blob filter works over gitea's **smart
|
||
HTTP***, so `list` never needed ssh — *it was built that way out of habit.*
|
||
|
||
**The tool writes only to the working tree** — *a fetched document, a lock, a
|
||
facet* — **and never commits or pushes.** *Committing and pushing are the person's,
|
||
under their own credentials.*
|
||
|
||
> *Same reason `cart` gives for an agent committing and not pushing:* **the
|
||
> consequences of a push land on people it cannot experience.** *A tool that can
|
||
> push is a tool that can publish, and publishing is an act somebody performs.*
|
||
|
||
**Implementation:** *never build `https://<token>@host/…`* — **git writes it into
|
||
`.git/config` and it surfaces in `git remote -v` and logs.** *Pass
|
||
`-c http.extraHeader="Authorization: token …"` per invocation instead.*
|
||
|
||
**Belief that could be shown wrong:** *that every host we meet allows token auth
|
||
over git-HTTPS.* **A host that does not would need ssh for `list` and nothing
|
||
else.**
|
||
|
||
## 2026-09-07 — untested, and load-bearing `marmalade`
|
||
|
||
**Nobody has confirmed that a gitea PAT scoped `read:repository` authenticates
|
||
*git over HTTPS*, not only the API and raw files.** *Those are different paths in
|
||
gitea; the token we had was measured only against the second.*
|
||
|
||
**Measured:** *an unauthenticated HTTPS clone of a private repository returns
|
||
`Failed to authenticate user`* — **so something is required, and which something is
|
||
unknown.**
|
||
|
||
**Recorded rather than assumed** *because it is the first thing to run against the
|
||
next token*, **and because if a read scope is not enough, that is the only argument
|
||
for keeping ssh.**
|
||
|
||
## 2026-09-07 — `--help` is the documentation surface `marmalade`
|
||
|
||
**Decided:** *`--help` is what a person or an agent actually runs when handed an
|
||
unfamiliar CLI, so it is the surface that has to be good* — **say what a command
|
||
does and what it will not do.**
|
||
|
||
**A `.loom/published/` for `loomctl` is deferred, not declined.** *The distinction
|
||
matters because `init` was declined.* **It earns publication when somebody depends
|
||
on this tool's behaviour rather than reading its help.**
|
||
|
||
## 2026-09-07 — decided by fallback: where the credential lives `marmalade`
|
||
|
||
**Nobody chose this; the fallback became the decision and that is recorded so it
|
||
can be found.**
|
||
|
||
**One file in `~`, kubeconfig-shaped, holding hosts and credentials together.**
|
||
*Never in a repository, and nothing that reads from a repository.*
|
||
|
||
**Believed to advance:** *the config is no longer only a secret* — **it is how you
|
||
talk to a host at all**, *API shape, ssh port if it ever matters, which transport a
|
||
command uses.* **Some of that is not secret and several people would want the same
|
||
values.**
|
||
|
||
> **The split is expected and was not done:** *a host list that can be committed,
|
||
> and a credential file that cannot.* **It was named rather than decided.**
|
||
|
||
**Belief that could be shown wrong:** *that one file is fine until somebody
|
||
shares.* **The first time two people want the same host list, the file has to
|
||
split**, *and doing it later means moving a file people have already written by
|
||
hand.*
|
||
|
||
## 2026-09-07 — Go, and `git` on `PATH` `marmalade`
|
||
|
||
**Decided:** *`loomctl` is written in Go.*
|
||
|
||
**Believed to advance:** *it gets installed on other people's machines — a laptop,
|
||
a cluster node, a CI runner* — **and a single static binary is the difference
|
||
between "download it" and "first install a runtime."** *The work is precise HTTP
|
||
header control, shelling out to `git`, and reading files; the standard library does
|
||
the first and third with no dependencies.*
|
||
|
||
**Considered: TypeScript on npm.** *A real distribution channel and the familiar
|
||
one.* **Not chosen because it puts a runtime in front of a tool meant to be run
|
||
casually and often** — *including by agents in loops that pay start-up cost every
|
||
time.*
|
||
|
||
**Consequence: `git` must be on `PATH`.** *`list` shells out rather than using a
|
||
library, because the behaviour measured across three hosts **is** git's behaviour —
|
||
filter support, the silent fallback, the user's existing credential helpers.*
|
||
**`list` is the only command that needs it, which makes `list` the seam if this
|
||
ever bites.**
|
||
|
||
**Belief that could be shown wrong:** *that distribution is the deciding
|
||
constraint.* **If `loomctl` only ever runs in one or two places, the binary
|
||
advantage is worth little and the choice was made on a cost nobody pays.**
|
||
|
||
## 2026-09-07 — a wrinkle in the isolation role `marmalade`
|
||
|
||
**`osprey` cast the isolation role as: the cart on the default branch, the work on
|
||
a branch named for the cart.** *It assumed the work happens **during** the round.*
|
||
|
||
**Both rounds so far have been design rounds that decide work happening
|
||
**after**.** *So the branch is named for the round that authorised it, and outlives
|
||
the cart that named it.*
|
||
|
||
**Recorded rather than fixed.** *The role still works; its justification was
|
||
written for a case that has not yet occurred.* **If a round ever does produce code
|
||
while it is open, nothing here changes.**
|
||
|
||
## 2026-09-07 — `loomctl external` exists, and its first run reconciled two documents `marmalade`
|
||
|
||
**Built: `list`, `add`, `check`.** *Go, no dependencies outside the standard
|
||
library, `git` shelled out for `list` only.*
|
||
|
||
**The first real run did the thing the tool is for.** *Eight documents were
|
||
adopted by hand before it existed; `check` reported all eight `unlocked`, and
|
||
`add` locked them* — **and two came back changed**, `bedrock/starting.md` *and*
|
||
`cart/cart.md`. **Neither change would have been noticed by anybody.**
|
||
|
||
**Believed to advance:** *the lock is what makes `check` possible at all.* **With
|
||
no lock there is nothing to compare and the only honest report is `unlocked`** —
|
||
*and the tool refuses to invent one by adopting whatever the remote currently
|
||
serves.*
|
||
|
||
**Belief that could be shown wrong:** *that a per-person path derived from the URL
|
||
is good enough.* **`<host>/<owner>/<repo>/<name>.md` guesses that the first two
|
||
path segments name an owner and a repository**, *which is true of gitea, GitHub and
|
||
GitLab and is not a rule.* **`--path` exists for when it is wrong.**
|
||
|
||
## 2026-09-07 — supersedes the isolation role: a cart is not committed `marmalade`
|
||
|
||
**`cart` is now `v1` and it changed the thing we cast a role on.**
|
||
|
||
> **So the cart is not committed.** *It lives in the working tree of the machine
|
||
> the two presences share, and `.loom/cart/` is ignored by version control.*
|
||
|
||
**The reason is not tidiness:** *a committed cart grows a third file by itself.*
|
||
**The two-file rule defends against somebody asking for one; version control does
|
||
not require anybody to ask** — *anyone who can clone can add a third, and the
|
||
agreement's test is never invoked because nobody had the conversation.*
|
||
|
||
**And it is what makes a round end.** *Committed, a cart is gone from the tree and
|
||
permanent in history* — **so "gone" means "no longer live" and the negotiation
|
||
stays quotable forever.** *Ignored, gone means gone.*
|
||
|
||
**So `.loom/cart/` is now in `.gitignore`.** *The isolation role entry above
|
||
assumed the cart lived on the default branch; **the cart lives on no branch.***
|
||
*What survives of that entry is the other half: work is isolated on a branch named
|
||
for the round that authorised it.*
|
||
|
||
**Not undone: `osprey` and `marmalade` are already in this repository's history.**
|
||
*Rewriting history to honour a rule adopted afterwards would cost more than it
|
||
buys*, **and the two rounds are quotable forever, which is exactly what `v1` says
|
||
not to want.** *Recorded rather than repaired.*
|
||
|
||
**Belief that could be shown wrong, and it is a real conflict:** *the annotation
|
||
protocol this repository works under says **commit before dissolving, git is the
|
||
only archive of the conversation.*** **An ignored cart has no archive**, so
|
||
dissolving a notes file destroys the annotations outright. *One of the two is
|
||
wrong and it is not ours to settle.*
|
||
|
||
## 2026-09-07 — what the planted change actually proved `marmalade`
|
||
|
||
**`cart.md` was changed upstream deliberately, without telling us, to see whether
|
||
the tool would notice.** *It did — and the sequence is worth recording, because
|
||
the obvious reading is wrong.*
|
||
|
||
**`check` did not catch it.** *All eight documents were unlocked, and `unlocked`
|
||
means **I cannot tell you**.* **It was `add` that revealed the change, by
|
||
overwriting the file** — *and the only reason anybody saw what had changed is that
|
||
`git` happened to be watching the working tree.*
|
||
|
||
> **So the mechanism is proven and the workflow is not.** *A conditional request
|
||
> against a lock works. **A document nobody has locked is a document nobody is
|
||
> checking**, and it stays that way silently.*
|
||
|
||
**Decided, as a consequence:** *`add` now reports when it replaces local content
|
||
that differs from what the publisher is serving.* **It used to say only
|
||
`adopted`.** *A copy that differs is the only evidence that something moved while
|
||
the document was unlocked, and destroying it silently is how a change nobody saw
|
||
becomes a change nobody can find.*
|
||
|
||
**Belief that could be shown wrong:** *that a note is enough.* **`add` still
|
||
overwrites** — *it does not stash the old bytes anywhere* — **and outside a git
|
||
working tree the note tells you something was lost without letting you see it.**
|
||
*If that bites, `add` needs `--dry-run` or a refusal.*
|
||
|
||
## 2026-09-07 — the unowned half: somebody has to run it `marmalade`
|
||
|
||
**Nothing here answers *when* `check` runs.**
|
||
|
||
*`bedrock` says it about running systems and it is just as true of this:*
|
||
**nothing serves the truth, so the only mechanism is somebody looking.** *The tool
|
||
makes looking cheap; it does not make it happen.*
|
||
|
||
**Recorded as a need with no owner rather than a feature**, *because the answers
|
||
are all outside the tool* — **a git hook, a CI job, an agent's session start, a
|
||
scheduled run** — *and choosing one here would put a scheduler inside a fetcher and
|
||
a comparator.*
|
||
|
||
**Belief that could be shown wrong:** *that staying out of it is right.* **If in
|
||
practice nobody ever runs `check` unaided, a tool that only reports when asked is
|
||
a tool that reports nothing**, *and the thing we declined to build is the thing
|
||
that was needed.*
|
||
|
||
## 2026-09-07 — a changed external is a polad, which the specimen said first `marmalade`
|
||
|
||
**Decided:** *`check` stages what moved into `.loom/cart/current/polad/`*, **a
|
||
candidate artifact shaped exactly like what it would become**, *whose exits are
|
||
apply or discard.*
|
||
|
||
**This was in the specimen and we lost it.** *"A changed external becomes a polad
|
||
in the cart, and somebody decides."* **The round that discarded the specimen
|
||
discarded this with it**, *and it came back only because somebody asked what the
|
||
stash should be.*
|
||
|
||
**Believed to advance:** *`externals` says reconciliation runs the other way* —
|
||
**given what changed in theirs, what do we change in ours** — *and the facets
|
||
usually survive while the manifests, the config and the code that a usage named
|
||
are what move.* **So staging prints the `.usages.md` beside it**, *which is the
|
||
file that names what to go fix*, **and says so when there is none**: *a usage that
|
||
does not name what it justifies is half a usage, and no usage at all is a document
|
||
nothing records a dependency on.*
|
||
|
||
**With no cart open, `check` reports what moved 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.**
|
||
|
||
**Belief that could be shown wrong:** *that requiring an open cart is right.* **If
|
||
most changes arrive when no round is open, the useful behaviour is the one that
|
||
never runs**, *and the polad needs somewhere else to live.*
|
||
|
||
## 2026-09-07 — supersedes "add says what it replaced": add does not replace `marmalade`
|
||
|
||
**`add` adopts what is not here, and refuses what is already adopted.** *The
|
||
earlier entry made `add` announce an overwrite; **it no longer overwrites at
|
||
all**.*
|
||
|
||
**Believed to advance:** *a command that both adopts and re-fetches is a command
|
||
that overwrites the only evidence a change happened.* **Splitting them gives each
|
||
one job** — *`add` adopts, `check` notices.*
|
||
|
||
**One exception, and it is the only way out of a dead end:** *a document that is
|
||
present but **unlocked** was fetched by hand before the tool existed.* **Nothing
|
||
records its origin and the path does not round-trip, so `check` cannot ask about
|
||
it and `add` refusing would strand it forever.** *So `add` accepts it, and the
|
||
bytes decide:*
|
||
|
||
- **identical** → *the lock is written and nothing is rewritten.* **The assertion
|
||
a lock makes — this local copy is the one being served — is then verified rather
|
||
than assumed**, *which was the whole objection to adopting a remote `ETag`
|
||
blindly.*
|
||
- **different** → *staged as a polad; the local copy is left alone*, **because a
|
||
copy that differs is the only evidence that something moved while nothing was
|
||
watching.**
|
||
|
||
*Measured on this repository: eight documents adopted by hand, all eight locked
|
||
with nothing rewritten.*
|
||
|
||
## 2026-09-07 — `apply`, because the lock is the half a person forgets `marmalade`
|
||
|
||
**Decided:** *`loomctl external apply [path…]` moves a staged polad into place and
|
||
moves its lock with it.*
|
||
|
||
**Believed to advance:** *applying by hand is one `mv`, and it leaves a lock
|
||
describing the copy you just replaced* — **which is exactly the drift the lock
|
||
exists to prevent.** *The polad carries the `ETag` that was served with the bytes
|
||
somebody reviewed, so applying locks what was actually read rather than whatever
|
||
the publisher serves at apply time.*
|
||
|
||
**This is not the tool fixing anything.** *It executes a decision a person already
|
||
made, one document at a time, after the report.*
|
||
|
||
**Belief that could be shown wrong:** *that two exits are enough.* **For an
|
||
external, discard does not mean the change goes away** — *the upstream moved
|
||
whatever we do* — **so discarding is really "knowingly stale", and nothing
|
||
currently records that choice.** *If people discard often, that silence is the
|
||
next defect.*
|
||
|
||
## 2026-09-07 — declined: a separate `adopt` verb `marmalade`
|
||
|
||
**Considered:** *`loomctl external adopt <path> <url>`* — **a command whose job is
|
||
to say "this copy came from there"**, *for a document that is present but
|
||
unlocked.* **Both parties reached the deadlock independently and this was the
|
||
other way out.**
|
||
|
||
**Not done, because the byte comparison makes the verb unnecessary.** *Whatever
|
||
the command is called, it must not trust the claim* — **it has to fetch and
|
||
compare, because the whole point is that nobody knows whether the local copy is
|
||
still a copy.** *Once it does that, it is `add` with an origin supplied, and the
|
||
user's intent in both cases is the same sentence: **I depend on this document and
|
||
here is where it lives.***
|
||
|
||
**And a verb earns its place by naming an act, not a state.** *"Present but
|
||
unlocked" is a condition a repository is temporarily in* — **a permanent command
|
||
for it advertises a transitional situation as a normal one.**
|
||
|
||
**Belief that could be shown wrong:** *that the condition is transitional.* **If
|
||
people and agents keep dropping documents into `externals/` by hand — and they
|
||
will — it is a recurring category and not a migration**, *and then it deserves its
|
||
own word, discoverable in `--help` rather than found in a hint.*
|
||
|
||
*The domain word survives regardless: `externals` says adopting is fetching a URL,
|
||
and the tool says `adopted` throughout.*
|
||
|
||
## 2026-09-07 — the credential is not needed here, and is not gone `quince`
|
||
|
||
**`jeffry/homelab-cluster` is public and `jeffry/homelab-impl` is private.**
|
||
*Adoption flows public → private, so nothing this repository depends on requires a
|
||
credential, and `loomctl` has been exercised end to end without one.*
|
||
|
||
**That does not remove the PAT. It moves when you need it.** *`externals` now
|
||
says: **do not adopt from a source less readable than the repository you are
|
||
adopting into.*** *Which sorts the cases:*
|
||
|
||
- **a public repository adopting** — *may only adopt public documents*, **so it
|
||
never needs a credential**, *and needing one is the signal that something is
|
||
wrong.*
|
||
- **a private repository adopting private documents** — *legitimate, and needs
|
||
one.*
|
||
|
||
> **So needing a credential stopped being a capability and became a signal.** *The
|
||
> tool cannot tell the two apart — it sees that the source is private and cannot
|
||
> see who may read the repository the copy lands in* — **which is exactly why
|
||
> `add` warns rather than deciding.**
|
||
|
||
**Belief that could be shown wrong:** *that public-adopting-public covers the
|
||
common case.* **The moment a private repository wants to depend on another private
|
||
one, the credential is required and untested.**
|
||
|
||
## 2026-09-07 — status change: the git-over-HTTPS assumption is unexercised, not blocking `quince`
|
||
|
||
**An earlier entry records that nobody has confirmed a read-scoped PAT
|
||
authenticates git over HTTPS, and calls it load-bearing. It is no longer
|
||
blocking.**
|
||
|
||
*It bears on `external list` against a private repository, which is the
|
||
private-to-private case above and does not occur here.* **The path is unexercised
|
||
rather than untested-and-in-the-way.**
|
||
|
||
**Recorded because the code did not change and its status did**, *which is the
|
||
kind of thing only a log says.* **It stays the first thing to run against the next
|
||
token.**
|
||
|
||
## 2026-09-07 — the completeness case has a mechanism: it is the facet `quince`
|
||
|
||
**The open question was: a `200` tells you a document moved and says nothing about
|
||
whether your casting still covers it.**
|
||
|
||
**`check` prints the document's `.usages.md` when it stages a polad**, *and for an
|
||
agreement that is the file its roles are cast in* — **so the question is answered
|
||
by reading the facet the tool just pointed at.**
|
||
|
||
**It failed on the first real change because `externals.md` had no facet at all**,
|
||
*and the tool said `no .usages.md — nothing records what depends on this`:* **a
|
||
correct report and useless as a prompt.**
|
||
|
||
**So both were written.** *`externals.usages.md` names which Go file implements
|
||
which rule — an unusual usage, because this repository implements the convention
|
||
rather than using it — and `cart.usages.md` gained the `v1` casting it was missing.*
|
||
|
||
**Belief that could be shown wrong:** *that pointing is enough.* **Nothing checks
|
||
that a facet was updated, or that it was even read.**
|
||
|
||
## 2026-09-07 — decided: nothing committed announces an open cart `quince`
|
||
|
||
**A cart is local and untracked, so a clone cannot see that a round is open.**
|
||
*That is the boundary and not a defect:* **`v1` makes the cart local to the
|
||
working tree the two presences share, and somebody who has only cloned is by
|
||
construction not one of them.**
|
||
|
||
**`.gitignore` records that carts happen here. Nothing records that one is open**,
|
||
*and the asymmetry is deliberate* — **a mechanism for announcing something
|
||
designed to be ephemeral is the first step in it not being ephemeral.**
|
||
|
||
**Belief that could be shown wrong:** *that the shared working tree is the right
|
||
unit.* **If two presences ever work from different machines, the cart has nowhere
|
||
to live**, *and everything above stops being true at once.*
|
||
|
||
## 2026-09-07 — conversion now destroys, so the log is the only record `quince`
|
||
|
||
**Under `cart/v1` the cart is untracked, so converting a round deletes its dailies
|
||
outright.** *Every previous conversion left them in git.*
|
||
|
||
**So an entry that was not written before the `rm` is gone**, *and the write-ahead
|
||
log is the mechanism* — **stage as you go, because at conversion the cart is the
|
||
only copy and you are about to delete it.**
|
||
|
||
**And it sharpens a conflict recorded in `cart.usages.md`:** *the annotation
|
||
protocol here says commit before dissolving because git is the only archive.*
|
||
**With an ignored cart there is no archive, so dissolving a notes file destroys
|
||
the annotations outright.** *Both documents are loom's; this is where an adopter
|
||
can see the collision.*
|
||
|
||
## 2026-09-07 — declined for now: reference-only adoption `quince`
|
||
|
||
**`externals` offers two ways out of the confidentiality rule.** *We implement
|
||
neither, and this records why the first is not built.*
|
||
|
||
**Reference-only is mechanically small:** *a lock with no file. `check` never
|
||
touches the copy — it sends `If-None-Match` and reads the status — so the only
|
||
code that changes is telling deliberate absence from loss, which is one optional
|
||
field in the lock.* **The facets stay**, *which is the ownership line drawn
|
||
exactly: the facet is ours, the document is theirs.*
|
||
|
||
**The cost is larger than the document suggests, and it is why this is worth an
|
||
entry rather than a `TODO`:** *with a copy, `CHANGED` gives you a diff, and today
|
||
the diff was the whole answer.* **Reference-only keeps no old bytes, so it tells
|
||
you *that* a document moved and never *what* moved** — *and the question
|
||
`externals` says reconciliation asks is a question about the delta.*
|
||
|
||
> **So it is not adoption minus offline reading. It is adoption minus
|
||
> reconciliation-by-diff.**
|
||
|
||
**Not built because we have no instance.** *Everything this repository adopts is
|
||
public.* **And the convention's other exit — ask them to publish — is the one that
|
||
actually occurred**: *`homelab-cluster` went public and the problem dissolved.*
|
||
**The better exit made the worse one unnecessary in the only case we have had.**
|
||
|
||
**Belief that could be shown wrong:** *that the case stays hypothetical.* **A
|
||
private repository here depending on another private one makes it real** —
|
||
*`homelab-impl` is the obvious candidate* — **and then the design above is a few
|
||
hours.**
|
||
|
||
*The warning now names both exits, including the one we have not built. **Telling
|
||
somebody a rule and not the way out of it is how a guardrail becomes something
|
||
people route around.***
|
||
|
||
## 2026-09-07 — measured: only gitea resolves its own short raw URL `rowan`
|
||
|
||
**A lock must record a resolved URL** — *a short form follows whatever the default
|
||
branch is at the time you ask, so a branch rename reports as a change in the
|
||
document.* **Adopting by name means building that URL, and the three hosts do not
|
||
behave alike:**
|
||
|
||
```
|
||
gitea /{owner}/{repo}/raw/{path} 303 -> /raw/branch/main/{path} resolved
|
||
github raw.githubusercontent.com/{o}/{r}/HEAD/{path} 200, no redirect NOT resolved
|
||
gitlab /{owner}/{repo}/-/raw/HEAD/{path} 200, no redirect NOT resolved
|
||
```
|
||
|
||
**`HEAD` works on all three and resolves on none.** *It would put a moving ref in
|
||
the lock, which is the hazard we recorded and then nearly built.*
|
||
|
||
**So gitea's redirect does the work, and the other two need the branch resolved
|
||
first** — *`git ls-remote --symref <url> HEAD`, which is one round trip and no
|
||
clone.*
|
||
|
||
**Belief that could be shown wrong:** *that gitea keeps redirecting.* **The short
|
||
form is a convenience, not a documented interface**, *and if it stops we resolve
|
||
the branch the same way as everybody else.*
|
||
|
||
## 2026-09-07 — supersedes "git is needed for `list` only" `rowan`
|
||
|
||
**Adopting by name against github or gitlab needs `git ls-remote` to resolve the
|
||
default branch.** *Against gitea it does not, because the redirect answers the
|
||
same question.*
|
||
|
||
**So the earlier claim — `list` is the only command that needs `git`, which makes
|
||
`list` the seam — is now wrong for two of three flavors.** *The seam is unchanged
|
||
in practice here, where everything is gitea, and the entry would otherwise read as
|
||
still true.*
|
||
|
||
*Recorded because the code changed in a way that quietly moved a boundary somebody
|
||
might rely on.*
|
||
|
||
## 2026-09-08 — measured: the private path works, and nothing is unexercised now `rowan`
|
||
|
||
**Supersedes the entry that recorded the git-over-HTTPS assumption as unexercised.
|
||
It has been run.** *A read-scoped token, issued for one session and destroyed
|
||
after.*
|
||
|
||
```
|
||
git clone --filter=blob:none --depth=1 over HTTPS + token, private repo OK, 124KB
|
||
loomctl external list jeffry/homelab-impl OK, authenticated
|
||
short raw URL + 303, authenticated 404 anon -> 200, resolved branch
|
||
If-None-Match across the redirect, authenticated 304
|
||
```
|
||
|
||
**So a private repository can adopt from another private one**, *by name and by
|
||
URL, with `check` working off the lock afterwards.* **Every path in the tool has
|
||
now been run at least once.**
|
||
|
||
*Incidentally confirmed: `homelab-impl` publishes nothing and adopts six documents
|
||
from `homelab-cluster`, each with a `.usages.md`.* **Consuming inward, which is
|
||
the arrangement that made the confidentiality hazard impossible in that shape.**
|
||
|
||
## 2026-09-08 — the tool is a mast, not a lock `rowan`
|
||
|
||
**Recorded because it decides a question that keeps coming back, and nothing in
|
||
the code says it.**
|
||
|
||
> **`loomctl` grants no access.** *It reads what your credentials already let you
|
||
> read.* **Anything it does, a person could do with copy and paste** — *and the
|
||
> locks mean nothing outside the tool and the discipline of the agreement.*
|
||
|
||
**So it warns and does not refuse.** *The point is not to make the wrong thing
|
||
impossible; it is to make it deliberate* — **the same move as annotating a file
|
||
you could simply edit.**
|
||
|
||
**Which is also why `add <full-url>` stays**, *even though it can reach outside a
|
||
publisher's `.loom/published/` and is, in that form, a small general-purpose file
|
||
fetcher.* **A general-purpose tool is not the aim**, *so the escape hatch is kept
|
||
and made to say what it is, rather than removed.*
|
||
|
||
**What the tool adds over copy and paste is not restriction. It is the record** —
|
||
*origin, resolved branch, `ETag`.* **A pasted document has no provenance**, *so
|
||
nobody can later ask where it came from or whether we were allowed to have it* —
|
||
**not because the question is hard, but because the evidence is gone.**
|
||
|
||
## 2026-09-08 — the confidentiality check is coarse, deliberately `rowan`
|
||
|
||
**`add` now asks whether *this* repository is readable anonymously**, *by resolving
|
||
`origin` and making one unauthenticated request.* **So the warning fires when the
|
||
source is private and the destination is public, rather than on every adoption a
|
||
private repository performs** — *which was the previous behaviour and would have
|
||
been noise in exactly the workflow that is legitimate.*
|
||
|
||
**Belief that could be shown wrong, and it is a known blind spot rather than a
|
||
guess:** *the signal distinguishes public from not-public and nothing finer.*
|
||
**Two repositories private to different people is the case where private-to-private
|
||
genuinely widens access, and this check cannot see it** — *so the tool says so, in
|
||
the message, instead of implying a verdict it has not earned.*
|
||
|
||
**Failing open is deliberate:** *no origin, or an unparseable one, reports "I
|
||
cannot tell" and warns.* **"Cannot tell" must never read as "not public."**
|
||
|
||
## 2026-09-08 — three gaps closed, and the workaround they justified is now the rule `rowan`
|
||
|
||
**`publication`, `recording-decisions` and `cart` were amended in answer to gaps
|
||
filed from here.** *Closed at reconciliation rather than at repair: each stayed
|
||
true of the copy we held until `check` reported `200`.* **The gap files for the
|
||
first two are deleted; `cart.gaps.md` keeps four of five.**
|
||
|
||
**What survives is what they justified, and in every case it is the same shape —
|
||
something we invented locally became specified behaviour, unchanged in practice
|
||
and entirely changed in standing:**
|
||
|
||
- **`wal.md`.** *We built it before `cart/v2` named it, and only because it was
|
||
suggested to us.* **`v2` cites that:** *a practice that works only when somebody
|
||
is standing there to suggest it is not a practice.*
|
||
- **"A fetch you did not perform."** *Ours was a declined command and an insight
|
||
kept without an implementation; it is now the test in `publication`.*
|
||
- **The compaction rule**, *including the four kinds that may never be dropped,*
|
||
**plus one addition we did not think of: a compaction is itself a decision and
|
||
gets an entry.**
|
||
|
||
## 2026-09-08 — declined: fixing `annotating` `rowan`
|
||
|
||
**Four real gaps stand unfixed, deliberately.** *It is the most-depended-on
|
||
document here and a revision costs every adopter an afternoon.*
|
||
|
||
**Two of the four are not gaps in the agreement at all.** *The rule exists — the
|
||
source hash that detects a violated freeze, and **dissolve** as the definition of
|
||
what responding is — **but it lives in a local editor protocol and not in the
|
||
agreement**.* **That needs a decision about where a rule lives, which is not an
|
||
amendment and is not ours.**
|
||
|
||
**Local answers keep running:** *verify the source hash and the anchors before
|
||
responding, and say so if they disagree.*
|
||
|
||
## 2026-09-08 — a casting file goes stale on amendments that add no role `rowan`
|
||
|
||
**Twice in one day.** *`cart/v1` and `cart/v2` each invalidated nothing we had
|
||
cast and each left `cart.usages.md` incomplete.*
|
||
|
||
> **`check` reports that a document moved and cannot say whether your casting
|
||
> still covers it.** *The mechanism is that it prints the `.usages.md` beside a
|
||
> staged polad, which it did, both times.* **It points; it cannot make anybody
|
||
> read.**
|
||
|
||
**Belief that could be shown wrong:** *that pointing is enough.* **Nothing checks
|
||
that a facet was updated, or that it was opened.**
|
||
|
||
*Related, and the reason this entry can be trusted: this round arrived headed
|
||
`rowan` in a file named for a round that had already converted, with no `name`
|
||
file.* **The name is what tags durable entries**, *so a mismatch mis-files the
|
||
record permanently.* **The heading won; it is recorded in `cart.usages.md`.**
|
||
|
||
## 2026-09-08 — declined: a round on the third-file question `rowan`
|
||
|
||
**Not opened, and not sent upstream as a gap either.** *Left open in
|
||
`cart.gaps.md` where it was filed.*
|
||
|
||
**Because it may not be a category.** *Ours appeared because a round was converted
|
||
while somebody was still writing, which the agreement already forbids.* **A third
|
||
file that only ever arrives as a conversion-timing failure has its fix upstream**,
|
||
*and the choice between moving it and rejecting it never has to be made.*
|
||
|
||
> **Before writing the rule, count how many arrive any other way.**
|
||
|
||
*One instance is not a sample* — **which is the reason `cart/v2` gave for leaving
|
||
it alone, applied one step further.**
|
||
|
||
## 2026-09-08 — `orient`, and declined: a second orientation file `sorrel`
|
||
|
||
**Decided:** *one generated file, `.loom/orientation.md`, for whoever arrives
|
||
next.* **It restates no rule** — *every rule it might repeat is owned by a
|
||
document already in the working tree, and a copy of a rule is a copy that goes
|
||
stale.*
|
||
|
||
**Declined: splitting it into an externals half and a publishing half.** *The
|
||
splitting rule is **generate what varies, adopt what does not*** — **and the
|
||
publishing half varies not at all and is already adopted**, *so `publication.md`
|
||
appears in the index like any other adopted document, in exactly the repositories
|
||
that adopted it.* **A second command would emit a file whose whole content is a
|
||
pointer to a file already in the tree**, *and would revive the `published` noun
|
||
that went with `published check`.*
|
||
|
||
**Declined: `.loom/externals/README.md` as the path**, *which was measured rather
|
||
than argued:*
|
||
|
||
```
|
||
$ loomctl external check
|
||
README.md unlocked no origin recorded — ...
|
||
```
|
||
|
||
**`check` walks that directory for adopted documents, so a generated file there
|
||
reports as unlocked in every repository using the feature, forever.** *And the
|
||
deeper reason is the invariant: everything under `.loom/externals/` is somebody
|
||
else's document, which is what makes "do not edit these" a rule you can state in
|
||
four words.*
|
||
|
||
**The `.usages.md` is pointed at, not summarised.** *What depends on a document is
|
||
free prose, and anything that extracts a claim from prose will eventually extract
|
||
it wrong.*
|
||
|
||
**Belief that could be shown wrong:** *that a fixed path is worth more than a
|
||
configured one.* **It only pays if it is the same everywhere**, *which is not ours
|
||
to decide and is with `bedrock`.*
|
||
|
||
## 2026-09-08 — the lock records visibility, and the word it will not use `sorrel`
|
||
|
||
**Decided:** *the lock carries what the source could be read as when it was
|
||
fetched, and `check` audits it against this repository's visibility now.*
|
||
|
||
**Believed to advance:** *access is verified once, at fetch, and the copy is
|
||
durable.* **Whether an adoption is still legitimate rests on the relative
|
||
visibility of two repositories — a fact somebody can change with a checkbox a year
|
||
later, without ever seeing the adoption.** *Recording it turns a silent permanent
|
||
hazard into something that runs.*
|
||
|
||
**The value is `public` or `not-public`, and never `private`.** *An anonymous
|
||
request tells those two apart and nothing finer* — **so it cannot see two
|
||
repositories private to different people, which is the case where adopting between
|
||
private repositories genuinely widens access.** *The word `private` would claim
|
||
precision the signal does not have.*
|
||
|
||
**Belief that could be shown wrong:** *that the coarse signal is worth having.*
|
||
**If the cases that matter here are all private-to-private, this alarm will never
|
||
fire and the field is ceremony.**
|
||
|
||
## 2026-09-08 — a probe is not a fetch, and confusing them clears alarms `sorrel`
|
||
|
||
**Recorded because the bug was one line and the trap is permanent.**
|
||
|
||
*Visibility was computed by one function used in two places.* **At `add` time,
|
||
"no credential is configured" means the fetch that just succeeded was anonymous,
|
||
so the source is public** — *sound.* **In the audit, nothing is being fetched:
|
||
"no credential configured" says nothing about whether a stranger could read the
|
||
URL**, *and returning `public` there would have **silently cleared real
|
||
alarms**.*
|
||
|
||
> **The worst failure available to a check is to report that it found nothing.**
|
||
|
||
**Probing is now its own function that always asks with no credential at all**,
|
||
*because what matters is what a stranger can read and not what we can.* **Found by
|
||
testing the alarm path rather than after shipping it.**
|
||
|
||
## 2026-09-08 — `orient`, not `prompt`, and why that was mine to notice `sorrel`
|
||
|
||
**Renamed before it shipped.** *`prompt` reads as "produce something to feed a
|
||
model", and the file is a table of contents a person can read.*
|
||
|
||
**The correction was right and the assumption behind it was mine:** *`prompt` was
|
||
not being used in the narrow sense, and I took it at the narrowest reading
|
||
available.* **Second time in two days** — *the first was reading `others` as
|
||
`everyone` in `publication.md`, which cost a command that was built, validated
|
||
three times, and then declined.*
|
||
|
||
*Recorded as a working note rather than a decision: **when a word could be read
|
||
narrowly or broadly, this repository has evidence that I pick narrow.***
|
||
|
||
## 2026-09-08 — published: what the tool guarantees `tansy`
|
||
|
||
**`.loom/published/guarantees.md`**, *and step 4 of `publication` was run: `200`
|
||
anonymously, off no credential of ours.*
|
||
|
||
**The earlier decision did not reverse; its condition fired.** *`.loom/published/`
|
||
was recorded as deferred rather than declined, on the condition that somebody
|
||
depends on the tool's behaviour rather than reading its help.* **A docs site whose
|
||
build calls `add`, `check` and `apply` is that consumer.**
|
||
|
||
*The delta is not the command list — that is a copy of what the code owns.*
|
||
**`--help` says what the tool does; a published page says what will keep being
|
||
true**, *and a reader cannot learn from a binary which of its behaviours survives
|
||
the next version.*
|
||
|
||
**The page includes what is not promised**, *which is the half a reimplementation
|
||
needs most:* **the command surface, the orientation file's format across
|
||
versions, the precision of the visibility signal, and that anything is checked
|
||
when nobody is looking.**
|
||
|
||
## 2026-09-08 — we do not promise the surface; we promise the change lands `tansy`
|
||
|
||
**A consumer's README tells a person to type `loomctl external add loom/<repo>
|
||
<doc>.md`.** *Nothing parses it — which is exactly why no check would catch a
|
||
rename.* **The thing that moved would not be a document anybody adopted.**
|
||
|
||
**Promising the surface would freeze the CLI. So the promise is narrower and
|
||
runs:**
|
||
|
||
> **We do not promise the surface holds. We promise a change to it lands on the
|
||
> page you already lock.**
|
||
|
||
*`guarantees.md` carries an append-only `Surface changes` section*, **so a rename
|
||
reports `200` to every consumer the day it happens.**
|
||
|
||
**Belief that could be shown wrong:** *that anybody remembers to write in it.*
|
||
**Nothing enforces the section** — *the same weakness as a facet nobody updates.*
|
||
|
||
## 2026-09-08 — two ETags can be compared and never ordered `tansy`
|
||
|
||
**Found by accident, and it is a consequence of a rule we published this
|
||
morning.**
|
||
|
||
*The docs site stamped a hand-written guide with the `ETag` it was read against,
|
||
and its banner said `written against an older guarantees`.* **It was backwards:
|
||
the guide was ahead of the site's copy, not behind.**
|
||
|
||
**And it cannot be fixed by trying harder.** *An `ETag` is opaque — that is the
|
||
point of it, and we guarantee we never interpret one.* **So a consumer can know
|
||
that two versions disagree and can never know which is behind.**
|
||
|
||
*The banner now claims no direction.* **Anybody building freshness on this
|
||
inherits the same limit**, *and it belongs in the log because it follows from the
|
||
guarantee rather than from anybody's code.*
|
||
|
||
*Consequence nobody predicted: reconciling cleared the banner with no
|
||
acknowledgement, which is correct — an acknowledgement is for a guide that is
|
||
behind, and this one was ahead.*
|