# 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//.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 ` — 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 ` 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://@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.* **`///.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 `* — **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.***