Files
loom-cli/.loom/cart/current/claude-marmalade.md
T
jeffryandClaude Opus 5 92529fc5f2 marmalade: the third file was my conversion, not his timing
cart says do not convert while someone is mid-thought, ask whether they are ready,
and say so if you convert anyway. I asked and got approval of a plan, which is not
the same as having nothing left to write — so the check passed and was still
wrong. Reported upward: are you ready to convert and is there anything you are
still writing are different questions, and only the second would have caught it.

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

157 lines
7.6 KiB
Markdown

# claude — cart `marmalade`
**`osprey` converted, so this is a new round.** *Its name is meaningless and I
picked it arbitrarily; change it if you like.*
---
## Two mechanical things before the design
**Your text arrived as `jeff-osprey.md` and I have moved it, unedited, to
`loom-marmalade.md`.** *`cart` allows two files and gives the test:* **what
happens to this file when the person changes?** *`jeff-…` dies with Jeff, so it is
a person and not a stake* — **and loom renamed itself to `loom` last round for
exactly this reason**, *saying that if you wrote here you would write into that
file.* **Not a correction of you; the shape predicted it and the shape was right.**
**And it landed in a cart that had converted**, *which is fine — it opened the next
one.* **Nothing travelled silently:** *everything below is reintroduced with a
fresh reason, and the design that survived `osprey` is in
[`.loom/event-log.md`](../../event-log.md) rather than in a file I am carrying
forward.*
## `loomctl`, and the noun that is missing
**Subcommands, yes.** *And your grouping exposes something the log got wrong.*
**`osprey` named three commands — `pull`, `check`, `reachable` — as if they were
one family. They are two.**
```
loomctl external add | check things somebody else published, that we depend on
loomctl published check things we published, that somebody else depends on
```
> **`reachable` is not an `external` operation at all.** *It looks at
> `.loom/published/`, which is our surface and not anybody's dependency.* **Under
> your grouping it has no home, and that is the grouping doing its job.**
*`publication.md` already draws this line — **published is a change of kind** — and
the CLI having a noun for each side is the same distinction wearing a verb.*
**I would drop the word `reachable` and make it `loomctl published check`**, *so
that `check` means one thing in both nouns: **ask somebody else whether what we
believe is still true.*** *Against an external it asks the publisher; against our
own, it asks as a stranger.*
## `external list` is the first command that needs to know what a host is
**Everything in `osprey` is host-agnostic on purpose.** *A conditional `GET`
behaves identically on gitea and GitHub — we measured it.* **`list` is not like
that.** *Enumerating `.loom/published/` means asking a service what is in a
directory, and every host answers differently or not at all.*
**That is the real cost of the command, and I think it is worth paying**, because
of what it buys:
- **The origin path stops being parsed.** *You type a repository and a document
name; the tool builds the URL and writes it to the lock.* **The problem that ate
two passes of `osprey` disappears by never arising.**
- **It is discovery**, *which the log records as the thing a fetcher cannot do*
**and your `gitea.md` story is exactly the failure it fixes.** *I could not have
pulled that document, because I did not know it existed.*
> **You said there is no registry yet, so you are the registry.** *`external list`
> is the smallest thing that stops that being true.*
## Select: what does `external list` talk to?
- **each host's REST API** — *gitea `/api/v1/repos/{o}/{r}/contents/.loom/published`,
GitHub `/repos/{o}/{r}/contents/…`, one adapter per host*
- **git itself** — *a treeless or shallow fetch, then `ls-tree` the published
directory; the same protocol everywhere, no adapters, no API tokens*
- **a published index** — *the convention grows a file listing the surface, and
`list` is a fetch like every other command*
*My lean is **git itself**, and I want to be honest that it is not obviously
right.* **git is the one API every one of these hosts already speaks**, *and we
have working ssh here* — **so `list` needs no host adapter and no second auth
story**, *which collapses most of your `auth` section.* **The costs are real:** *it
is heavier than one request, it needs git credentials rather than a read token,
and it fetches more than it needs to answer.*
**Fallback if unanswered:** *I build it against git, behind a small seam, and log
that the REST adapter is the fallback if the git path is too slow or too
credentialed for a public repository.*
## Auth: kubeconfig-shaped is right, and it is not only credentials
**Your instinct holds for a reason the specimen missed.** *`osprey` assumed one
token because it assumed one host.* **The moment there are several, the per-host
thing is not just a secret — it is *how you talk to this host at all***: *its API
shape, whether ssh is on a strange port, which transport `check` should use.*
**That is a config, and kubeconfig is the right silhouette:** *named contexts, one
current, everything addressable by name.*
**Two things I would carry forward from `osprey` into it:**
**A token cannot be asked what it may do** — *gitea's `/api/v1/repos/…` reports the
**account's** rights, not the token's* — **so the name is the only record of its
scope**, and the config should make naming unavoidable rather than optional.
**And it lives in `~`, never in a repository.** *A file that holds hosts and
tokens together will be tempting to commit the first time two people need the same
host list.* **Splitting it — a committed host list, a private credential file — is
the obvious answer and I have not thought it through**, *so I am naming it rather
than deciding it.*
## Affirm: subcommands now, plugins not yet?
**I would not build a plugin boundary before `beads` exists.**
*`kubectl` and `gh` both grew plugins after they had users and after somebody
outside wanted in.* **Asserting the seam now means guessing where it goes**, *and
`osprey` just spent a round on what a confident guess costs* — **it is the same
argument that killed `init`: a boundary nobody has felt asserts something nobody
decided.**
**Fallback if unanswered:** *subcommands only, with the internals arranged so that
`external` and `published` do not share state.* **If that stays true, the plugin
seam is discovered rather than designed.**
## Question: does `loomctl` document itself the way loom asks everyone else to?
**Your first requirement — legible to an AI or a human — is loom's own thesis
pointed at loom.** *So the test is available: **this repository has a
`.loom/published/`, and `loomctl published check` can be run against it.***
*I think that is the acceptance test for the whole tool, and it is nicer than any
unit test I would otherwise write.* **The tool's first real use is on itself, and
its second is `homelab-cluster`'s `gitea.md`** — *the document I needed and could
not read.*
**Fallback if unanswered:** *I write `.loom/published/` for `loomctl` as I build,
and treat `--help` as a rendering of it rather than a second source of truth.*
## Addendum: the third file was mine, not yours
**You were writing it before I converted.** *Which makes this the other rule in
`cart`, and it lands on me:*
> **Do not convert while someone is mid-thought. Ask whether they are ready. If
> you convert alone anyway, say so.**
**I did ask, and you said the plan was good, and you were mid-thought anyway.**
*So the check passed and was still wrong* — **approving a plan is not the same as
having nothing left to say**, and the question as `cart` phrases it invites the
first answer when it wants the second.
*Worth reporting upward: **"are you ready for me to convert?" and "is there
anything you are still writing?" are different questions**, and only the second one
would have caught this.*
**Nothing was lost** — *the shelf would have caught it and it did not need to,
because you simply wrote it again into the next round.* **Which is the mechanism
working.**