Files
loom-cli/.loom/event-log.md
T
jeffryandClaude Opus 5 24610ba353 marmalade: publication is not publicness, and a token tells you by refusing
Jeff's correction breaks published check rather than denting it. homelab-cluster
publishes six documents and is private deliberately; publication.md says others,
not everyone, and I read one word as the other. The tool as logged would have
reported a correct repository as broken, so a superseding entry is added rather
than revising the osprey entry. What survives is that bedrock's public page
promising a private repository as the better answer was a real defect, and it was
never about homelab-cluster.

So published check must not report a verdict. It reports the identity it used, and
warns when that identity is the repository's own owner — publishing is not an act
you can complete alone, and a tool that verifies with your own credential has
helped you complete it alone and told you it was fine.

Also corrects me: I confirmed the specimen's token claim enthusiastically without
testing the case that would show it wrong. A refused write names the token's
scopes in the error body, so a token cannot be asked what it may do but can be
told by being refused. Verified that no other channel exists — the tokens endpoint
rejects token auth, and no scope header comes back on success.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018UTxuSizozEA8yDitPuris
2026-09-07 14:13:44 -04:00

16 KiB

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 confidencewhich 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.mdthe 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 303s /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 ritualand 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 transportand 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 questionand 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 invisiblea 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 ETagsthe exact thing the convention forbidsand 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'sthey 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 itthe 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 itand 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 purposeit 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 worseit 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 repositoriesthe 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 interfaceit is a message, and messages change.