marmalade converts: eleven entries, and no shelf
Moves the wal into the log and closes the round. Nothing was unresolved: the one open item — where the credential lives — is recorded as decided by fallback rather than shelved, since cart says silence is a usable reply and that a fallback which quietly becomes the decision is worth being able to find later. The round decided the shape (loomctl, nouns, no plugin seam until somebody reports a gap, no audience declaration), declined published check on the registry argument with both its falsifiers, kept the insight that outlives it, settled list on git with the three-host measurement behind it, established that nothing needs ssh and nothing needs write access anywhere, chose Go, and recorded git-on-PATH as the consequence. Two entries carry things nobody decided. One is the untested assumption that a read-scoped PAT authenticates git over HTTPS and not only the API and raw files, which is the first thing to run against the next token and the only argument that would bring ssh back. The other is a wrinkle in the isolation role: it assumed work happens during a round, and both rounds so far have decided work that happens after, so a branch is named for the round that authorised it and outlives the cart. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018UTxuSizozEA8yDitPuris
This commit is contained in:
@@ -1,328 +0,0 @@
|
||||
# claude — cart `marmalade`
|
||||
|
||||
**`osprey` converted, so this is a new round.** *Its name is meaningless and I
|
||||
picked it arbitrarily.*
|
||||
|
||||
**Rewritten to dissolve your annotations.** *The prompts below are answered rather
|
||||
than asked, and what is still open says so.*
|
||||
|
||||
---
|
||||
|
||||
## How the round opened
|
||||
|
||||
**Your notes arrived as `jeff-osprey.md`, into a cart that had converted.** *Moved
|
||||
unedited to `loom-marmalade.md`, because `cart` allows two files and gives the
|
||||
test — **what happens to this file when the person changes?*** *`jeff-…` dies with
|
||||
you, so it is a person and not a stake, and loom renamed itself to `loom` last
|
||||
round saying that if you wrote here you would write into that file.*
|
||||
|
||||
**And the timing was mine, not yours.** *`cart` says do not convert while somebody
|
||||
is mid-thought; I asked, you approved a plan, and approving a plan is not the same
|
||||
as having nothing left to write.*
|
||||
|
||||
> **You called that the design rather than the defect, and you are right.** *There
|
||||
> is a deliberate buffer — **better to make a mess occasionally, inside the cart,
|
||||
> than to carry rules strict enough to prevent it.*** **The shelf exists for this
|
||||
> and did not even need to be used**, *because you simply wrote it again into the
|
||||
> next round.*
|
||||
|
||||
*So it is recorded as the mechanism working, not as something to fix.*
|
||||
|
||||
---
|
||||
|
||||
## Settled this round
|
||||
|
||||
### `published check`, and why it cannot report a verdict
|
||||
|
||||
**`reachable` is gone as a word.** *The command is `loomctl published check`, so
|
||||
that `check` means one thing on both sides: **ask somebody else whether what we
|
||||
believe is still true.***
|
||||
|
||||
**And the test is not "is this public".** *I built it that way and you broke it:*
|
||||
**`homelab-cluster` publishes six documents and is private on purpose.**
|
||||
*`publication.md` says what a repository has handed over for **others** to depend
|
||||
on* — **others, not everyone**, *and I read one word as the other.*
|
||||
|
||||
**So the command reports the identity it used, never a verdict:**
|
||||
|
||||
```
|
||||
gitea.md 200 as anonymous
|
||||
gitea.md 200 as reader
|
||||
gitea.md 200 as jeffry — OWNER, this proves nothing
|
||||
```
|
||||
|
||||
> **The third line is what earns the command.** *`publication.md` says publishing
|
||||
> is not an act you can complete alone* — **and a tool that verifies your
|
||||
> publication with your own credential has helped you complete it alone, and told
|
||||
> you it was fine.**
|
||||
|
||||
*Which makes your `auth` section load-bearing: **a kubeconfig-shaped config is not
|
||||
only credentials, it is named identities you can test as.*** **`--as <context>` is
|
||||
the whole feature.**
|
||||
|
||||
### No audience declaration
|
||||
|
||||
**Confirmed: no field where a repository states who it published for.** *It would
|
||||
let the tool compare intent to result, and it asserts a category nobody has
|
||||
decided* — **the argument that killed `init`.**
|
||||
|
||||
**`--as` defaults to anonymous and the owner warning is always on.**
|
||||
|
||||
*Your future version is better than a declaration and does not need one:* **project
|
||||
it from the other side — read everybody's `externals/` and derive who is actually
|
||||
depending on you.** *Nothing now; it is `registry`'s shape, and `registry` is
|
||||
deliberately not built.*
|
||||
|
||||
### `external list` talks to git
|
||||
|
||||
**Measured across three hosts.** *`git archive --remote` is out — gitea allows it,
|
||||
GitHub `422`s, GitLab `404`s.* **Partial clone is uniform:**
|
||||
|
||||
```
|
||||
git clone --filter=blob:none --depth=1 --no-checkout then git ls-tree HEAD:.loom/published
|
||||
|
||||
gitea filter ok .git=124KB
|
||||
github filter ok .git=124KB
|
||||
gitlab filter ok .git=144KB
|
||||
|
||||
jeffry/homelab-cluster 0s 124KB over ssh, private
|
||||
databases.md gitea.md private-access.md
|
||||
public-access.md recovering-data.md storage.md
|
||||
```
|
||||
|
||||
**REST would have been three dialects, not one with variations:**
|
||||
|
||||
```
|
||||
gitea /api/v1/repos/{o}/{r}/contents/{path}
|
||||
github /repos/{o}/{r}/contents/{path} (api.github.com)
|
||||
gitlab /api/v4/projects/{url-encoded}/repository/tree
|
||||
```
|
||||
|
||||
**The argument for git is consistency, not thrift.** *One mechanism against three
|
||||
hosts instead of three adapters* — **and REST `404`s on the 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 the damage, which is why both flags
|
||||
are there.** *Detect the warning and say so, rather than letting `list` quietly
|
||||
become a full clone of somebody's monorepo.*
|
||||
|
||||
### Transports, honestly
|
||||
|
||||
| | transport | credential |
|
||||
|---|---|---|
|
||||
| **`external list`** | git | *ssh key or https creds you already have* |
|
||||
| **`external add`, `external check`** | HTTP | **a read token, for private repos** |
|
||||
| **`published check`** | HTTP | ***not* the owner's — that is the point** |
|
||||
|
||||
**Two credentials per host, not one.** *I claimed earlier that git collapsed most
|
||||
of the `auth` story; it does not.* **`add` and `check` want HTTP because freshness
|
||||
is a conditional request** — *a `304` is a few hundred bytes and a partial clone is
|
||||
124KB, so checking eight externals over git would cost a megabyte to learn
|
||||
nothing.*
|
||||
|
||||
### Subcommands now; plugins when somebody reports a gap
|
||||
|
||||
**`loomctl external …`, `loomctl published …`, and `loomctl beads …` when it
|
||||
exists.**
|
||||
|
||||
**No plugin boundary yet**, *and your framing is better than my reasoning was:*
|
||||
**the seam gets built when it is a reported gap, not a designed one.** *`kubectl`
|
||||
and `gh` both grew plugins after somebody outside wanted in* — **and a gap is
|
||||
exactly how loom says that arrives**, *with a name attached and a workaround
|
||||
recorded beside it.*
|
||||
|
||||
### `--help` is the documentation surface; `.loom/published/` is later
|
||||
|
||||
**You are right about what actually happens:** *hand somebody an unfamiliar CLI and
|
||||
they run `--help`.* **That is the discovery surface, so it is the one that has to
|
||||
be good** — *for a person and for an agent, which mostly means the same thing:
|
||||
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 and this is not.* **When somebody depends on
|
||||
this tool's behaviour rather than reading its help, that is when it has a contract
|
||||
worth publishing** — *and `published check` gets to be run against its own
|
||||
repository, which is a nicer acceptance test than anything I would write.*
|
||||
|
||||
---
|
||||
|
||||
## Measured, so nobody re-derives it
|
||||
|
||||
**A read token, issued for one session and since deleted.**
|
||||
|
||||
**It authenticates raw HTTP on a private repository.** *`404` anonymous, `200` with
|
||||
`Authorization: token` **or** `Bearer`, and `If-None-Match` returns `304`
|
||||
authenticated.* **So `add` and `check` work on private repositories** — *the
|
||||
assumption the specimen made and nobody had run.*
|
||||
|
||||
**The gitea `ETag`/blob coincidence holds on private repositories too.** *Which
|
||||
changes nothing, because we do not use it.*
|
||||
|
||||
**And the specimen's sharpest claim needed correcting, by you making me try the
|
||||
write:**
|
||||
|
||||
```
|
||||
POST …/contents/… 403
|
||||
required=[write:repository]
|
||||
token scope=read:activitypub,…,read:repository,read:user
|
||||
```
|
||||
|
||||
*`/api/v1/repos/{o}/{r}` reports `admin, push, pull: true` for a token that cannot
|
||||
write — those are the **account's** rights.* **But the refusal names the scope.**
|
||||
|
||||
> **A token cannot be asked what it may do. It can only be told, by being
|
||||
> refused.**
|
||||
|
||||
**No other channel exists:** *`/api/v1/users/{u}/tokens` rejects token auth
|
||||
outright, and no scope header comes back on success.* **For us this changes the
|
||||
reason and not the rule** — *`loomctl` never writes, so it never provokes the one
|
||||
response that would tell it anything.* **Surface gitea's error bodies rather than
|
||||
replacing them**, *since they carry the only scope information the host emits.*
|
||||
|
||||
---
|
||||
|
||||
## Still open: where the credential lives
|
||||
|
||||
**The token is deleted and we said we would decide this soon. It is the one thing
|
||||
this round has not answered.**
|
||||
|
||||
**What `osprey` fixed and still holds:** *it lives in `~`, never in a repository,
|
||||
and it is named for its scope because the name is the only record.*
|
||||
|
||||
**What is new, and what makes it harder:** *the config is no longer only a secret.*
|
||||
**It is how you talk to a host at all** — *API shape, ssh on a strange port, which
|
||||
transport `check` should use, and now **named identities to test as***. *Some of
|
||||
that is not secret and several people would want the same values.*
|
||||
|
||||
> **The obvious answer is to split it:** *a host list that can be committed, and a
|
||||
> credential file that cannot.* **I have not thought it through and I am naming it
|
||||
> rather than deciding it.**
|
||||
|
||||
**Fallback if unanswered:** *one file in `~`, kubeconfig-shaped, holding both — and
|
||||
an entry recording that the split is expected and was not done yet.* **Nothing in
|
||||
the repository, and nothing that reads from the repository**, *so that the mistake
|
||||
we would most regret is the one that cannot happen by accident.*
|
||||
|
||||
---
|
||||
|
||||
## Corrections I made this round
|
||||
|
||||
**Kept together, because there are three and the pattern is the point.**
|
||||
|
||||
**I claimed git collapsed the auth story.** *It does not — I liked my answer.*
|
||||
|
||||
**I confirmed the token claim enthusiastically without testing the case that would
|
||||
show it wrong.** *You made me try the write and the refusal named the scopes.*
|
||||
|
||||
**I built `published check` on anonymous fetchability.** *Publication has an
|
||||
audience and the audience is not always everyone.*
|
||||
|
||||
> **All three are the same shape: a result that agreed with me, and no test of the
|
||||
> case that would not have.** *It is also exactly what `osprey` found in the
|
||||
> `ETag` story* — **a verified fact is much louder than a rule** — *and I have now
|
||||
> done it three times in a day while writing that sentence down.*
|
||||
|
||||
*Not an apology. **A thing to point a test at**, and the reason the log carries a
|
||||
belief rather than a conclusion.*
|
||||
|
||||
---
|
||||
|
||||
## Reviewed: what needs ssh, and what needs write
|
||||
|
||||
**Short answer: ssh, nothing. Write access, nowhere — including here.**
|
||||
|
||||
### Every operation, and what it actually needs
|
||||
|
||||
| | what it does | transport | credential |
|
||||
|---|---|---|---|
|
||||
| **`external list`** | *partial clone, `ls-tree` the published dir* | **git over HTTPS** | *read PAT* |
|
||||
| **`external add`** | *`GET` the raw file, record URL + `ETag`* | **HTTPS** | *read PAT* |
|
||||
| **`external check`** | *conditional `GET`, `If-None-Match`* | **HTTPS** | *read PAT* |
|
||||
| **`published check`** | *`GET` our own published file* | **HTTPS** | ***not ours*** |
|
||||
| **everything it writes** | *files in the working tree* | **none** | **none** |
|
||||
|
||||
**Measured just now: `git clone --filter=blob:none --depth=1 --no-checkout` over
|
||||
`https://` on gitea** — *filter accepted, 124KB, `ls-tree` lists the published
|
||||
directory.* **I had built and measured `list` over ssh out of habit, and it never
|
||||
needed it.**
|
||||
|
||||
### The tool never writes over the network, so it needs no write credential
|
||||
|
||||
**Including in this repository.** *`loomctl` puts files in the working tree — a
|
||||
fetched document, a lock, a facet — and stops.* **Committing and pushing are yours,
|
||||
under your own credentials.**
|
||||
|
||||
> *That is the 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 supposed to be an act
|
||||
> somebody performs.*
|
||||
|
||||
**So `write:repository` should never appear on any token this tool is given**, *and
|
||||
a token that has it is a token we would rather not hold.*
|
||||
|
||||
### The one thing a single PAT cannot do
|
||||
|
||||
**`published check` needs an identity that is not the owner's, and yours is
|
||||
yours.** *That is not an ssh problem and it is not solvable by scoping — **it is
|
||||
the command's entire point.***
|
||||
|
||||
**Two workable identities, both HTTPS:**
|
||||
|
||||
- **anonymous** — *the right answer when the audience is everyone*
|
||||
- **a second credential belonging to the actual audience** — *a reader account, or
|
||||
a token from somebody in the group you published for*
|
||||
|
||||
*And if you hold neither, the honest output is **"checked as owner, proves
|
||||
nothing"**, which is still worth printing.*
|
||||
|
||||
### One thing untested, and it is the load-bearing one
|
||||
|
||||
**Whether a gitea PAT scoped `read:repository` authenticates *git over HTTPS*, not
|
||||
just the API and raw files.** *They are different paths in gitea, and the deleted
|
||||
token was only ever measured against the second.* **Measured: an unauthenticated
|
||||
HTTPS clone of the private repository returns `Failed to authenticate user`**, *so
|
||||
something is required; nobody has confirmed that a read PAT is enough.*
|
||||
|
||||
**First thing to run against the next token.** *If it turns out git-over-HTTPS
|
||||
needs a broader scope than raw HTTP, that is an argument for keeping ssh for `list`
|
||||
after all* — **and it would be the only argument.**
|
||||
|
||||
### An aside that is too neat not to record
|
||||
|
||||
**`:2222` cost me three tool calls and a guess, and the tool does not need it.**
|
||||
*Every transport here is HTTPS on 443.* **The fact that bit me is a fact for
|
||||
people cloning repositories, not for `loomctl`** — *which is its own small lesson
|
||||
about who a published document is for.*
|
||||
|
||||
### Implementation note: keep the token out of the URL
|
||||
|
||||
**Do not build `https://<token>@host/…`.** *git writes the remote into
|
||||
`.git/config`, and it turns up in logs and in `git remote -v` afterwards.*
|
||||
|
||||
**Pass it per-invocation instead:**
|
||||
|
||||
```
|
||||
git -c http.extraHeader="Authorization: token <t>" clone --filter=blob:none …
|
||||
```
|
||||
|
||||
*Nothing persisted, nothing in the repository.* **Which is the same rule as the
|
||||
credential file: the mistake we would most regret is the one that happens by
|
||||
accident.**
|
||||
|
||||
---
|
||||
|
||||
## `published check` is declined, and there is a `wal.md` now
|
||||
|
||||
**Staged in [`wal.md`](wal.md) rather than here**, *so conversion is a move and not
|
||||
a rewrite.* **Four entries: the decline and its two falsifiers, the insight that
|
||||
outlives the command, Go, and `git`-on-`PATH` as the consequence of `list`.**
|
||||
|
||||
**Your registry argument is the one that carries it**, *not "we do not need it
|
||||
yet".* **A probe simulates a consumer; a registry with named consumers has real
|
||||
ones** — *and an observed fetch beats a synthetic one.*
|
||||
|
||||
*Not a rationalisation, on one condition: **that the falsifiers are written down
|
||||
where somebody will hit them**, which is what makes it a decline rather than a
|
||||
thing we talked ourselves out of. **They are in the `wal`.***
|
||||
@@ -1,12 +0,0 @@
|
||||
So, this CLI...
|
||||
|
||||
## auth
|
||||
|
||||
There is a possibility that we could be reading from different git hosts (github, gitea, gitlab, etc.) so just one PAT might not be the right call. (I keep seeing something kube config shaped?)
|
||||
|
||||
## Shape of CLI
|
||||
|
||||
- Must be documented in a way that is legible to an AI or human
|
||||
- This is the beginning of a project that is under the umbrella called "loom". Makes me think this CLI should at the very least be implemented heavily with subcommands, or even plugins, kubectl or gh style. This might be something like `loomctl external [args]`, etc. Another (future) tool will be `loomctl beads [args]`.
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
marmalade
|
||||
@@ -1,93 +0,0 @@
|
||||
# Write-ahead log — cart `marmalade`
|
||||
|
||||
**Staging for `.loom/event-log.md`.** *Things decided mid-round that must survive
|
||||
conversion, written in the shape an entry wants so that converting is a move and
|
||||
not a rewrite.*
|
||||
|
||||
**This file is not a daily and is not annotated.** *If something here is wrong, say
|
||||
so in your daily and I will change it.*
|
||||
|
||||
---
|
||||
|
||||
## Declined: `published check`
|
||||
|
||||
**Decided:** *not built.* **The `published` noun does not ship; `loomctl` has
|
||||
`external` and, later, `beads`.**
|
||||
|
||||
**Believed to advance:** *`published check` **simulates** a consumer.* **A registry
|
||||
with named consumers has real ones**, *and an observed fetch beats a synthetic
|
||||
probe — the probe says a stranger **could** have fetched it, the registry says
|
||||
somebody **did**.* **Building the probe first is building 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;* **without it the config
|
||||
is one read credential per host**, *which is where we wanted to land anyway.*
|
||||
|
||||
**Belief that could be shown wrong, two ways:**
|
||||
|
||||
- **`registry` never materialises.** *Then nothing ever asks this question, and the
|
||||
failure it catches goes on being invisible.*
|
||||
- **`registry` records *declared* consumers rather than *observed* fetches.** *A
|
||||
declared consumer does not answer the question at all* — **"who says they depend
|
||||
on us" is not "who can actually read us"** — *and then this comes back.*
|
||||
|
||||
**What it cost to decide:** *the command found three real problems in one
|
||||
afternoon, one of which turned out to be a false positive it would produce
|
||||
forever.* **The workaround is one `curl`.** *A real need with a cheap workaround is
|
||||
a gap, not a missing feature.*
|
||||
|
||||
## Keep, even though nothing implements it
|
||||
|
||||
**The insight outlives the command and belongs in the log on its own:**
|
||||
|
||||
> **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**, *which is the same mistake with a database
|
||||
behind it.*
|
||||
|
||||
**Measured, and worth keeping with it:** *`/api/v1/user` gives the acting login and
|
||||
the repository gives its owner, so **"am I checking as myself" is one request and
|
||||
not a hard problem.*** *It is a cheap check that nobody thinks to make.*
|
||||
|
||||
## Decided: Go
|
||||
|
||||
**Decided:** *`loomctl` is written in Go.*
|
||||
|
||||
**Believed to advance:** *this is a tool that gets installed on other people's
|
||||
machines — a laptop, a cluster node, a CI runner — and **a single static binary
|
||||
with no runtime is the difference between "download it" and "first install a
|
||||
runtime".*** *The work is HTTP with precise header control, shelling out to `git`,
|
||||
and reading files; Go's standard library does the first and third with no
|
||||
dependencies at all.*
|
||||
|
||||
**Considered: TypeScript on npm.** *`npm i -g` is a real distribution channel and
|
||||
the familiar one.* **Not chosen because it puts a runtime between the user and a
|
||||
tool whose whole job is to be run casually and often**, *including by agents in
|
||||
loops where start-up cost is paid every time.*
|
||||
|
||||
**Belief that could be shown wrong:** *that distribution is the deciding
|
||||
constraint.* **If `loomctl` ends up only ever running in one or two places, the
|
||||
binary advantage is worth little** *and the choice was made on a cost nobody pays.*
|
||||
|
||||
## Consequence: `git` must be on `PATH`
|
||||
|
||||
**`external list` shells out to `git`** — *`clone --filter=blob:none --depth=1
|
||||
--no-checkout`, then `ls-tree`* — **rather than using a git library.**
|
||||
|
||||
**Believed to advance:** *the behaviour we measured across gitea, GitHub and GitLab
|
||||
is `git`'s behaviour*, **including `--filter` support, the silent fallback when a
|
||||
server refuses it, and the user's existing credential helpers.** *A library
|
||||
reimplements all of that and would have to be re-measured.*
|
||||
|
||||
**So the tool has a runtime dependency after all**, *just not a language one.*
|
||||
**`list` is the only command that needs it** — *everything else is HTTP* — **which
|
||||
makes `list` the seam if `git`-on-`PATH` ever becomes a problem.**
|
||||
|
||||
**Belief that could be shown wrong:** *that shelling out stays simple.* **The first
|
||||
time we need to parse `git`'s output for something it does not print stably**, *the
|
||||
library trade looks different.*
|
||||
@@ -322,3 +322,199 @@ 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.**
|
||||
|
||||
Reference in New Issue
Block a user