loomctl external: list, add, check — and its first run found two changed documents

Go, standard library only, with git shelled out for list alone. list enumerates a
publisher's .loom/published by partial clone and ls-tree; add fetches one document,
writes it under .loom/externals and records the resolved origin and the publisher's
ETag in .loom/externals/.locks; check asks conditionally and reports.

The first real run did what the tool exists for. All eight documents adopted by
hand before it existed reported unlocked — the tool refuses to invent a lock by
adopting whatever the remote currently serves, since that would assert the local
copy is the one being served, which is the thing it was about to check. Locking
them fetched two that had moved: bedrock/starting.md, which now says the worked
example is private and will not link to something you cannot fetch, and cart.md,
which went to v1.

cart v1 changes a role we cast: a cart is not committed, because a committed cart
grows a third file by itself — version control does not require anybody to ask, so
the two-file rule is never invoked — and because ignored, gone means gone. Adds
.loom/cart/ to .gitignore and supersedes the isolation entry rather than editing
it. osprey and marmalade are already in history and are left there: rewriting to
honour a rule adopted afterwards costs more than it buys.

Records the conflict this creates rather than settling it: the annotation protocol
here says commit before dissolving because git is the only archive, and an ignored
cart has no archive, so dissolving would destroy the annotations outright.

Credentials are read-only, per host, and passed to git through the environment
rather than argv, because argv is visible to every process on the machine.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018UTxuSizozEA8yDitPuris
This commit is contained in:
2026-09-07 14:48:29 -04:00
co-authored by Claude Opus 5
parent f0b3269610
commit 1543df0a0c
12 changed files with 897 additions and 7 deletions
+52
View File
@@ -518,3 +518,55 @@ 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.*