Two decisions from cart osprey in loom-cli, and this repository had nowhere to record them until now. 404 is named as two answers wearing one status, filed as a gap using this document's own test and correctly ours to close. And the correction that matters: a facet describes the local pair. A gap is true of the copy you hold rather than the document upstream, so a publisher fixing their end does not close it — we nearly deleted a consumer's .gaps.md on exactly that mistake. It follows from the thing this convention rests on, that the copy is theirs and everything beside it is ours, which we had not followed through to reconciliation. A gap closes at reconciliation, not at repair. With the consequence we would not have reached: what survives is not the gap but what the gap justified. A workaround is often not retired by a fix — it stops being a workaround and becomes specified behaviour, unchanged in code and entirely changed in status, which is invisible where the code is and therefore an entry in the consumer's own log. And a closed gap is not a decline: opposite sign, and filing one as the other puts a thing you wanted into a list of things you rejected. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
100 lines
4.2 KiB
Markdown
100 lines
4.2 KiB
Markdown
# Externals
|
|
|
|
**How a repository records what it depends on, and finds out when it changes.**
|
|
|
|
## Pull what you use
|
|
|
|
**You fetch a copy of somebody's document and keep it** at
|
|
`.loom/externals/<host>/<path>.md`. *The path says where it came from, so nothing
|
|
has to record an origin.*
|
|
|
|
**Pull what you need to understand, not everything it depends on.** *A document
|
|
you fetch may refer to others; follow one when you hit something you do not know.
|
|
**Pre-resolving that is how you get a `node_modules`.***
|
|
|
|
## Two facets beside it
|
|
|
|
- **`.usages.md`** — *what we use, and **which of our artifacts depend on it***
|
|
- **`.gaps.md`** — *what we expected here and did not find*
|
|
|
|
> **A usage that does not name what it justifies is half a usage.** *"We use the
|
|
> tailnet approach" is weak. **"We use the tailnet approach — see
|
|
> `manifests/ingress.yaml`" is the whole value**, because it answers the only
|
|
> question reconciliation asks.*
|
|
|
|
## Freshness is a conditional request
|
|
|
|
**Locked on the publisher's `ETag`, verbatim — never a hash you compute.** *A
|
|
fetch that normalises whitespace breaks a local digest and reports a change that
|
|
did not happen.*
|
|
|
|
| | |
|
|
|---|---|
|
|
| **`304`** | nothing changed |
|
|
| **`200`** | changed — the new copy is a candidate, not a replacement |
|
|
| **`410`** | gone — follow whatever the response points at |
|
|
| **`404`** | **unresolved.** *Report both readings; do not pick one* |
|
|
|
|
**`404` is two different answers wearing one status.** *The document was
|
|
withdrawn, or you no longer have access — **and over HTTP they are
|
|
indistinguishable**, because a host that distinguished them would leak the
|
|
existence of things you may not see.*
|
|
|
|
> **Say both. Do not guess.** *They want different actions — re-pull elsewhere,
|
|
> versus ask somebody for access — and a tool that picks one will be wrong half
|
|
> the time silently.*
|
|
|
|
*Over ssh they **are** distinguishable — permission denied against repository not
|
|
found — so a client that has both transports should say which it used.*
|
|
|
|
## Reconciliation runs the other way
|
|
|
|
**You cannot fold your facets back into their document.** *So the question is
|
|
not what do we rewrite here:*
|
|
|
|
> **Given what changed in theirs, what do we change in ours?**
|
|
|
|
**The facets usually survive unchanged.** *What moves is the manifests, the
|
|
config, the code that a usage named — which is why a usage names them.*
|
|
|
|
**And gaps reconcile too**, which is the half nobody builds for: *a new version
|
|
may have filled one, and nothing will tell you.*
|
|
|
|
> **A facet describes the local pair.** *A gap is true of **the copy you hold**,
|
|
> not of the document upstream — so a publisher fixing their end does not close
|
|
> it.* **It closes when you fetch the new copy and replace the pair**, and until
|
|
> then it is still true of what is in your tree.
|
|
|
|
**What survives a closed gap is not the gap. It is what the gap justified.** *If
|
|
you recorded a local workaround, ask whether it is retired or merely no longer
|
|
provisional* — **the second is the common case and it is invisible in the code**,
|
|
which is why it is an entry in your own log. *Somebody inheriting your workaround
|
|
will go looking for the gap that justified it, and find nothing.*
|
|
|
|
*A closed gap is **not** a decline. A decline is what you considered and did not
|
|
do; a closed gap is what you needed and got.*
|
|
|
|
**The new copy replaces the old pair wholesale.** *There is no merging a document
|
|
you do not own.*
|
|
|
|
## Venues, for things you cannot fetch
|
|
|
|
**`.loom/venues/<name>.md`** — *Kafka, React, HTTP.* **Choices we made about
|
|
something with no document to hang them on.**
|
|
|
|
*If there is a document, hang it there instead: **put a choice where
|
|
reconciliation will look for it.***
|
|
|
|
## Gaps that belong to nobody
|
|
|
|
**`.loom/gaps/`** — *a need with no owner.* **The test: could you say whose job
|
|
this is?** *If yes, it is a claim against them and belongs in their `.gaps.md`.*
|
|
|
|
> **Moving a file from `gaps/` into an external's `.gaps.md` is how a gap gets
|
|
> homed.** *The position is the claim.*
|
|
|
|
**A gap records the local workaround beside the need.** *"We are using Docker Hub
|
|
for now."* **Forgetting it was provisional is the disease; the provisional answer
|
|
is fine** — and when the gap is finally homed, every workaround that should be
|
|
retired is findable.
|