a round, four externals, and the spec as a specimen

The repository is furnished rather than scaffolded — every file has content and a
reason, which is the distinction the spec itself draws when it says to resist
building init as a scaffolder.

Four externals fetched and locked under .loom/externals/: bedrock, externals,
annotating, cart. Copies, not to be edited, with a usages file beside cart
casting three of its four roles and leaving one open. The one left open is what
isolates work in progress, and the usages file says how we cast it wrong once and
why the belief was more useful than a preference would have been.

The spec moves out of the README and into the cart as a specimen, which makes it
discard-by-default — that is a stronger statement of "argue with this" than a
sentence asking them to. bedrock and externals stay non-discardable, since
accommodating them is what makes this a loom tool.

The daily says where we expect to be wrong: whether check can say anything useful
about a 404, and whether hash-as-lock survives a fetch that normalises anything,
which we have only tested against gitea.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-07 12:29:53 -04:00
co-authored by Claude Opus 5
parent 304b854dbd
commit 81c4917b9f
13 changed files with 863 additions and 81 deletions
@@ -0,0 +1,65 @@
# claude-substrate — cart `osprey`
**Opened before you arrived**, so that the first thing here is a round rather
than a briefing.
---
## What is in this repository already
**Four externals, fetched and locked**, under `.loom/externals/`. *`bedrock` is
the primitives, `externals` is the convention you are implementing, `cart` and
`annotating` are how we will work together.* **They are copies. Do not edit
them** — a facet goes beside a file, never into it.
**One specimen: [`proposed-spec.md`](specimens/proposed-spec.md).** *It is the
tool as we imagined it, and **a specimen is discard-by-default** — it belongs to
this repository and you may throw it away without asking us.* **That is not
politeness; it is what a specimen is.**
> **`bedrock` and `externals` are not discardable.** *Accommodating them is what
> makes this a loom tool rather than some other thing.* **Read them as given.
> Argue with the specimen.**
## What we think this is
**A fetcher and a comparator, and it should stay one.** *Every act in the model is
a file in somebody's repository — publishing is writing one, adopting is fetching
a URL, reporting a gap is writing one.* **Nothing sends a service a request.**
**The idea that makes it small:** *on gitea, a raw file's `ETag` **is** the git
blob hash of that file.* **So there is nothing to record** — hash the local copy,
compare to the remote's `ETag`, done. *Verified on both a public and a private
repository.*
## Where we expect to be wrong
**Where the conventions chafed.** *A thing you had to do twice. A rule you worked
around to make a command sane.* **Friction is data about us, not a failure of
yours**, and most of it never gets reported because it reads the other way.
**Whether `check` can say anything useful about a `404`.** *Over HTTP, "gone" and
"you lost access" are the same response.* **We decided to report the ambiguity
rather than guess** — *if that is annoying in practice, it is worth knowing.*
**Whether the hash-as-lock survives contact.** *It assumes the local copy is
byte-identical to the remote. **A fetch that normalises anything breaks it**, and
we have not tested a proxy, a CDN, or a host that is not gitea.*
## What we would ask you not to do
**Do not build `init` as a scaffolder.** *Four empty directories assert four
things nobody has decided, and **a file that carries no delta should not
exist.*** *If `init` earns its place, it creates one file and asks one question.*
---
## Question: what isolates your work while a round is open?
*The one role we left uncast — see
[`cart.usages.md`](../../externals/git.hypertheory-labs.dev/loom/cart/cart.usages.md).*
## Affirm: is the `ETag`-as-blob-hash assumption load-bearing enough to verify first?
*If it does not hold, most of the specimen collapses and it is better to find out
in an afternoon than in a week.*
+1
View File
@@ -0,0 +1 @@
osprey
@@ -0,0 +1,85 @@
# loom-cli
**Not built yet.** *This is the spec, written while it was fresh.*
**A small tool for the operations a person should not do by hand:** *fetch a
document you depend on, and find out when it changed.*
---
## The one idea that makes it small
**On gitea, a raw file's `ETag` is the git blob hash of that file.** *Verified on
both a public and a private repository — the header and `git rev-parse` return the
same value.*
> **So there is nothing to record.** *`git hash-object <the local copy>` **is** the
> lock. Compare it to the remote's `ETag` and you have your answer.*
**No lock file, no state, no `pull` metadata to drift.** *And it catches a case we
had not considered: **if somebody edits the local copy, the hash stops matching
and `check` reports it** — which is correct, because an adopted copy that has been
edited is no longer a copy of anything.*
## Commands
### `loom check`
**For every file under `.loom/externals/`:** *compute its hash, `HEAD` its source,
compare.*
| result | means |
|---|---|
| **hashes match** | nothing changed |
| **hashes differ** | **upstream moved** — the new copy is a candidate, not a replacement |
| **local hash differs from what the remote had at last fetch** | *somebody edited a copy that is not ours* |
| **`404`** | **gone, or access lost — these are indistinguishable over HTTP.** *Say so; do not pick one.* |
**Report. Do not fix.** *A changed external becomes a polad in the cart, and
somebody decides.*
### `loom pull <url>`
**Fetch one document into `.loom/externals/<host>/<path>`.** *The path mirrors the
URL, so nothing has to record an origin.*
**Do not create a `.usages.md`.** *An empty facet asserts that we have something
to say and we do not — **a file that carries no delta should not exist.***
### `loom publish <path>`
**Verify a document in `.loom/published/` is fetchable by somebody who is not
you.** *One anonymous request.*
> **Publishing is not an act you can complete alone.** *If nobody can fetch it,
> nothing happened, and `published/` is a directory named after a promise.*
### `loom init` — **resist this**
**Its temptation is to scaffold**, and four empty directories assert four things
you have not decided. *The useful version creates `.loom/README.md` and asks one
question.* **Creating the tree is not a feature.**
## Credentials
**Read over HTTP with a read-only token. Write over ssh.** *Split by operation,
not by whether a repository is public.*
**Name the token for its scope — `loom-read`.** *A token cannot be asked what it
may do: `/api/v1/repos/…` reports the **account's** rights, not the token's.
**Scope is set once at creation and invisible afterwards, so the name is the only
record.***
*Token in `~`, never in a repository.*
---
## Why so little
*Every act in the model is a file in somebody's repository.* **Publishing is
writing one; adopting is fetching a URL; reporting a gap is writing one; homing it
is an `mv`.** *Nothing sends a service a request.*
**So this is a fetcher and a comparator, and it should stay one.** *If it starts
holding state, ask what it knows that no repository knows — that is the thing that
will rot.*