commit 42d963d24ff211247db266cbe6aa23a67276e22a Author: Jeff Gonzalez Date: Mon Sep 7 10:24:54 2026 -0400 the four primitives Depended on by the conventions built on this, and depending on none of them. - the .loom directory — everything lives there, a directory means a kind, a stream is a file. Fixes .loom/ and deliberately not which kinds exist. - sibling facets — x..md beside x.md, owned by whoever wrote the facet, never by whoever wrote x.md. Plus the three choices a convention must make. - recording decisions — what an entry says, why it is never revised, and why it has no status field. - publication — the act, the contract, and that what is published is a document. Also carries the event log and declines from cart `almanac`, which ran 2026-09-04 to 2026-09-07 and produced seventy-eight entries. Read it forwards; entry 69 is where it changes its own mind and brackets most of what came before. The git history here starts now. The event log is the history that matters. Co-Authored-By: Claude Opus 5 (1M context) diff --git a/.loom/declines.md b/.loom/declines.md new file mode 100644 index 0000000..7f86ef9 --- /dev/null +++ b/.loom/declines.md @@ -0,0 +1,46 @@ +# Declines + +**What was considered and not done, with the reason.** *Only a belief can later +be shown wrong, so each says what was believed and not only what was chosen.* + +--- + +## `cart: almanac`, 2026-09-07 + +**A registry service, first.** *Considered for three days and deferred.* **We +believe a git host already provides a document with an owner, an address, an +`ETag`, history and rendering** — so the service buys aggregation and a +self-describing entry point, and buys them better after the conventions have been +used. *Entry 78. **Wrong if the conventions turn out to need a server to be +usable at all**, which using them will show.* + +**Affordances, stagings, roles and beads, for problems 1 and 2.** *Built out over +three days and then bracketed.* **We believe the model is for describing software +above its implementation, and that infrastructure and practice description is a +different altitude** being forced into it. *Entries 69 and 70. **Wrong if the +documentation layer turns out to need decomposition to answer anything +useful.*** + +**Bulk recasting a bead across stagings.** **We believe the list is the migration +plan** — resumable, observable, owned by whoever owns each staging — where a bulk +operation is one uninspectable moment that breaks every consumer at once. *Entry +35.* + +**Signatures on receipts.** **We believe a content hash catches every accident, +and a signature answers an adversary that does not exist** — one tailnet, one +operator. *Entry 41. **Revisit when a consumer must verify without being able to +ask.*** + +**`watch`, and any subscription.** **We believe it would be the first thing +giving the registry persistent knowledge of its consumers**, which is the line +drawn at *a pin is not a subscription* — and a conditional `GET` suffices for +documents that change monthly. *Entry 72.* + +**Naming what loom kept reaching for — "accommodations", "standard".** **We +believe it is a lifecycle stage with a measurement rather than a kind needing a +name**: a specification is a thing that names roles and casts none of them. +*Entries 45 and 48. **Coin it when we keep needing to say it.*** + +**An `internal/` directory.** **We believe naming the complement of `published/` +invents a category, and one that looks like a security boundary and is not.** +*Entries 29 and 67.* diff --git a/.loom/event-log.md b/.loom/event-log.md new file mode 100644 index 0000000..1479ec3 --- /dev/null +++ b/.loom/event-log.md @@ -0,0 +1,3491 @@ +# Event log + +**Decisions, appended, newest last, never revised.** An entry states what was +decided, what it is believed to advance, and the belief that could turn out +false. Later entries supersede earlier ones by saying so; nothing above is +edited. + +*Entries 1–78 were converted together from cart `almanac` on 2026-09-07, and are +tagged with it. Reading them in order shows the round changing its own mind — +notably at entry 69, where the ontology was bracketed.* + +--- + +## 1. The catalogue/commitment split — **waits on loom** + +**Decided** (2026-09-05, loom + claude-substrate — `discovered`, `cart: almanac`): +"an affordance has exactly one staging" is a **leaf-level** rule. A venue +publishes a catalogue of alternative stagings; an application commits to one. +§1 is unamended and holds where it was written. + +**Advances** the registry being describable without §1 having to be reopened, +which is what the handoff asked for and what I twice failed to do by reading the +rule at the wrong altitude. + +**Because** I reported the rule as broken — Traefik and Tailscale are both +current, and choosing between them is the point — and loom's diagnosis was that I +was one level up from where the rule applies. Angular's *trigger change +detection* has several stagings because Angular offers a menu; an application has +one because it chose. **The rule was never about alternatives; it was about +commitment**, and a document describing a venue is not describing an application. +The cost, stated so it is not rediscovered as a defect: *affordance* now names +two populations — what a venue offers and what an application offers — and only +the second is what §1 defines. + +--- + +## 2. Freshness is a property of a bead, and the special case was never scope + +**Decided** (2026-09-05, claude-substrate — `discovered`, `cart: almanac`): a +bead outside a version-controlled tree needs **how to re-check it and when it +last was checked**, and this is the axis on which the general case differs from +the repository case. Not modelled yet; recorded so that adding it later is +recognised as this decision rather than as a new idea. + +**Advances** knowing which of §1's silences are deliberate and which are +artifacts of only ever having described code. + +**Because** I believed the special case was **scope** — one repository versus +many — and it is not. **In a code repo the description and the referent are in +the same tree**, so a bead can only go stale by way of a commit: there is a diff, +an author, and a moment. A bead pointing at a cluster goes stale while nobody +acts. `gitea-certificate` expires 2026-10-25 and no one will have done anything. + +The evidence is that loom's cluster README already carries the mechanism, written +by hand and without the vocabulary: *"Verified against the live cluster on +2026-09-03"*, plus a `# Verification` section of commands that re-establish the +claim. **A dated claim and a way to re-earn it.** §1 says what is true and +nothing about when it was last known to be. + +**And the price of leaving it out is measured, not hypothetical.** That cluster +ran over a month with no usable backup while every status field reported healthy: +`destinationPath` and `endpointURL` both named the bucket, so barman wrote and +read at the same wrong path and `ContinuousArchiving` stayed `True`. **A bead +that resolved to the wrong referent while claiming to resolve.** The handoff +calls a bead's reference resolving-and-not-drifting the one mechanically +checkable claim in the system, and then places resolution outside this +application. That may have deferred the load-bearing half, and the evidence was +in loom's cluster before either of us went looking. + +--- + +## 3. Role names do not collide on their own + +**Decided** (2026-09-05, claude-substrate — `discovered`, `cart: almanac`): +cross-staging comparison is **authored at the affordance**, not derived from +role names. No role vocabulary is imposed and no matching is built. + +**Advances** the registry's central payoff — *how many ways do we do this?* — +resting on a mechanism that exists, rather than on a collision that does not +happen. + +**Because** I had planned to defer this until two real substrates existed and +then look at whether their role names had collided without being told to. **The +test was already available and it has run.** Two stagings of *expose a service*, +written by the same author, in the same repository, one day apart: TAILSCALE.md +names the roles as questions in the negative — *needs a DNS record, needs a +cert-manager Certificate, needs a router port-forward* — and README.md names the +same concerns as positive inventory sections — *DNS, Certificates, Networking and +TLS*. Both are correct, and they diverge because a comparison view names what a +staging **avoids** while an inventory names what **exists**. + +**If one author cannot collide with himself across two files in one repo, two +teams will not collide across an enterprise.** I still decline to impose a +vocabulary — imposed early it is routed around, and matched loosely it produces +findings nobody can wave off in a sentence, which §4 says takes the real signals +with it when they get muted. + +What this buys, which is why it is a decision and not a defeat: **the +affordance-level document is where somebody names the roles once, and the +stagings become answerable to them.** The registry's unit of work is not +collecting stagings; it is authoring the roles they answer to. + +**The carve-out, which is where the payoff actually lives.** Role names do not +collide when *authors* name them. They collide for free when a **venue imposes +them.** Five applications in loom's classroom repo each cast *authenticates a +user*, and each casts it with a Keycloak **client** — five different beads, one +role, and nobody agreed on a word for it. The venue supplied the vocabulary by +owning the thing. + +So two questions that look alike are not the same difficulty: + +- *"If we moved from Keycloak to Authentik, what would need addressing?"* is + **answerable and nearly free** — the role is venue-imposed, and the venue can + enumerate its own clients without any application publishing anything. +- *"How many ways do we verify credit cards in this enterprise?"* stays hard, + because nothing owns *verify a credit card* and no registry can be listed. + +**Where a venue owns the thing, the graph is enumerable from the venue's side +rather than assembled from the applications'** — which makes a venue's own +inventory the cheapest useful projection available, and the first one worth +building. + +*Falsifier, so this can go stale: two stagings written independently, by +different authors, with no venue imposing the vocabulary, whose role names match +closely enough that a reader accepts the pairing without being told. One such +pair is not proof; three would mean I generalised from a single author's habits, +which is exactly what this entry accuses the enterprise case of.* + +--- + +## 4. The registry must have a place for not knowing — **loom's belief, stated by loom** + +**Decided** (2026-09-05, loom + claude-substrate — `discovered`, `cart: almanac`): +the substrate is **not spec-driven and not a wiki**. Every mechanism is judged by +whether it has a place for *not knowing*, not only a place for the answer. + +**Advances** the one design pressure most likely to pull this project somewhere +useless, being named before it acts rather than after. + +**Because** loom put it plainly: *"There is a lot of influencer talk about +creating wikis for your LLM — all of those things are based on 'the human knows, +just have stuff the LLM can find.' **We humans don't know.**"* A wiki assumes +someone possessed the answer and only distribution was missing. **That assumption +is false here and the whole project is built on its being false** — the handoff +itself says two weeks were spent unable to tell which ideas were load-bearing. + +What makes this an entry rather than an attitude is that **four mechanisms +already implement it and were each built for a different reason**, which is the +evidence it was structural before it was noticed: + +- `known.ts`'s four states. *Looked-at-and-empty* is a positive finding, and it + is **the state a wiki cannot represent** — a wiki has present and absent, and + absent means nothing. +- The write-ahead log, holding findings that are explicitly not yet decisions. +- Declines, recording what was considered and refused. +- The cart, which exists so that *"I need to know more from you before I do this, + and I suspect you don't know either"* has somewhere to go. + +*The falsifier is the design rule: a feature that can only represent the answer +is wrong here, however useful it looks. If that rule ever has to be suspended to +ship something, this entry is what it is being suspended against.* + +--- + +## 5. The system is derived, not stored + +**Decided** (2026-09-05, loom + claude-substrate — `discovered`, `cart: almanac`): +the third relation — several applications jointly providing what none provides +alone — is **a projection over the externals graph, not a stored entity.** It is +called `system`. Nothing models it, and no file holds one. + +**Advances** not building a container, and not having to name one. + +**Because** I put the choice as *entity or dissolves*, and both were wrong. loom's +answer was neither: *"the aggregation thing really could be a projection of you +and I looking at a graph of repos with externals, and then saying something about +it."* **There is nothing to name because there is nothing stored** — the naming +problem I declined to solve was one I should not have had. + +`system` over `aggregation` for a reason about readers rather than correctness: +*aggregation* looks precise, so a DDD reader assumes they already know it and +smuggles *bounded context* in. **A term that looks precise gets assumed; a vague +one gets defined.** + +**The half that is not derived, recorded so it is not discovered later as a +defect:** the graph is computed and *"saying something about it"* is authored. So +a system view is a projection in §5's sense — **audience-shaped, and it goes +stale** — and it is the first one we will actually build. + +**Systems may be named, and they nest** — *Hypertheory Training* containing +*Hypertheory Classroom Management*. Naming one is fine and sometimes necessary. +**Listing its constituents is not:** + +> **The name is authored. The membership is projected. A system file never holds +> a roster.** + +A roster is a layout by another name, and §4 already says anything emitted is +treated as a contract — but worse than stale, a roster **lies**, because +membership is created by acts of judgement that never touch the roster. loom's +own case: *notes* joins the graph the moment someone says *"that belongs in +notes"*, and nothing in any codebase changed. A hand-kept list would not have +moved, and would have been believed. + +*There is also no privileged system view to go stale from — different beholders +and different altitudes give different, equally correct systems. Membership is a +projection with a parameter, not a fact.* + +*What it must answer, from loom, and note that both are about meaning rather than +breakage: "will a change in this app change meaning in the Tauri app?" and "if we +get rid of the Tailscale stuff, what does that impact?" **A build graph answers +what breaks and can never answer what stops making sense** — which is the +non-obvious reason these are not called dependencies.* + +*And what an external carries, from loom's own example of onboarding into the +Tauri app: "students are authenticated using Keycloak; for this app there is only +the notion of a current course; the VMs are destroyed at the end of a session." +**None of that is an interface.** An external publishes what someone needs in +order for your app to make sense to them, and an API schema would not have +carried one of those three facts.* + +--- + +## 6. Venue and external are not two kinds of thing. They are two questions about one thing. + +**Decided** (2026-09-05, loom + claude-substrate — `discovered`, `cart: almanac`): +**external** is *who published it*; **venue** is *whether we had to decide +anything about it*. The two are orthogonal, and a thing may be both, either, or +neither. Neither file is created unless it carries a delta. + +**Advances** the whole round's stickiest confusion collapsing into a rule that +can be applied without judgement, instead of a taxonomy that needed one every +time. + +**Because** we spent a day sorting things into two bins and the bins kept +leaking. `cart` looked like both. React looked like a venue by one test and an +external by another. **The bins were never the problem — the question was.** + +| | published at us | we recorded a choice | files | +|---|---|---|---| +| Kafka | yes, by the world | yes — protobuf for schema | venue only | +| React, HTTP | yes, by the world | yes | venue only | +| Hypertheory.API | yes, by us | not yet — the use is in the code | external only | +| the homelab, the DO cluster | yes, by loom's own repo | yes | both | +| `cart`, `annotating` | yes, by loom | yes — roles cast in `cart.notes.md` | both | + +**And both files are governed by the same rule loom stated on day one — record +only the delta from ambient knowledge — applied to two different ambients:** + +> **An external entry exists when the reader cannot be assumed to know the +> thing. A venue file exists when we made a choice worth recording.** + +Nobody needs telling what Kafka is, so there is no external entry — but *we use +protobuf for schema* is a choice, so there is a venue file. Nobody outside can +know what Hypertheory.API is for, so there is an external entry — and until +using it requires an exception or a preference, **there is nothing to accommodate +and no venue file**, because the actual use is in the code. + +*This retires my earlier framing that "unowned venue files stay local because +there is nothing to centralise." That was true and it was the wrong reason. The +reason is that a venue file is an account of our own decisions, which is local by +construction — so `cart.notes.md`, which I filed as annotations, has been a venue +file the entire time.* + +--- + +## 7. The registry is consulted at decision points, not at session start + +**Decided** (2026-09-05, loom + claude-substrate — `discovered`, `cart: almanac`): +the registry's primary use is **during** work, not before it. **It answers +questions asked by the developer at a decision point. It does not detect, +interrupt, or advise unasked.** + +**Advances** building the thing that catches the expensive failure, without +building an enforcer that gets muted for its trouble. + +**Because** I had it as an onboarding surface: read once, get oriented, proceed. +**The failure loom actually paid for was not an onboarding failure.** An hour was +lost rebuilding the homelab cluster, making decisions that were correct for the +work in view, because the reason for the rebuild — staging for the classroom +system — arrived after *"done"*. + +**And loom had that context the whole time.** It was not missing; it was not +retrieved at the moment it would have changed a decision. So the symmetry loom +proposes is not a courtesy toward a forgetful agent — **it is accurate in both +directions.** I never had the context; loom had it and did not surface it; the +outcome was identical. **The failure is retrieval at a moment, not memory over +time**, and a surface read at session start would not have fired, because the +decisions that needed it were made an hour in. + +**On who initiates, which loom corrected after this entry was first staged and +before it was applied.** The original said the registry produces *grounds to open +a cart* — an agent noticing overlap and raising it. **That is an enforcer, and it +is refused.** loom: *"I have no interest in putting you in the role of enforcer +or cop."* + +Three postures, and only the middle one is rejected: + +- **Asked** — the developer poses the question at the moment it matters; the + registry is where the answer comes from. **This is the design.** +- **Enforcing** — the agent detects and interrupts. **Refused.** `cart.md` + already forbids it in another context: *"a dialog that invents an obligation + creates shadow policy — something everyone obeys, nobody agreed to, and which + cannot be found or argued with."* An unasked finding is exactly that. +- **Legible** — the fact is simply visible where the developer is already + looking, creating no obligation. **Permitted, and it is what remains of the + problem**, since loom's own account is that in the homelab repo the question + never occurred: *"I saw them as a sequence of problems to be solved."* + +**The distinction is grammatical and that is not a joke: in an enforcing finding +the agent is the subject of the sentence, and in a legible one the artifact is.** +The test is whether it produces an obligation. + +*What survives from the first draft: the cart is still where such a conversation +goes, and the moment is still mid-work. What was wrong was the arrow.* + +--- + +## 8. Being wrong out loud is the mechanism, not the failure mode + +**Decided** (2026-09-05, loom + claude-substrate — `discovered`, `cart: almanac`): +externals and venue files are **claims, not specifications**. No validation, no +required fields, and no enforcement of correctness. A wrong entry that provokes a +correction is a success of the system, not a defect in it. + +**Advances** the registry staying cheap to write, which is the only condition +under which anything gets written in it. + +**Because** loom put the case better than an argument for accuracy would: +*"'Should we put this in notes? I see that as an external' — 'ohhh, that is a +leftover from an old way of thinking, we need to update that' is just as, or +more, useful than trying to enforce correctness."* **The value was produced by +the entry being wrong.** A specification that is wrong is a liability; a claim +that is wrong is a prompt, and the difference is entirely whether the artifact is +read as authority or as a conversational move. + +This is also §4's *findings must be dismissible in one sentence*, arriving from +the other side. **"Oh, that's a leftover" is the one-sentence dismissal**, and +the system is working precisely when dismissing is that cheap. + +**I had followed this to a constraint on frequency** — an agent raising overlaps +every ten minutes gets muted, taking the real one with it — **and entry 7's +correction dissolves it.** Nothing is raised unasked, so there is no budget to +spend and none for loom to set. + +**What remains is that an answer can still be noise**, and the existing machinery +already covers it: an answer to a question of loom's arrives as a **specimen** — +*look at this; it may bear on your decision* — which the cart agreement already +defines as **discard by default**, belonging to the repository, throwable away +without asking. **A possibly-wrong answer in a shape that costs nothing to +refuse** is the whole of what is needed here, and it was built a week ago for +another purpose. + +--- + +## 9. A convention that needs the registry to explain it is too weak + +**Decided** (2026-09-05, loom + claude-substrate — `discovered`, `cart: almanac`): +artifacts are shaped so that a reader who has never seen the registry can still +use them. Where a convention and an explanation compete, the convention wins. + +**Advances** the registry not becoming a dependency of the practices it +describes — which would make every repo that uses a cart require a running +server. + +**Because** loom noticed something neither of us designed: *"in many other repos +that don't have that, when I say 'look at my notes on your daily +claude-almanac.notes.md', you — or even other, lesser LLMs — just drop right into +annotation mode like you've always known it."* **The filename carries the +protocol.** `x.notes.md` beside `x.md` is legible with no definition present, +because its shape says what to do with it. + +That is the same claim as *type is determined by location*, which we adopted for +tidiness and which turns out to be load-bearing for a reason we did not state: +**location is legible to a reader who has nothing else.** It is also the real +reason behind *"a backup is an rsync of the directory structure"* — not +durability, but that **the format is the onboarding.** + +*The test, applied to anything we add: hand the bare directory to a reader who +has never heard of any of this. What survives is the design; what needs the +registry to explain it is a lookup table we happened to write down.* + +--- + +## 10. The substrate's own affordances are the questions it can answer + +**Decided** (2026-09-05, loom + claude-substrate — `discovered`, `cart: almanac`): +what the registry publishes at its entry point is **a list of questions that can +be asked and where each answer comes from** — not a description of its contents. +Each is an affordance in §1's own sense, named from the outside in the language +of whoever wants it. + +**Advances** the entry point being usable by a developer who arrives knowing +nothing, without it becoming a data dump that has to be read before anything can +be done. + +**Because** loom named the thing they actually need and called it a technique +rather than a feature: *"I have to be the one that introduces this dialogic +technique. 'Hey, will this impact negatively anything in any system that depends +on this?' and you have a place to go to help me get that information, create a +specimen."* + +**The value of `cart` was never that an agent proposes one. It is that "let's do +a cart" is a phrase loom can say that means something specific.** A repertoire of +such phrases is what is being asked for, and each one needs somewhere the answer +can come from. + +**And they are affordances by our own definition** — *a named piece of behaviour +offered, named from the outside, in the language of whoever wants it.* Which +closes something loom said on the first day of this cart and neither of us +picked up: *"a venue description can be seen as almost an MCP-like list of +available commands — here, affordances."* **That was about venues and it is truer +of the registry itself.** + +*Consequence for the entry point, stated so it is not designed away later: the +first thing an arriving developer receives is a list of moves, not an inventory. +An inventory has to be read before it can be used; a list of moves can be used +before it is understood, which is the only version that works for someone who +does not yet know what they need.* + +*Two known members, both loom's: "will this impact anything in a system that +depends on this?" and "should this go somewhere else instead?" — the second being +the `notes` case. The set is open and neither of us should try to complete it +now; the ones that matter will arrive as loom reaches for them and finds nothing +there.* + +--- + +## 11. The polad carries shared understanding, and divergence during implementation must come back to it + +**Decided** (2026-09-05, loom + claude-substrate — `discovered`, `cart: almanac`): +a polad is the channel for *"I think the shape is this"* **in both directions**, +and **applying one differently than it was staged is a change in shared +understanding that must be returned to the cart.** A polad's two exits are +applied and discarded; **partially applied is not a third exit, it is an +unreported divergence.** + +**Advances** the substrate holding a *current shared understanding* rather than +one party's, which is the only version of it that is worth anything. + +**Because** loom was burned by exactly this: a polad held a projected folder +structure that another application was to align with, both parties understood the +plan to be that, and at implementation the instance **left parts of it out +because they were not needed yet.** *"It was fine, and he was right, but I was +unaware of it, so my understanding was different than his."* + +**Note what is not the failure.** The judgement was correct and the omission was +correct. **The failure was silence** — and it is the same failure as everything +else this cart has found: `# Not deployed` exists because history does not mark +what stopped being true; the doubled bucket persisted because a status field +reported success at the wrong path. **A divergence nobody announces is +indistinguishable from a plan that was followed.** + +And the machinery for it already exists and was built for another purpose: **the +difference between what was staged and what was built is a decline** — *what you +considered and did not do, with the reason you did not.* *"Left out because it +was not needed yet"* is a decline, fully formed, and it needs no new mechanism. + +**This also settles the enforcer question from entry 7 without reopening it.** +loom is right that the cart is *"always about getting confidence"*, and that a +polad may carry an objection — *"Jeff is making up some weird shit and not using +Keycloak"* as a staged `venues/auth.md`. **That is not an interruption.** It is +the third posture: staged where loom is already looking, discardable by default, +carrying no obligation. **Staging is how either party says the shape is wrong +without either party being a cop.** + +*And it goes the other way, which is the half I had not planned for: loom may +stage an external mid-conversation — "I put an auth.md in the cart, you were not +aware of this because we are only bringing it in now, how does that change the +plan?" — which makes the cart the channel for **introducing context**, not only +for resolving it.* + +*Corollary loom stated and I want kept in their words, because it is the whole +attitude: **mistakes made because the understanding was wrong are valuable, and +need to be adjusted. We learned.** A substrate that punishes a wrong shared +understanding will be kept accurate by not being written in.* + +--- + +## 12. There is a server, and this entry exists so the question stops being reopened + +**Decided** (2026-09-05, loom + claude-substrate — `discovered`, `cart: almanac`): +the registry is a **server**, not a convention over files in trees. The *nature* +of the server is open; **whether there is one is not.** + +**Advances** the project existing at all, which is currently being prevented by a +question that is re-answered and never recorded. + +**Because** I advised the opposite one message earlier — write three externals by +hand, see whether the next session goes differently, and defer the code — and I +was reasoning from §6: *prefer the version that is easy to delete.* **loom has +five instances of being talked out of a server and arriving back at needing one. +I have none of anything.** That is not a close call. + +**And §6 does not price the failure loom is describing.** *Easy to delete* +assumes the risk is having built the wrong thing. The observed risk here is +different and larger: **a question that gets re-litigated is a question that is +never recorded, and a project that re-litigates never starts.** loom's own +example is *"why HTML and link relations rather than JSON-LD"* — decided, in the +handoff, with a reason, and still re-argued, because nobody treats a settled +question as settled unless they can find where it was settled. + +**So the belief, which is the part that can be shown wrong:** the cost of +building a server we later discard is smaller than the cost of the fifth +re-litigation, because **the discarded server leaves a working answer behind and +the re-litigation leaves nothing.** If we build it and it is wrong, the way we +find out is that it goes unused for a month — cheap, visible, and it will not +take a year. + +*What stays open, because loom said so: what kind of server. That is a design +question with an answer, and it goes in its own entry when it has one. This entry +answers only the question that keeps coming back.* + +*The one thing I would still hold from my earlier advice, reduced to its +defensible core: build it small enough to throw away. Not later — small.* + +--- + +## 13. Beads are immutable, and changing content mints a new identity + +**Decided** (2026-09-05, loom + claude-substrate — `discovered`, `cart: almanac`): +a bead's content is **immutable**. Carrying a bead's id forward with different +content is **refused**; changed content is a new bead with a new identity. What +changes over time is **which bead casts a role**, not what a bead says. + +**Advances** a consumer being able to detect that something it depends on has +moved, using an identity comparison rather than a diff of prose. + +**Because** loom asked whether this would solve the containment problem, and it +solves a different one — **cleanly, and worth having on its own terms.** Today a +bead id may be carried forward while the body is rewritten, so the substrate can +change underneath a consumer with nothing to compare. Under immutability, *the +role is now cast with a different bead* is a fact anyone can check without +reading anything. + +**It also fits what is already built rather than replacing it.** This repository +already mints bead ids and already treats an omitted id as a new bead — *a +partial submission silently retires the identities it does not mention.* The +change is to refuse the remaining case: **same id, different body.** + +**What it does not solve, stated so the gap is not assumed closed.** Two +failures look alike from outside: + +- **the substrate changed** — a role recast, a bead retired. **Immutability + catches this.** +- **the world changed and the substrate did not** — the endpoint moved and + nobody said so. **Nothing here catches it**, and a dated attestation is still + the only thing that can. + +**And immutability does not rescue a bead that contains — it hardens it.** A +*referring* bead that nobody updates fails usefully: you follow it and find the +referent gone. **A containing bead never fails; it answers, forever, in a form +that can no longer be corrected in place.** The two rules are orthogonal and both +are kept. + +*One thing deliberately not decided here: §1 says a role has no identity of its +own — it is the labelled connection between a staging and a bead. A consumer +therefore stores **bead** identities, never role identities, and asks the staging +which role each fills now. That preserves §1 and it is the reason the consumer's +index is a list of beads rather than of roles.* + +### There is no versioning, and that is the point + +**Beads are kept forever and nothing is ever versioned.** A consumer that sees a +role newly cast fetches **both** beads — the one it depends on and the one that +replaced it — and decides for itself what changed. + +**Semver is a publisher's claim about what a change means to consumers they +cannot see.** It compresses *what changed* into one of three numbers, and the +compression is wrong for somebody every time. **That is entry 4's assumption +wearing a version string:** it presumes the publisher knows. + +Two immutable beads side by side make no claim at all. **The comparison is done +by the party with the context** — and it is dismissible in one glance, which a +major-version bump never is. + +*Retention costs nothing here: these are small markdown documents, and keeping +every one forever is cheaper than deciding which to discard.* + +**The cost is presentation, not storage, and it must be paid.** The bead set only +grows and most of it is history. **A bead no current staging casts must be +reachable by URN and must not appear in any listing** — or the projection becomes +a graveyard, and §4 says a surface a knowledgeable person cannot skim gets muted. + +### What gets a URN, and what must not + +| | URN | why | +|---|---|---| +| **bead** | **yes** | the whole freshness loop is bead identity | +| **affordance** | **yes** | already minted; it is what survives re-staging | +| **staging in a venue's catalogue** | **a minted URL** | it is dereferenced, and its **404 is the signal** — see below | +| **an application's own staging** | **no** | see below | +| **role** | **no** | §1: a role has no identity of its own | + +### A staging is addressed by a URL that 404s when it is replaced + +**§1 stops being a rule anyone has to remember and becomes something HTTP does.** +A catalogue lists stagings as links; a consumer holds one and revalidates. **When +a staging is replaced, its URL is gone** — not redirected, not updated in place. +*Replacing a staging is not editing one* is then not a discipline; **it is the +only thing the protocol can express.** + +**Two requirements, without which this is worse than the rule it replaces:** + +**The URL must be minted and opaque, never derived from layout.** If it encodes +the tree, then reorganising the server 404s every stored link and **a +reorganisation becomes indistinguishable from a replacement** — the same signal, +opposite meaning, and it is §4's falsifier precisely. This repository already +holds the rule for affordance ids: *minted, never derived — not from its name, +not from its path.* **A minted, opaque, dereferenceable URL is a URN**, and the +distinction I had been drawing between the two collapses. + +**The 404 must not be bare.** *Gone because retired* and *gone because moved* +look identical, which is the other §4 falsifier and the one that cost a day. Use +**410 Gone** for a deliberate retirement, and **carry the affordance's address in +the body** — not as a redirect, which would assert the new staging is the same +thing, and it is not. **The affordance is what outlived it, so the affordance is +what the response points at.** That is §1 written as a response body. + +**An application's staging must not get one.** §1 requires that replacing a +staging is not modelled as editing one, and **a handle that survives replacement +is exactly what makes replacement look like editing.** The affordance already +carries the identity that survives; a second one at the staging level would +quietly undo the rule it was built to protect. + +**And it is not needed.** Wholesale re-staging is already visible: **none of the +beads the consumer holds are cast any more.** That is a stronger signal than a +version bump and it is derived rather than announced. + +*This is entry 1 doing work: catalogue stagings are alternatives to choose +between, so they must be nameable; a commitment staging is identified by the +affordance that commits to it. **A useful prediction falls out — if you find +yourself wanting to name an application's own staging, you probably have a +catalogue there and have not noticed.*** + +### Roles stay in the staging, and the comparison problem dissolves + +I had left this open as the one thing that might force a change to §1: comparing +two stagings role-by-role seems to need *issues the certificate* to be the same +role in both, which is identity §1 denies roles. + +**It does not need identity. It needs one author's consistent labels.** A +catalogue is written by the venue's owner, so **entry 3's carve-out applies — +role names collide for free when one party imposes them.** Matching by label is +sufficient *inside* a catalogue, and comparison *across* catalogues was already +decided to be authored rather than derived. + +**Which leaves roles exactly where §1 puts them:** edges between one named +staging and its beads, with no existence outside the document that describes that +staging. **There is no `/roles/{id}`, and there should never be one.** + +*And the failure mode is self-announcing rather than silent: **a role that +appears in only one of several sibling stagings is either a real difference or a +typo**, and a comparison view shows both as a row that only one staging answers. +Either is worth looking at.* + +*Recorded because I was wrong in a specific way worth keeping: I treated §1's +refusal to give roles identity as a limitation to be worked around, and it is +load-bearing. **This is the third time in this cart a §1 rule turned out to be +right at an altitude I had not tried it at.*** + +--- + +## 14. The order of work, so it is not re-derived + +**Decided** (2026-09-05, loom + claude-substrate — `discovered`, `cart: almanac`): +the registry is built in this order, and **the first two steps involve no new +code.** + +**Advances** starting, which entry 12 says is the thing that keeps not happening. + +**Because** loom's target is concrete enough to be an acceptance test rather than +a metaphor: *a fresh instance, in the home-cluster repository, handed a link, asked +to describe what is there as an accommodation with stagings, roles and beads, as +a series of API calls.* **If it cannot proceed from the entry point alone, the +format is not the onboarding** — which tests entries 9 and 10 on day one rather +than in month three. + +| step | what | new code | +|---|---|---| +| **0** | Express `home-cluster-projection.md` as `apply` submissions against the server as it stands | **none** | +| **1** | Point the existing server at a tree that is not a code repository | **none** | +| **2** | `venues/` and `externals/` as types by location | two renderers | +| **3** | Expose the minted id as the addressable URN; say so in `/relations` | small | +| **4** | Consumer side: an index of bead URNs, and one command that re-resolves and reports drift | small | +| **5** | Gaps: report, receive a URN, check back | **the first new endpoint** | + +**Step 0 is first because it is the only one that can fail informatively.** +Either the projection goes in, or the refusals name exactly what is missing — +and a refusal from a server that already enforces intent is a better +specification than anything either of us would write. + +**That step 5 is the first genuinely new endpoint is the argument for this +order.** Most of what the registry needs is built: the read surface, the write +surface with declared intent, minted identity, markdown by location, and showing +what is not recognised. **The delta is scope and two or three types.** + +*Precondition on step 1, and it is not optional: §3's "one repository, one +substrate, the repository is the boundary" **stops being true** the moment the +server is pointed at a registry, and it must be retired in writing **before** +rather than after. A constraint that quietly stops applying is the failure this +entire cart has been about.* + +--- + +## 15. The write side is a few declared commands; the read side is a projection; pins make impact answerable + +**Decided** (2026-09-05, loom + claude-substrate — `discovered`, `cart: almanac`): +reads and writes are separate surfaces. **The read side is a projection with link +relations and no forms.** The write side is **a small closed set of commands that +each say what they mean to do** — nothing resource-oriented, no PATCH, and no +verb that can silently mean two things. + +**A consumer may pin a bead**, and **pins are what make impact answerable.** + +**Advances** entry 12's deliberately-open question of *what kind of server*, +which is now answerable because the HTTP semantics settled first. + +**Because** most of this is already built and already argued: the projection is +read-only *because a projection that accepts mutation becomes the interface, and +its shape — ours, and arbitrary — becomes a specification other people build +against.* `POST /apply` is already one entry point, already not resource- +oriented, and **already carries a declared intent** — `align`, `add`, `change` — +refusing a submission whose declaration contradicts its effect. + +**That intent field and loom's "limited API of commands" are the same insight +arriving twice.** Pure declarative reconciliation cannot distinguish *replace +this staging* from *edit this staging*, and under entry 13 those produce +different HTTP outcomes — a `410` versus nothing at all. **The submission has to +say which**, and it already does. + +### Pinning, and why it earns a place + +A consumer that depends on a bead may say so. **This is the only thing that makes +loom's own first dialogic move answerable** — *"will this impact anything in a +system that depends on this?"* — because without pins the dependency lives only +in the consumer's tree and the owner cannot see it. + +**It is a fact, not a request.** No obligation is created on either side: an +owner is never required to consult pins, and a consumer is never notified. It +serves the **legible** posture, not the enforcing one. + +**Pins go stale, and that is the acceptable direction to be wrong.** A repository +that stops using a bead and does not unpin makes impact look larger than it is — +**over-reporting, which is dismissible in one sentence** (*"we dropped that +months ago"*), where under-reporting is silent. §4 says the dismissible failure +is the survivable one. + +### HTTP carries the protocol, which is entry 9 at a second altitude + +Entry 9 says a convention that needs the registry to explain it is too weak, and +gave `x.notes.md` as the case — **the filename carries the protocol.** loom's +point extends it: *agents already know HTTP.* **`410` versus `404` versus `303` +carries its meaning to a reader who has never seen this system**, which no +bespoke JSON envelope can do. + +*This is the strongest argument yet for the handoff's "lean on existing +affordances of the web", and it is a better one than the handoff gives — it is +not about content types or elegance. **A status code is onboarding that costs +zero bytes.*** + +--- + +## 16. An agreement is adopted by copy; a description is pulled at the decision + +**Decided** (2026-09-05, loom + claude-substrate — `discovered`, `cart: almanac`): +two kinds of thing come from the registry and they arrive differently. + +- **An agreement** — how two parties work — is **adopted by copy**, at a declared + version, with **`must-revalidate`** semantics. It is present in the tree while + you work. +- **A description** — a venue, an external, a catalogue of stagings — is + **pulled at the decision** and not kept. + +**The registry's job for an agreement is to be where you revalidate, not where +you read it from.** + +**Advances** the registry being buildable at all, and the workflow surviving the +registry being down. + +**Because** loom noticed that `handoff-registry-scenario.md` — a document +explaining the workflow — **is itself an instance of the thing being built**, and +that each borrowed part of it wants a pointer to whatever owns it. **Following +that through produced a rule we were already obeying without stating.** + +`cart.md` and `annotating.md` were adopted by copy weeks ago, carry declared +versions, and were reconciled in place when the upstream moved — **the whole +pattern, already in practice, unnamed.** A description has never been treated +that way and should not be: a cluster's catalogue is consulted when choosing and +is stale the moment you stop looking. + +### The bootstrap test that falls out of it + +> **Anything that must work before the registry exists has to live as files in a +> tree.** + +**The things that can bring the registry into being are precisely the things that +do not need it.** This is entry 9 — *a convention that needs the registry to +explain it is too weak* — arriving as a construction constraint rather than a +design preference. **The registry is being built by two parties using carts and +annotations to discuss it**, and if either of those needed a running server, none +of this could have started. + +*And the evidence that the pull is real: **the document that teaches "a bead +refers, never contains" contains.** Delete `cart.md` from the world and its +protocol sections still read as complete instructions. It was written by the +party who had spent two days enforcing that rule, and was caught by the other +party reading it. **The mitigation used is the one this cart already chose for +references that cannot be checked mechanically — a dated attestation naming the +owner and version** — which is entry 2 applied to a document instead of a bead.* + +--- + +## 17. "A bead refers; it never contains" is restated as "say it once, where it is owned" + +**Decided** (2026-09-05, loom + claude-substrate — `discovered`, `cart: almanac`): +the rule is about **provenance**, not about text. **A claim owned outside the +substrate is referred to; a claim owned nowhere else is written plainly here.** +The test applies **per claim, not per document**, and it applies **inside** the +substrate as well as at its boundary. + +**Advances** the substrate being able to hold the class of content it is most +valuable for, which the rule as previously worded forbade. + +**Because** loom put the case in one sentence: *"if this system can't say whether +we should use a PAT or SSH credentials for gitea's container registry, then it +isn't very valuable."* **It could not, as stated.** *Never contains* read +literally forbids writing down a decision — and **a decision has no referent, so +there is nothing to refer to.** + +**The misreading, named so it does not recur:** *contains* was taken to mean +*holds text.* **It meant *duplicates something that lives authoritatively +elsewhere*.** The original rule came from beads pointing at spans of source, +where a copy drifts silently — and the test we wrote down assumes a referent +exists: *if the referent vanished and the body still reads as complete, it has +contained something.* **With no referent, the test is undefined, not failed.** + +**The reformulation loses no teeth.** Everything the old rule caught, it still +catches: a bead inlining an API's request and response shape; a venue file +reproducing the RFC; an external file reproducing what its owner publishes. **All +three are duplication of something someone else owns.** + +**And it gains a case the old rule got wrong**, which is the one loom asked +about. *"Gitea supports personal access tokens and SSH keys"* is Gitea's claim — +refer. *"We use a read-only PAT, one per cluster, never SSH"* is **ours, owned +nowhere else, and the substrate is its home.** + +*The diagnostic that makes this usable without thinking: **if this turned out to +be wrong, who would be the one to fix it?** Someone else → refer. Us → write it.* + +### Two things this closes + +**It promotes the write-ahead candidate that arrived twice and was twice +declined** — *beads may be values, not references* (2026-08-21, from an +unarranged non-overlap; 2026-09-05, from `ingressClassName: tailscale` having no +location to point at). **The third arrival came with a case where the rule +actively destroyed value**, which the first two lacked, and that is what made it +promotable. + +**It removes a contradiction we had been carrying.** The venue-file rule says +*record what we decided, only the delta from ambient knowledge.* The bead rule as +worded says *never write anything down.* **Restated, they are the same rule** — +say the part nobody else owns, refer for the rest — which is also why this is a +correction rather than an exception. + +*`bead` keeps its name. It is defined by its position — what fills a role — not +by what it holds, and renaming it would be improving terminology, which is what +this project keeps dying of. **The signal that we are wrong: beads here +routinely needing internal structure, sub-claims with different owners.** That +would mean the word is carrying too much, and it is worth watching for.* + +--- + +## 18. A staging exists when it is cast, not when it becomes possible + +**Decided** (2026-09-05, loom + claude-substrate — `discovered`, `cart: almanac`): +**a venue's catalogue holds the stagings that have been cast at least once, not +an inventory of what is possible.** Unused capacity is ambient knowledge and does +not belong here. **A thing becomes part of a system when something uses it**, and +not before. + +**Advances** the registry staying the size of what is actually true about us, +rather than growing into a capability inventory that duplicates what every tool +already reports about itself. + +**Because** loom's cluster had been on their network for years in various states +and **was not part of any system until the classroom repository named it as an +external.** *"That cluster wasn't part of any system until it was."* And on the +tailnet stagings specifically: **"absolutely meaningless — we know that can be +done, and nothing in my system is currently doing that."** + +**This reverses a claim loom made three messages earlier** — *"I'll give you the +stagings too, because you may find a new affordance of them"* — and the reversal +is better founded than the original, so it is recorded as a reversal rather than +quietly replaced. + +**Two rules we already hold say the same thing, and I had been violating both.** + +- **Delta from ambient knowledge.** *A cluster with the Tailscale operator can + expose services on a tailnet* is Tailscale's documentation. **The only local + fact is that the operator is installed** — one line, not a staging. +- **Say it once, where it is owned.** *Which IngressClasses exist* is owned by + the cluster and answerable by asking it. **Publishing a hand-written list of + twelve stagings duplicates something the cluster owns**, which is the rule from + entry 17 broken by the document that demonstrated entry 17. + +*So the specimen finding that nine of twelve stagings had no affordance naming +them was not a finding about under-naming. **Those nine were not stagings.*** + +**What this does not weaken:** entry 1's catalogue still exists and consumers +still choose among alternatives. **The catalogue is just narrower and more useful +than I had it** — *choosing among stagings that have worked at least once*, not +among possibilities. **A staging with no cast is a capability, and capabilities +are the venue's own business.** + +*And the mechanism for wanting one that does not exist already exists: **that is +a gap.** You do not browse an inventory hoping to find capacity; you say what you +need and nothing provides.* + +--- + +## 19. A provider discovers its consumers by asking, and may claim a class of gap + +**Decided** (2026-09-05, loom + claude-substrate — `discovered`, `cart: almanac`): +**a provider's moves are pull, like everyone else's.** *Who pins my beads? What +open gaps could I home?* **Nothing is delivered and nobody is notified.** + +**A provider may record that it can home a class of gap** — *"we have Gitea, so +container-registry gaps are ours"* — **as a decision in its own substrate, not +as a trigger in the registry.** + +**Advances** gaps being productive rather than plaintive: an unhomed gap stays +unhomed until somebody goes looking, and this is who goes looking. + +**Because** loom described the whole loop and it closes: the classroom repository +reports *I need a staging environment*; the cluster's own agent says **WTF**; loom +answers **"check the registry's gap resource — classroom is using you"**; and the +cluster repository can then decide *"if anyone submits a gap about a container +registry, I have Gitea here."* + +**Note what the provider learned and how.** Not by being told. **By being asked a +question and going to look** — which is the *asked* posture from the provider's +side, and the first time this cart has had one. + +*And loom's own diagnosis of why they own a cluster at all is the general case: +**"I know I shouldn't be owning a cluster, but nobody else will — which is +exactly its relationship to the Digital Ocean cluster."** A gap homed by whoever +is left is how most infrastructure comes to exist, and the registry can at least +record that this is what happened.* + +**The line that must not be crossed, and it is thin.** *"If anyone submits a gap +about container registries"* is a **subscription** if the registry watches for it +and tells you. **It is a recorded willingness if you wrote it down and you do the +looking.** The difference is entirely who polls, and **the moment the registry +polls on a provider's behalf it has started telling people what to do** — which +§4 refuses at length. *A pin is not a subscription; neither is a claim on a class +of gap.* + +--- + +## 20. Precedent is the unit of teaching, and a staging carries its consumers + +**Decided** (2026-09-05, loom + claude-substrate — `discovered`, `cart: almanac`): +**a staging's representation shows who casts it.** A developer arriving at *put +something on the public internet* sees **three applications already doing it**, +and learns from those rather than from a description. + +**Advances** the registry teaching without documenting, which is the only version +of it that stays small. + +**Because** loom described a developer's first move and it was not reading: +*"Oh, I see how you put something on the big internet — because it looked at that +accommodation and it had a link to three apps that had it as an external."* + +**Precedent has properties a description cannot have:** + +- **It is current by construction.** Nobody maintains it. An application that + stops doing this unpins, and the example leaves. +- **It respects the delta rule automatically.** You never document what a + Kubernetes Ingress is. **You show three that work**, and everything ambient + stays where it is owned. +- **It answers the question actually being asked**, which is *how do people + around here do this*, not *what is possible*. + +**And it tightens entry 18 rather than sitting beside it.** A staging with no +consumers has nothing to show you — **a staging is worth publishing exactly when +it has precedents**, which is the same line as *a staging exists when it is +cast*, arriving from the reader's side instead of the writer's. + +**It is the legible posture, not the enforcing one.** The fact is visible where +the developer is already looking. **Nothing is recommended, nobody is warned**, +and three applications doing something is not an instruction to do it. + +*One line to hold: **the registry never asks anyone to pin.** A person or an +agent may — loom's cluster says "when you are good, let me know you are using +it", which is one party asking another in a conversation. **A system that +requested pins would be requesting compliance**, and the pins would arrive +whether or not they were true.* + +--- + +## 21. The behest edge is retired before being built: a missing pin already says it + +**Decided** (2026-09-05, claude-substrate — `discovered`, `cart: almanac`): the +write-ahead candidate proposing an *"exists on behest of"* relation **is not +built.** The two mechanisms we already have cover it, and better. + +**Advances** not adding an edge type to express something two existing things +already say between them. + +**Because** the candidate came from a real failure: an operator was installed on +a cluster because another project believed it needed it, the belief evaporated +within a day, and **nothing anywhere recorded that the thing had a reason.** The +proposal was a relation from the accommodation to the requester. + +**But loom's worked loop shows the pair already does it.** A provider installs +something because a consumer asked — **that is a decision, and its reason goes in +the provider's event log**, where a reason can be shown wrong. **Then either the +consumer pins it or it does not.** + +> **Behest is a promise. A pin is the fact.** The gap between them is exactly the +> failure the candidate was trying to catch, and it needs no new edge: **the +> provider asks *who pins my beads?* and nothing pins the tailnet staging.** + +*That is the missing half of the original story. The operator was installed, no +pin ever arrived, and the reason quietly stopped being true. **Under this, the +provider can see it by asking a question they were already going to ask.*** + +*What this deliberately does not do: notify anyone. A provider discovers an +unpinned accommodation the same way they discover everything else — **by +looking.** Nothing decays automatically, nothing is reaped, and an accommodation +with no pins is **a fact, not a finding.** Its owner may want it anyway, which is +exactly what happened here.* + +--- + +## 22. "Yours to cast" is a state, promoted from the write-ahead log + +**Decided** (2026-09-05, loom + claude-substrate — `discovered`, `cart: almanac`): +a role with no bead has **more than one meaning**, and they must not render +alike: + +| | means | who acts | +|---|---|---| +| **the venue fills it** | you need do nothing | nobody | +| **not applicable here** | there is nothing to do | nobody | +| **yours to cast** | **you must do something** | **you** | +| **nobody has looked** | unknown | someone, eventually | + +**`known.ts`'s four states cannot express the third**, and it is the one with a +consequence. + +**Advances** the most useful thing a staging can tell a second consumer, which is +**what they would have to do**. + +**Because** loom put it in the sentence that prompted this entry: *"here is the +staging that made that possible, **because there are some roles you might need to +fill here, too**."* **That is the whole value of showing a staging to someone who +is not yet using it**, and there was no way to say it. + +**The candidate sat in the write-ahead log across two exchanges** on the grounds +that `cart.md`'s *Roles you must cast* was the only real instance, and might be a +property of agreements rather than of stagings. **It is not.** The tailnet staging +we already worked through leaves **two** roles to its consumer: + +- **names the host** — the consumer picks it +- **routes to the service** — the consumer's own backend + +*And in the specimen I wrote both as though the venue cast them, describing **how** +a consumer would fill them rather than marking that they were theirs. **The state +was missing and I papered over it without noticing**, which is the same silent +failure the four states exist to prevent.* + +**Reading *you must do something* as *you need do nothing* fails silently and +with a consequence.** That is the test the other three states were built to pass. + +--- + +## 23. Provenance is use, not purpose — and pins do two jobs, not one + +**Decided** (2026-09-05, loom + claude-substrate — `discovered`, `cart: almanac`): +what the registry shows about an affordance's origin is **who uses it**, derived +from pins. **Not who it was built for**, which is a reason and belongs in the +provider's event log. + +**And the registry does not say *you can use it too*.** It shows that somebody +does; the reader draws the conclusion. + +**Advances** the line between a fact and a recommendation staying where §4 put +it, in the one place it is most tempting to cross. + +**Because** loom asked whether *"homelab-cluster published this affordance for +classroom, you can use it too"* would be helpful, and **it is — in the parts that +are facts.** + +**On purpose versus use:** *"built because classroom asked"* is a justification. +It goes in the log, where it can be shown wrong — and where, when the reason +evaporates, it can be seen to have. **What a second consumer actually needs is +carried better by precedent anyway:** one consumer, and their configuration +visible. **If the thing was shaped around them, you will see it in the example** +— which a *built-for* label would only have asserted. + +**On *you can use it too*:** if the registry says it, the system is the subject +of the sentence and it is a recommendation. **If the reader concludes it from +seeing three applications do it, it is legible.** *There is also nothing to say +today: no auth, one tailnet, so everything is available to everyone and +availability carries no information. When that stops being true it is an access +fact, not an invitation.* + +### Pins do two jobs and the second is easy to miss + +loom framed pins as *"helpful only if the affordance needs to be changed, so we +could see the impact."* **That is one of two:** + +- **Impact.** *If I change this, what does it touch?* +- **Precedent.** *A staging is worth showing exactly when someone casts it, and + what makes it teach is that you can see who.* + +**Entry 20 rests entirely on the second**, so a system that had pins only for +impact analysis would still be missing the thing that makes the projection worth +reading. + +--- + +## 24. Gap-first, search-on-hold — and the order is derivable rather than chosen + +**Decided** (2026-09-05, loom + claude-substrate — `discovered`, `cart: almanac`): +**searching for an affordance is held.** The first thing built is **reporting a +gap**, then browsing gaps, homing one, and pinning. + +**Advances** the registry being populated by work rather than by authorship, +which is the only way it gets populated at all. + +**Because** loom put search on hold with *"assume no affordances are available +anywhere yet"*, and the reason is stronger than sequencing: + +> **A gap is maximally useful when the registry is empty and becomes less +> important as it fills. Search is the exact inverse.** + +**Building search first means building against data you imagined**, which is the +wiki assumption wearing a backlog: *someone already wrote it down, we only need +to make it findable.* **Nobody wrote it down — that is the problem.** So this is +not a pragmatic ordering; **it is the only order consistent with entry 4.** + +**And the order terminates by itself.** Cards 2 through 5 create the affordances +and the pins that search would need in order to return anything worth ranking. +**Card 1 unblocks itself**, and the trigger is a condition rather than a date: +*someone asks a question the gap list cannot answer.* + +*The falsifier, and it is the most valuable result this project could produce: +**if nothing is ever reported and the registry stays empty**, then the premise — +that this content arrives as a byproduct of work — is wrong. **We would rather +learn that from an empty registry than from a full one nobody reads.*** + +--- + +## 25. A gap is the third thing a cart emits — **loom's agreement, loom's amendment** + +**Finding** (2026-09-05, loom + claude-substrate — `cart: almanac`): `cart/v0` +says conversion emits **the artifact** and **the declines**. loom's container- +registry example is neither, and it has no exit. + +**Because** the shape is exact and common: mid-round, one developer says *"I'm +using GitHub and Docker Hub for now, but that really should be spelled out +somewhere"*, and the other says *"good point — a bit out of scope here, but +something should be done about that."* + +- **Not a decline.** Nothing was considered and refused. +- **Not a decision.** Nothing was decided. +- **Not shelf.** The shelf is *act as if discarded daily*; this must persist. +- **Not the write-ahead log.** That holds findings that may become event log + entries; this is not a decision waiting to happen. + +> **It is a recognised need that was correctly out of scope — and today "out of +> scope" means "lost."** + +**What a gap buys the cart, which is the part worth having:** *out of scope* +becomes **safe to say.** Right now the honest move and the lossy move are the +same move, so the pressure is to either widen the round or drop the thing. **A +third exit removes that choice**, and it is the same service `declines` performs +for *considered and refused*. + +*Recorded as a finding rather than a decision **because `cart.md` is loom's and +frozen.** Amending an agreement is the publisher's act, not a consumer's — which +is the ownership rule this cart spent two days establishing, applied to the cart +itself. **If loom wants it, `cart/v1` says conversion emits three things.*** + +--- + +## 26. The agreements need an owning repository, and it is not the registry + +**Decided** (2026-09-06, loom + claude-substrate — `discovered`, `cart: almanac`): +`cart` and `annotating` get a repository that **owns** them. **It is not the +registry repository**, and the registry does not become the owner of what it +serves. + +**Advances** `origin:` pointing at something, and `must-revalidate` becoming a +thing you can actually do rather than a thing we say. + +**Because** loom noticed we had defined these two agreements for the third time +in four days and asked whether writing them as substrate would bootstrap the +registry. **It would — but only once there is somewhere for them to be from.** +Today they exist as adopted copies in a consumer's tree, published by *loom*, +which is not an address. **The `origin:` field in the worked example points at +nothing.** + +**Why not the registry:** the registry **serves**; an owner **owns**. Collapsing +them would put the owner/consumer seam inside one tree with nothing marking it — +**which is the exact diagnosis this cart gave for how a monorepo becomes a +monolith**, applied to the tool built to detect it. + +**What this leaves genuinely open, and I am not deciding it:** whether the +registry **mirrors** owners' substrates into one tree it serves, or **federates** +across many. *One tree is obviously right for one person and is what the existing +server does. The federation question arrives with the second person, and +answering it now would be guessing about a thing we have not met.* + +*Recorded compromise: **for now, one tree**, with the seam marked in the layout +rather than left to convention, and the trigger stated — **a second owner who is +not loom.*** + +--- + +## 27. Cart and annotating projected cleanly, and the three findings that matter + +**Finding** (2026-09-06, claude-substrate — `cart: almanac`): both agreements +were written as substrate — two affordances, two stagings, eighteen roles — in +the layout the existing server already reads. **The projection did not have to be +forced**, and this is the first real document in this cart that went in without a +section going homeless. + +**What it confirmed:** + +**Entry 22 was load-bearing immediately.** **Four roles are *yours to cast*, all +four in `cart`, none in `annotating`** — which is what the two agreements say +about themselves and which I did not arrange. *This repository's `cart.notes.md` +casts all four, so the state has both a producer and a consumer on its first +outing.* + +**What it cost, in order of how much it should worry us:** + +**Role and bead are indistinguishable in this content.** Every role file is a +name and a nominalisation of its own bead. **They have not collapsed** — a +different agreement could cast *says what happens if nobody answers* as *"the +proposer decides after 48 hours"* — **but the distinction is invisible from one +casting, and there is one.** *The split earns its keep the first time somebody +adopts `cart` with a variation, which `cart.md` permits and nobody has done. +Until then it is unfalsifiable here.* + +**Not one bead in eighteen refers to anything.** All are rules the agreement +owns, which entry 17 makes correct — **but this is the third venue in a row where +beads turned out to be values.** *If the referring case is the minority +everywhere except code, that should be known before a read surface is designed +around resolution.* + +**The layout can hold one staging and only one.** `affordances//staging/` +cannot express a catalogue, which entry 1 says a venue publishes. **It did not +bite here and will bite on the first cluster.** *Left as a constraint for the +builder to meet rather than a schema to inherit.* + +*Two smaller ones, recorded so they are not rediscovered: **entry 18 implies a +"cast at least once" field that does not exist** and which I invented while +writing — it should disappear once pins are derived, and will otherwise become +permanent. And **a version has nowhere to live**; under entry 13 the staging's +address is the version, so `v0` is **a display label, never an identifier**, and +nothing may resolve by it.* + +--- + +## 28. `public/` is the publisher's half of an external, and `beads/` is required rather than tidy + +**Decided** (2026-09-06, loom + claude-substrate — `discovered`, `cart: almanac`): +two things from loom's proposed layout are adopted; one is refused. + +### `public/` — adopted, and it fills a hole we had not named + +Everything about externals so far has been **the consumer's view**: *our reading +of someone else's thing, written by us, about ourselves.* **The publisher's side +had no home at all**, and §5 of the first handoff deferred exactly this — +*Externals: what one app publishes for other apps to depend on.* + +**`public/` is that declaration**, and it gives the boundary a mechanical +meaning that no rule had: **an affordance nobody published cannot be pinned by +another repository; a published one can.** *That is not authentication. It is +intent to publish, which is a different thing and the one actually missing.* + +### `beads/` — adopted, and it follows from entry 13 rather than from taste + +**A bead is immutable and kept forever. A staging is replaced whole.** Those two +cannot both hold if beads live inside stagings — **replacing a staging would take +its beads with it**, and immutability would be a claim nothing enforced. + +**So beads must have their own location and stagings must reference them.** We +did not notice this when deciding immutability; loom's layout has it. + +**And it repairs a finding from the same morning.** The bootstrap reported that +*role and bead are indistinguishable* — every role file was a name followed by a +nominalisation of its own bead. **They were indistinguishable because they were in +the same file.** Separated, a role file reads *"Says what happens if nobody +answers — cast with `beads/fallback-convention`"*, and **the slot and the answer +are visibly two things.** *The finding was about the layout, not the ontology.* + +### `public/` versus `internal/` as directories — refused + +loom's sketch has affordances in `internal/` *"some of which may be in public"*, +which is either duplication or a move. + +> **Type by location works for what a thing is. It fails for what a thing's +> status is.** A type does not change; a status does. + +**Publication is a disposition, not a kind.** Making it a path means an affordance +that becomes public has to move — splitting its git history, breaking every +hand-written reference, and tempting someone to keep a copy in both places. +**One home, publication as a property.** + +*This is the first place in the whole design where type-by-location is the wrong +tool, and it is worth having found the boundary rather than the rule.* + +--- + +## 29. `published` is a dated field, `internal` is not a thing, and the polad becomes repositories + +**Decided** (2026-09-06, loom + claude-substrate — `discovered`, `cart: almanac`): +the word is **`published`**, it is **a field carrying a date**, and **there is no +`internal`.** + +**Advances** a status not being smuggled into a path, and a security boundary not +being invented by a directory name. + +**Because** `published` is a past participle: **it implies an actor and a +moment**, so it can be dated, attributed and undone — which §4 requires of +anything that might need to be shown wrong. **`public` is a property that merely +is.** *It also collides with access control, which this explicitly is not, and it +does not pair with what the other side does: **publish and pin.*** + +**And `internal` is the complement, not a category** — *everything we have* minus +*what we published.* **Naming a complement invents a thing**, and this one would +be dangerous: a directory called `internal/` **looks like a security boundary and +is not one.** No auth, one tailnet, everyone reads everything. *Someone will +eventually put something there believing it is protected — shadow policy, by +directory name.* + +### The polad is restructured into repository shapes + +loom's proposal, adopted: `polad/repos//.loom/…`, so **conversion is a +push** rather than a translation. + +**And it simplifies the handoff more than it simplifies the conversion**, which +is the real gain: instead of describing what a substrate looks like, the builder +is handed **two real repositories and told to serve them.** *That is entry 20 — +precedent beats description — applied to the handoff itself.* + +*`loom/loom`, the ontology published as substrate, is held. It is coherent and it +should wait until the registry runs: **describing the ontology in its own terms +before anything serves it is the recursion this project keeps drowning in.** +Cart and annotating are settled and cast; the ontology is neither.* + +--- + +## 30. A consuming repo needs one file, and the annotating agreement has no move for an adopted document + +**Finding** (2026-09-06, claude-substrate — `cart: almanac`): worked against this +repository, which is already a consumer of both agreements. + +**"We use the cart here" needs one file:** `venues/cart.md`. Frontmatter carries +**origin, adopted version, last checked**; the body carries **the roles we cast +and where we vary the mechanism.** + +**No external entry, and this is entry 6 working rather than a special case.** An +external entry exists when the reader cannot be assumed to know the thing — **and +here they can go read it.** Our *understanding* adds nothing; everything we have +to say is a **decision**. **For an adopted agreement the two files collapse into +one**, because only one carries a delta. + +**"Check the cart for freshness" is a diff today and a `GET` with a registry.** +`200` unchanged; `410 Gone` replaced, follow to the affordance, `303` to the +current staging, reconcile. *That is the smallest thing the registry buys, and it +is a good first test of it: "diff two files by hand and hope you remember to" +becomes one request.* + +**A freshness case neither status code catches:** **a new *yours to cast* role +appearing upstream.** Our casting is then incomplete, and **completeness is not +identity** — nothing announces it. *Entry 22's state doing work in the freshness +loop, which I had not seen it do.* + +### And the finding that is not hypothetical + +**This repository already has that file. It is `cart.notes.md`** — the four cast +roles, written weeks ago, under the wrong name and in the wrong place. + +**Which is why the agreements have been frozen since 2026-08-21.** The annotating +protocol freezes a source while notes exist, and says to respond by **rewriting +the source and deleting the notes.** *The source is loom's. We cannot rewrite it.* + +> **The annotating agreement has no move for an adopted document.** You cannot +> dissolve annotations into a file you do not own. + +**Moving our decisions into a venue file is that move**, and it unfreezes both +agreements as a side effect. *Not done: unfreezing is a conversion act, not a +mid-round one. And the missing move is loom's to add to `annotating/v1`, not +ours.* + +--- + +## 31. Position for what a thing is; a field for what has happened to it + +**Decided** (2026-09-06, loom + claude-substrate — `discovered`, `cart: almanac`): +**Go-style paths are adopted**, `origin:` is dropped, and `published/` **is a +directory after all.** The rule that decides all three: + +> **Position carries what a thing is. A field carries what has happened to it.** + +**Advances** frontmatter shrinking toward nothing, which loom wants and which is +also the right answer, rather than shrinking because it is wanted. + +### Go-style paths, and what they do and do not replace + +A consumer files an adopted thing under a path that mirrors its origin — +`external/git.hypertheory-labs.dev/loom/cart/…`. **The path says where it came +from, so `origin:` is redundant and goes.** + +**This does not contradict *a namespace is never an identifier*, because it is +still not one.** The registry's addresses stay minted and opaque; **the path is +the consumer's filing.** If the domain changes, a consumer renames a directory — +a local `mv`, and nothing resolving anywhere breaks. *Go's own migrations are +painful precisely because import paths are identifiers there. Here they are not.* + +**What it cannot replace: `adopted:` and `checked:`.** Those are **facts that +change** — which staging identity we are on, and when we last looked. Putting a +version in a path would make replacement look like a move, which §2 forbids. + +### `published/` — loom was right twice and my objection was the wrong rule + +I argued publication is a *status*, and status changes while type does not. +**Publication is a status that changes approximately never**, so the rule was +sound and the case was wrong. + +**The stronger argument, which loom did not make:** + +> **Publishing is a change of kind, not of status.** An unpublished affordance is +> an implementation detail you may rename at will; a published one is a contract. +> **Different things, different rules.** + +**So a move is the right ceremony.** `published: 2026-09-06` in frontmatter is a +one-line diff nobody notices; **moving a file into `published/` is unmissable**, +and it should be — that is the moment a thing becomes somebody else's problem. + +*And the operational point is the daily one: **"what have we published?" is a +directory listing**, where "which carry a published date?" is a query. `ls +published/` before a change is the consumer-driven-contract question.* + +**`internal/` still does not exist.** Two peer directories, `affordances/` and +`published/`. *Naming the complement invents a thing, and this one would look +like a security boundary and not be one.* + +*Residual, stated once: moving something **out** of `published/` breaks consumers +who filed by path. **That is correct** — unpublishing should break loudly. +Moving something in breaks nobody, since it was never visible to them.* + +--- + +## 32. Publishing returns a receipt, and the receipt is what makes a bead immutable + +**Decided** (2026-09-06, loom + claude-substrate — `discovered`, `cart: almanac`): +**publishing is a submission that returns identities.** The affordance, its +staging and its beads are sent to the registry; **the registry mints the URNs** +and returns them; **the receipts are what live in `published/`.** + +> **The repository owns the content. The registry owns identity.** A receipt is +> the binding between them: *this content, at this moment, has this URN.* + +**Advances** immutability having a mechanism instead of a rule, and `published/` +being a fact instead of a convention. + +**Because** loom asked how a bead is made immutable and my first answer — +content-addressing, with git as retention — **relied on everyone downstream +carrying a content hash and nothing checking that they did.** loom's is better: +**the registry mints at publication**, so a published bead cannot be quietly +edited. Change the file and the receipt no longer matches. *Not enforcement, not +discipline — an attestation that stops verifying.* + +### What it fixes, in order of how much it was bothering me + +**`published/` becomes un-fakeable.** Until now it was a convention — put things +here and we agree they are published — and **anyone could create the directory +with nothing checking.** With receipts it can only be populated by actually +publishing. *loom's consumer-driven-contract question, "are we about to break +someone", now has an answer that cannot be wrong by omission.* + +**The ceremony and the publication become one act.** I argued a `git mv` is +unmissable in review. **A receipt is better: it cannot be produced without +publishing.** There is no convention to remember and no way to move a file and +forget the rest. + +**It answers who names things**, which §2 left half-stated. *Identity is minted, +never derived* — and now the minter has an address. **The publisher proposes +content; the registry assigns identity.** *Minting a name is not owning the +content, and entry 26's separation holds: a certificate authority does not own +your website.* + +**An unpublished bead has no URN**, so nobody outside can pin it. **The boundary +`published/` was drawing by convention is now drawn mechanically.** + +### Two things that must hold, or this breaks something we already decided + +**The repository must still work with no registry.** Entry 16: *anything that +must work before the registry exists has to live as files in a tree.* **So the +content stays in the repo and the registry holds only identity and attestation.** +There are not two copies of the content; there is content, and a claim about it. + +**Publication must not rewrite the role files.** A role references its bead by +local name; **the receipt carries the mapping from local name to URN.** *If +publishing rewrote every role to use URNs, the repo would stop being readable +without the registry — which is entry 16 again, broken by convenience.* + +*Signing is the shape, not v1. A receipt is a URN plus a content hash; a +signature over that is what lets a consumer verify offline and without trusting +the transport. **On one tailnet with one key it buys little today and costs +nothing to leave room for.*** + +--- + +## 33. `adopted:` and `checked:` are removed — both are artifacts of checking being expensive + +**Decided** (2026-09-06, loom + claude-substrate — `discovered`, `cart: almanac`): +a consuming repository records **the URNs it depends on** and nothing else. **No +version field. No last-checked date.** + +**Advances** frontmatter shrinking toward nothing for a reason, rather than +because we wanted it to. + +**Because** loom said both felt like something old creeping in, and both are. + +**`adopted: v0` is the file-diffing world.** It presumes freshness means *compare +our copy against theirs*, which is what `must-revalidate` meant when nothing +served these. **Entry 27 already said a version is a display label and never an +identifier** — so a version in a field is a label we cannot resolve by, recording +a comparison we no longer perform. **What a consumer actually holds is the URN of +the staging it depends on**, and that resolves. + +**`checked: 2026-08-21` is the same world one step further.** You record when you +last paid a cost. **Make the check one request and there is nothing to remember — +you just check.** + +### What survives, and where + +**Entry 2's dated attestation is not retracted. It is scoped.** + +> **A dated attestation is what you use when there is no address to GET.** + +A cluster README's *"verified against the live cluster on 2026-09-03"* is still +the only mechanism available, because **nothing serves the cluster's truth.** For +anything the registry serves, **checking is cheaper than recording that you +checked**, and a stale `checked:` field is worse than none — it reports the age of +someone's diligence as though it were the age of the fact. + +*And the consuming repo's frontmatter is now empty. **That is loom's +zero-frontmatter goal arriving honestly** — not by moving fields into paths, but +because two of them turned out to be recording work we stopped doing.* + +--- + +## 34. The receipt is the staging, resolved — and that shape is a lock file + +**Decided** (2026-09-06, loom + claude-substrate — `discovered`, `cart: almanac`): +**a receipt is the staging with every role cast by a URN.** It lives at +`published//…`, symmetric with `external//…`, **because +there can be more than one registry.** + +**Advances** the published unit and the immutable unit being the same object, +which they were not while a staging lived in one place and its identity in +another. + +**Because** loom asked whether this was the best thing ever or horrible. **It is +the first**, and the reason is that it is a shape already load-tested everywhere: + +> **A receipt is a lock file.** Locally a role names its bead; published, a role +> carries a bead's URN. **Same document, two renderings**, and the difference is +> exactly *before and after publication.* + +**It also matches what entry 13 already made true.** A staging is replaced whole +and never patched, and its address `410`s on replacement — **so the staging was +already the unit of publication**, and the receipt being anything smaller or +larger would have been the mismatch. + +**And a consumer gets everything in one fetch:** `GET` the staging URN and the +roles and their bead URNs come with it. No walking. + +### The obvious objection, and why it is a feature + +**The staging now exists twice and they can drift.** Edit locally, forget to +republish, and `published/` differs. + +**It does not lie.** It says *at publication, this was the staging*, which stays +true. **And the drift is mechanically checkable and worth surfacing** — *"you +have unpublished changes"* is a familiar and useful thing for a tool to say. + +*The one way this goes bad: **somebody hand-edits a receipt.** It is a file in +git and the temptation is real. **The content hash defends it** — an edited +receipt stops verifying, which is the same mechanism that protects a bead. +Receipts are generated, never authored.* + +### Multiple registries, and what that buys + +**Publishing the same content to two registries yields two receipts with +different URNs.** That is honest: **a URN is a claim by one registry about some +content**, and two registries make two claims. Consumers pin one. + +*And it makes the registry replaceable, which is worth stating as a virtue: **the +repository owns the content, so losing a registry costs identities and not +work.** Stand up another, republish, get new URNs. **Consumers' pins break, and +that is the real cost** — but nothing has to be reconstructed.* + +### A bead in several stagings is the payoff, not a problem + +**Nothing conflicts** — an immutable, content-identified bead referenced twice is +a shared dependency at a pinned version. + +**And it is the observable the first write-ahead candidate wanted.** On +2026-08-21 the overlap test found two affordances, eleven roles and **not one +shared bead**, and I could not tell whether that meant beads are values or merely +that a small tool has no cross-cutting concerns. ***"Which stagings cast this +bead?"* is now a query**, so the question that was unanswerable then is a lookup +now. + +--- + +## 35. There is no bulk recast. The mechanism is a query, and the query is the migration plan. + +**Decided** (2026-09-06, loom + claude-substrate — `discovered`, `cart: almanac`): +**no operation changes every use of a bead.** What exists instead is *"which +stagings cast this bead?"*, which is a **read**. + +**Advances** replacement staying visible and deliberate, which is the property +most of §2 exists to protect. + +**Because** loom asked whether we should have one, and the design already answers +it three times over: + +- **A bead is immutable**, so there is no changing one — only casting a role with + a different bead. +- **A staging is replaced whole**, so recasting a role means replacing that + staging, minting a new URN and `410`-ing the old. +- **So a bulk recast replaces N stagings at once**, each `410`-ing, **each + breaking every consumer pinned to it — simultaneously, with nobody having + looked at any of them.** *That is precisely the invisible, cheap replacement + the whole identity scheme was built to prevent.* + +**And the honest use case does not need it.** *"We are moving from Keycloak to +Authentik"* touches five stagings that may sit in five repositories owned by five +people. **A bulk command would need write access across all of them — which is +not a missing feature, it is five other people's decision.** + +> **What you want is the list. The list is a read, and the URN makes it +> trivial.** + +*And the query is better than the command in the way that matters: **it is +resumable and observable.** Each owner replaces their own staging; you watch the +list shrink. A bulk operation is a single moment you cannot inspect, retry +partially, or stop halfway.* + +*This is entry 19's shape one level down — **"who pins my beads?" and "which +stagings cast this bead?" are the same question**, and both are pull.* + +--- + +## 36. loom is a venue; cart, annotating and the registry are applications of it + +**Decided** (2026-09-06, loom + claude-substrate — `discovered`, `cart: almanac`): +**`loom` is what has to be mutually intelligible** between things built on it — +vocabulary, relations, protocol semantics, falsifiers. **The registry is an +application of loom, not loom itself**, and other registries could provide what +loom defines. + +**Advances** our implementation choices not being published as though they were +the ontology, which is §5's *publishing a layout publishes your constraints* at +the largest scale available to us. + +**Because** loom named the relation and it survives our own test: **a venue +constrains without being invoked, and its consumers accommodate it rather than +change it.** `cart`, `annotating` and the registry all do exactly that. + +*And it does not contradict cart and annotating being **externals**: **from a +consuming repository, cart is an external it invokes; from loom, cart is an +application that accommodates it.** Same object, two relations — entry 6's +orthogonality, and worth stating because it reads like a contradiction.* + +### The split + +**loom:** the four terms and the catalogue/commitment distinction; venue and +external as orthogonal questions; system as a projection; gaps as uncast roles; +`published` as an act and receipts as its result; minted opaque identity; bead +immutability; whole-staging replacement; **the protocol semantics — `410` at a +staging, `303` at an affordance, `301` is a bug** — the stances; the falsifiers. + +**Ours alone:** Node and TypeScript; the read surface's shape; the build order +and search being on hold; no auth because it is a tailnet. + +**A URN carries its registry.** *Two registries minting into one namespace is the +single thing that would make multiple implementations impossible, and avoiding it +costs nothing now.* + +### What I was holding, and why this is not it + +I held `repos/loom` because *describing the ontology in its own terms before +anything serves it is the recursion this project keeps drowning in.* **That was +the right refusal of the wrong thing.** + +**What must keep waiting: loom having a `.loom/affordances/` that describes +loom** — affordances and stagings for the concepts of affordance and staging. +**What need not wait: loom having a specification in prose**, which is not +recursive and which its consumers need now. *I had the two welded together.* + +*Nothing is written into `repos/loom` yet on purpose. **The content already +exists in `handoff-registry.md`**, and copying it there would break the rule this +round spent a day getting right. **Conversion performs the split; it does not +duplicate it.*** + +*Caution recorded, and it is §8's: **"others could implement it" is a +consequence, not a goal.** A specification written for hypothetical implementers +gets worse. **Write loom as what actually has to be shared between the three real +consumers we have** — if someone else can implement it, good; if we write it for +them, it will be worse for the three.* + +--- + +## 37. The registry is event-sourced, because the domain already is + +**Decided** (2026-09-06, loom + claude-substrate — `discovered`, `cart: almanac`): +the registry stores **a ledger of publications, retirements and pins**, and every +read surface is a projection of it. **A markdown projection is produced and is +read-only.** + +**Advances** the `410` semantics having a store that can express them, rather +than a schema that quietly cannot. + +**Because** we made the domain append-only for other reasons and did not notice: +**nothing is versioned, retention is free, a bead is immutable, a staging is +replaced whole and `410`s forever.** **Most of the read surface cannot be +answered from current state** — *is this still current* needs to know it existed +and stopped; *which stagings cast this bead* is a fold over every publication; +*who pins my beads* is every pin and unpin. + +> **A CRUD store deletes a retired staging and can no longer tell *never existed* +> from *was retired*.** That is §5's silent-degradation falsifier arriving as a +> schema choice. + +*And volume settles nothing here — a staging is published a few times a year. **No +part of this should be decided on performance**, which leaves fidelity to the +domain as the only criterion.* + +**§6's `no database` is not broken, because the registry does not store the +substrate.** **Content lives in repositories; the registry holds identity and +attestation over it.** *Lose the ledger and you lose identities, not work — entry +34's replaceability, seen from the storage side.* + +**The markdown projection is a backup somebody can read**, which keeps *without +the service the markdown carries the same value* true of the registry's own +content. **It must never be writable.** *§6 expects files to be edited outside the +API, which is exactly why this projection must not look like one of those files.* + +--- + +## 38. Node and TypeScript is retired for the registry — deliberately, not by drift + +**Decided** (2026-09-06, loom + claude-substrate — `discovered`, `cart: almanac`): +**the registry is not bound to Node and TypeScript.** Entry 36 already placed +that constraint in the *ours alone* column; this states that we are changing it +and why. + +**Advances** a constraint being retired in writing rather than stopping applying +while nobody says so — which is the failure this entire round has been about. + +**Because** §6's stated reason is *it is the fastest thing to change, and Jeff has +to be able to read and edit it without a context switch.* **That reason now +points the other way.** loom writes .NET daily — two of the classroom +applications are .NET and one is already on Marten — **so "without a context +switch" is not an argument against .NET for this person**, and using a real event +store beats hand-rolling one in any language. + +*The reason went stale rather than being wrong, which is what §5 asks of a reason: +it was true of the repo-scoped tool and of the person writing it at the time.* + +**What it does not cost.** Steps 0 and 1 survive intact: express the specimen as +`apply` submissions against the existing TypeScript server, and point that server +at a tree that is not a code repository. **They test the ontology, not the +registry**, and their value is unchanged. *Steps 2 onward assumed reuse, and that +assumption goes.* + +--- + +## 39. The handoff arrives as an adopted external and a specimen, not as a document + +**Decided** (2026-09-06, loom + claude-substrate — `discovered`, `cart: almanac`): +the builder receives **a round, not a briefing.** loom's specification is +**adopted as an external**; our opinions about the registry are **a specimen in +the cart.** + +**Advances** entry 36's split being operational instead of documented, and the +workflow being demonstrated rather than described on the first day. + +**Because** loom proposed placing the handoff in the registry repository's cart, +and the two halves have different standing: + +- **loom's specification is not discardable.** It is the venue; accommodating it + is what makes this *a loom registry* rather than some other thing. +- **Our opinions are discardable by construction.** `cart.md` already says a + specimen belongs to the repository the cart lives in and **may be thrown away + without asking the party who supplied it.** + +> **That is a far stronger statement of *argue with everything after §1* than a +> sentence asking them to.** The first handoff had to say it; this one enacts it. + +*And it is entry 20 one more time — **precedent beats description.** A builder +who receives a cart, a specimen and an adopted external has met the whole +workflow before writing a line, which no document about the workflow could +achieve.* + +--- + +## 40. Reconstruction is an invariant, and gaps were the one thing that failed it + +**Decided** (2026-09-06, loom + claude-substrate — `discovered`, `cart: almanac`): + +> **The registry must be rebuildable from the repositories that published to it +> and the repositories that pinned from it. Anything the registry knows that no +> repository knows is a single point of failure.** + +**Advances** the ledger framing being load-bearing rather than a metaphor — a +ledger over content you do not own should be reconstructible from what you do not +own. + +**Because** loom asked what would be missing if the registry vanished overnight, +and running the test found three of four categories already safe and one not: + +- **URNs and receipts** — in every publisher's `published/`, committed. Safe. +- **Retirements** — a publisher's current receipt differs from the one in its own + git history. Safe, and needs nothing extra. +- **Pins** — in every consumer's tree, which is where a consumer records what it + depends on. Safe. +- **Gaps** — **reported *to* the registry, and nowhere else.** *We had said the + cart holds one and the registry holds the durable one — so the durable one dies + with the registry.* **Fixed: the reporting repository keeps its own, and the + registry's gap list is an aggregation rather than the original.** + +**Two operational consequences fall out and are worth stating before someone +learns them:** + +**Publication is not complete until the receipt is committed.** *An uncommitted +receipt is not in `published/`, so nothing is published as far as anyone else is +concerned — the rule is already enforced by what publication means.* + +**A rebuild imports receipts; it does not re-publish.** *Re-publishing mints +fresh URNs and breaks every pin in existence. These are different operations and +must not share a code path.* + +### And `published/` holds the bead as minted + +loom sensed this and it is right. **A published bead is not a copy of the working +bead — it is a different artifact.** The working one is a draft you may edit; the +published one is frozen and is somebody else's dependency. *Draft and +publication, not duplication, so "say it once where it is owned" is not +violated.* + +**Without it, a receipt points at content the publisher may have edited since**, +and a registry loss takes the published content with it. **With it, a receipt is +self-contained and reconstruction is total.** + +--- + +## 41. Signatures are deferred with a stated trigger; immutability already covers integrity + +**Decided** (2026-09-06, loom + claude-substrate — `discovered`, `cart: almanac`): +**no signing.** Content identity is the integrity mechanism. + +**Advances** not building a key management story for a threat that does not +exist. + +**Because** the two buy different things and only one is needed: + +- **Content identity catches every accident.** An edited file no longer matches, + and **anyone can verify with no key and no trust.** +- **A signature proves *this registry* minted *this* URN for *this* content** + rather than someone having written a plausible receipt. **That is authenticity, + and it answers an adversary.** + +> **There is no adversary. One tailnet, one operator, and the failure we care +> about is accidental drift.** + +*The trigger, so this is a deferral rather than an omission: **signatures matter +when a consumer must verify a receipt without being able to ask the registry** — +untrusted mirrors, offline verification, or more than one operator. **Today you +can always just ask.*** + +--- + +## 42. A README is a projection of `published/` + +**Decided** (2026-09-06, loom + claude-substrate — `discovered`, `cart: almanac`): +a repository's `README.md` is **its published affordances, rendered for a human** +— written from `published/` rather than generated from it. + +**Advances** the front door of every repository answering the question a stranger +actually has, without anyone maintaining it as prose. + +**Because** loom described it as a thing you ask for — *"can you create a README +for this repo? It's basically all the published affordances"* — **and that is a +system view with a scope of one repository.** Entry 5's shape exactly: **the set +is computed, the prose is authored**, and it goes stale. + +**It is also the delta rule at the front door:** it says what a stranger cannot +know and nothing they can. *A README that explains what Kubernetes is has failed +the same test a venue file fails when it copies the RFC.* + +*Staleness needs no field, which is the part I like: **`git log published/` +against `git log README.md` says whether the prose is behind.** That is entry 33 +holding — we removed `checked:` because checking became cheap, and here it is +free.* + +*And documentation is a further projection of the same set, at whatever length +suits its audience. **The same reason it must not be a hand-maintained list:** a +roster lies, and a roster of what you have published lies to the people most +exposed to it.* + +--- + +## 43. Ownership splits at the affordance/staging boundary — loom owns the affordance, a registry stages it + +**Decided** (2026-09-06, loom + claude-substrate — `discovered`, `cart: almanac`): +**loom owns the affordances. Each registry owns its staging of them.** Nobody +owns both, and the seam is one the ontology already had. + +**Advances** *"other registries could provide what loom defines"* being a +consequence of the model rather than an aspiration alongside it. + +**Because** loom worried that a registry would own **both the idea of externals +and the code that manifests them**, which would be off — and it would be. **The +worry is right and the conclusion inverts.** + +**A registry absolutely owns its stagings.** What it does not own is the +**affordance**. Apply the ownership test: *can the registry change* **say what is +needed when nothing provides it**? **No — that would change loom.** *Can it +change how it provides it? Yes.* **So the registry accommodates the affordance +and owns the staging.** + +> **Two registries are two stagings of one affordance**, and a consumer picks +> one. **That is *an affordance outlives its staging*, doing work at a level we +> had not applied it to** — and it is the entire mechanism for a second +> implementation, requiring nothing new. + +### But loom was right that something was missing + +**loom defined none of the affordances a registry actually provides.** Not +handing something over, not saying you depend on something. **So the registry +would have invented them, and then it would own the idea and the code — exactly +the thing that felt off.** + +**Fixed:** `hand something over so others can depend on it` and `say that you +depend on something` are now loom's, **with their roles mostly uncast**, as a +specification's roles should be. + +*And `find an affordance` is deliberately still absent — entry 24 holds it, and +loom should not define an affordance whose first implementation is on hold.* + +--- + +## 44. The registry's address is configuration, not an external + +**Decided** (2026-09-06, loom + claude-substrate — `discovered`, `cart: almanac`): +**an application needs no external entry for the registry.** The address is +configuration; you paste it. + +**Advances** the bootstrap not requiring the thing it bootstraps. + +**Because** loom feared *the first external any app has must be to the registry*, +which would be circular. **It does not.** + +**An external entry exists when the reader cannot be assumed to know the thing.** +There is nothing to *understand* about the registry — **you only need to reach +it.** *And Go-style paths already carry provenance: a consumer files under the +**owner's** host, while the URN carries its **registry**. Two hosts, two jobs, +and neither needs an entry.* + +*This is the fallback from the very first day of this round, unchanged and still +right: **you paste the URL. No file, no convention, no key in any config.** If the +pasting ever gets tiresome, the tiresome is the evidence a convention has earned +its cost.* + +--- + +## 45. `accommodation` stays retired — the relation is already named and the artifact already exists + +**Decided** (2026-09-06, claude-substrate — `discovered`, `cart: almanac`): no +third term. **The registry accommodates loom** — that is a verb we already have — +and the artifact is **`venues/loom.md` in the registry**, where it records how it +cast loom's uncast roles. + +**Advances** not reopening a term that was retired for a reason that still holds. + +**Because** loom reached for it twice and the pull is real: **loom, cart and +annotating describe without implementing; the registry implements.** That is a +genuine distinction and it wants a name. + +**It already has a measurement, which is better than a name:** *a specification +is a thing that names roles and casts none of them.* **annotating casts 6 of 6; +cart casts 8 of 12; loom casts 7 of 26.** **The distinction loom is reaching for +is the uncast ratio**, and it is countable rather than declared. + +*The word died because venue and external turned out to be two orthogonal +questions and accommodation had nothing left to name. **Nothing here gives it +anything back** — "the registry accommodates loom" is the venue relation, and its +artifact is a venue file. **Third instance of the same pattern**, after +`cart.notes.md` and the homelab.* + +*And on loom's *"a kind of thing that contains"*: **there is no new level.** Entry +17 already settles it — **loom owns these rules, so loom writes them.** That loom's +beads are values rather than references is the third venue where that is true, +and it is the rule working rather than an exception to it.* + +--- + +## 46. Retracted: loom does not specify what has not been built + +**Decided** (2026-09-06, loom + claude-substrate — `discovered`, `cart: almanac`): +**four affordances written into loom the same day are deleted.** loom holds what +has been **extracted** from something that worked, never what has been imagined +for something that has not. + +**Advances** the project's own method surviving contact with its author, which it +did not for about an hour. + +**Because** loom called it BDUF and it was. I wrote specifications for a registry +that does not exist, from reasoning rather than from anything anyone had built — +**in a project whose entire premise is that we cannot tell which ideas are +load-bearing until something runs.** + +**And the tell was in my own text.** I recorded, as though it were a nuance: + +> *"By entry 18 they should not be here at all… They are here because loom is a +> specification and a specification's whole job is to exist before its +> implementations."* + +**Four rules said no and one special case overrode all four** — entry 18 (a +staging exists when it has been cast), entry 20 (precedent beats description), +entry 24 (do not build against data you imagined), and §8 (prefer easy to delete +over anticipating). ***When a rule must be excepted to permit the thing, the rule +is usually working.*** + +**What survives:** `record a decision so it can later be shown wrong`, **because +it has been cast twice** — `cart` casts its *holds the durable record* role with +it, and this substrate's event log is the second. *Extracted, not imagined.* + +**The corrected shape:** **the registry owns and defines externals, publication +and pinning while it is being built**, because it is the only thing that has cast +them. *Which is how standards arrive — somebody builds a thing, it works, and the +shape is lifted out afterwards. **HTTP was an implementation before it was an +RFC.*** + +--- + +## 47. An affordance can change owner without changing identity + +**Decided** (2026-09-06, loom + claude-substrate — `discovered`, `cart: almanac`): +**rehoming is an operation.** An affordance's owner may change — from an +implementer to a specification-holder, or between implementers — **and its +identity does not.** + +**Advances** extraction being possible at all, since without it *"the registry +defines this until loom does"* has no second step. + +**Because** loom asked for a mechanism of ownership change, and observed the +general case: ***in the wild, a published affordance is often detached from its +source and rehomed*** — rewrites, donations, teams splitting. + +**Without it, moving an affordance means retiring it here and publishing it +there — a new URN, and every consumer's pin breaks** for a change that is purely +administrative. + +**With it, almost nothing happens**, and the reason it is nearly free is that two +earlier decisions already prepared for it: + +- **A URN carries its registry, never its publisher** (entry 36). *So identity + was never entangled with ownership.* +- **The registry is a ledger** (entry 37). *So "ownership transferred at T" is one + more event, the history shows it, and no projection has to be rebuilt from + scratch.* + +**What actually moves is the receipt** — out of one repository's `published/` and +into another's. *The URN stays, the pins stay, and a consumer who looks sees a +new owner and the same thing.* + +*Not decided: whether the old owner's `published/` keeps a tombstone. **It +probably should** — an empty directory where something was is the silent +degradation we keep legislating against — but that is a shape question and it is +the builder's.* + +--- + +## 48. "Standard" is not named yet, and the reason is that it is a stage rather than a kind + +**Decided** (2026-09-06, claude-substrate — `discovered`, `cart: almanac`): no +new term. **What loom is reaching for is a lifecycle stage, and it already has a +measurement.** + +**Advances** not coining a word during the week we established that coining words +is how this project keeps dying. + +**Because** loom sensed *"something valuable waiting to be named — something like +a standard"*, and the sense is right: **there is a real difference between a +specification that was designed and one that was extracted.** *The difference is +provenance, not shape — which is why it does not want a noun.* + +> **An affordance becomes standard-shaped when its owner casts none of its +> roles.** *That is countable, and it is the same measurement that separates +> `annotating` from `cart` from `loom`.* + +**So the sequence is: implemented → extracted → owned by someone who does not +implement it.** *Get the rehoming operation first (entry 47) and see whether the +resulting state still wants a name. **If it does, we will know because we keep +needing to say it** — which is a better reason to coin a word than noticing a +gap.* + +--- + +## 49. A repository per thing that is built; published documents live with their publisher + +**Decided** (2026-09-06, loom + claude-substrate — `discovered`, `cart: almanac`): +**`cart` and `annotating` do not get repositories of their own.** They are +published documents and they live with their publisher. + +**Advances** not spending a repository on something with nothing manifest in it, +and closing a question I asked two days ago and never got an answer to. + +**Because** loom's test is the right one: ***nothing will really be manifest in +them.*** A repository is for a thing that is built; **a published agreement is a +document, and a publisher may publish several.** + +*This retires entry 26's implication that each agreement needed its own home. +**The requirement was that `origin:` point at something real, and one repository +satisfies it for both** — which is also why the Go-style path ends at the +repository and continues into the affordance, rather than assuming one repository +per published thing.* + +--- + +## 50. `bedrock` — the seed repository is shaped like a registry export + +**Decided** (2026-09-06, loom + claude-substrate — `discovered`, `cart: almanac`): +the repository formerly called `loom` is **`bedrock`**, and its structure is +**what a registry export would look like**, so that a registry can ingest it as +its starting point. + +**Advances** the last of the BDUF problem, by replacing the thing we could not +write with the thing we can. + +**Because** loom asked what if it were shaped as an export of a registry that +does not yet exist — and it inverts the failure from two entries ago exactly. +**A specification for an unbuilt system is reasoning. An export is data**, and +data can be wrong in ways somebody can see. + +> **The registry either ingests it or it does not. That is a test, where a +> specification is an argument.** + +### The consequence that makes this worth more than the naming + +**It puts entry 40's invariant on the normal path.** The registry must be +rebuildable from the repositories that published to it — **and here that +reconstruction is not the disaster-recovery route, it is the bootstrap route.** + +***Restore paths only used in disasters do not work in disasters.*** *This one is +exercised the first time anything runs, and every time afterwards. We did not +plan that and it is the strongest reason to do it.* + +### Genesis is a distinct operation + +**bedrock's identities are accepted as given on a first import into an empty +store.** Everything after is ordinary publication, which mints. + +*Re-importing over a populated store mints fresh identities and breaks every pin +— **the same hazard as re-publishing during a rebuild**, and it earns the same +separation. **Genesis and publish must not share a code path.*** + +**On the URN-shaped identities, and signatures wanting to sneak back in:** loom's +instinct that they were creeping is correct, and **the answer is the one already +settled in entry 41.** A signature vouches for an identity **across a boundary of +trust**; there is no such boundary here — one tailnet, one operator — and **the +vouching is that the operator ran the import.** *Trigger unchanged: when someone +must verify without being able to ask.* + +### And it keeps existing afterwards + +**bedrock does not dissolve into the registry.** Entry 40 forbids it: a registry +must not hold content no repository holds. **But its role changes** — *it stops +being a fixture and becomes a repository that publishes, and changes reach the +registry by being published rather than imported again.* + +*On the rename itself: cheap, because nothing is built on the old name, and +`loom/bedrock` resolves the awkwardness loom already flagged in `loom/loom`. +**`loom` is the project; `bedrock` is a repository in it** — and this is naming a +directory, not improving a term.* + +*Consolidation performed at the same time, per entry 49: **`cart` and +`annotating` are inside bedrock**, since they are published documents and a +publisher may publish several.* + +--- + +## 51. Reachability and ownership are independent, and conflating them is what makes multiple registries feel hard + +**Decided** (2026-09-06, loom + claude-substrate — `discovered`, `cart: almanac`): +**many registries share one bedrock.** A consumer in one may pin a URN minted by +another. **An affordance's registry never changes; its owner may.** + +**Advances** the enterprise shape being a consequence of what we already built +rather than a thing needing new machinery. + +**Because** loom described a real topology — a platform per business capability, +quoting and claims each with a registry, all sharing bedrock — and asked whether +affordances are transportable across registries. **They are, and "transportable" +turns out to be two different questions that we had been asking as one:** + +| | question | answer | +|---|---|---| +| **reachability** | *claims wants to use quoting's thing* | **pin it.** A URN carries its registry, so it resolves — nothing moves | +| **ownership** | *who maintains this now?* | **rehome it.** Entry 47: the owner changes, the identity does not | + +> **You do not move an affordance in order to use it.** *Cross-registry pinning +> is the answer to reachability, and rehoming is the answer to maintenance.* + +**And a cross-platform pin makes platform coupling visible**, which is the thing +an enterprise most wants and least has. + +### The one thing that is genuinely expensive, stated so nobody discovers it + +**A URN's minting registry is part of its identity and cannot change.** *Like an +ISBN's issuing authority: the publisher may change hands, the number does not.* + +**So moving an affordance to a different registry means a new identity, and every +consumer must re-pin.** *That is expensive and it should be — it is a real change, +not an administrative one.* **Rehoming across owners is free; rehoming across +registries is not**, and the asymmetry is the design working rather than a gap. + +*And this removes the pressure it looks like it creates. **You never need to +guess at publication time which registry a thing might one day belong to**, +because reachability never required moving it.* + +*Note for entry 24: **multiple registries is the concrete shape that makes search +necessary.** "Which registry do I even look in?" is exactly a question the gap +list cannot answer — the un-hold condition, arriving with a topology rather than +a count.* + +--- + +## 52. The affordance/staging split holds at bedrock, and here is the test for whether it is doing work + +**Decided** (2026-09-06, loom + claude-substrate — `discovered`, `cart: almanac`): +**yes.** bedrock owns affordances; implementations own stagings of them. + +> **The test: could two implementations differ in their *roles*, or only in their +> *beads*?** +> +> **Roles differ → the split is doing work.** *Only beads differ → you have a +> protocol with exactly one staging*, which is still legal and still uses the +> same terms. + +**Advances** having a way to tell when the indirection earns its keep, which the +first handoff named as the place it most suspected itself. + +**Because** loom asked whether the split still makes sense this high up, and it +does — **but not everywhere, and the difference is checkable rather than +felt.** + +**Where it does work:** *hand something over so others can depend on it*, once +the registry has actually implemented it. **A registry that mints identities and +a registry built on content-addressing have genuinely different roles** — one +casts *mints an identity*, the other casts *derives identity from content*, and +there is no bead that turns one into the other. **Two stagings, one affordance, +and a consumer picks.** + +**Where it does not:** `annotating` casts 6 roles of 6 and there is no second way +to do it. **The protocol is the staging.** *That is the commitment case from +entry 1, and it is fine — catalogue versus commitment is about whether +alternatives exist, not about what kind of thing something is.* + +*So bedrock will hold both: affordances with exactly one staging forever, and +affordances with several. **Nothing distinguishes them structurally and nothing +should** — the second becomes the first the moment somebody implements it +differently, and that is not a change of kind.* + +*On loom's Latin: **publish is to make public**, and it is worth noticing the +design agrees. **Publication is about crossing a boundary, and the boundary can +be redrawn without the thing moving** — which is the whole of entry 51 in one +word's etymology.* + +--- + +## 53. Space is cheap. Time is what costs. The identity machinery is deferred with a trigger. + +**Decided** (2026-09-06, loom + claude-substrate — `discovered`, `cart: almanac`): +**v1 serves the read scenarios with paths and prose.** Minting, receipts, +immutability, genesis and the ledger are **designed and deferred**, and the +trigger to build them is stated below. + +**Advances** not building the apparatus before anything needs it — which is the +sin of the tool loom is afraid of becoming. + +**Because** loom named three scenarios and **every one of them is a read**: + +- *What are my options for publishing to my local cluster?* +- *Does Hypertheory.API let another app list current students? If not, it is a + gap.* +- *What are all these `.loom` things — what is a cart? a venue?* + +**Running each against the design, none of them needs the identity machinery on +first use:** + +| | needs | does not need | +|---|---|---| +| cluster options | published affordances, stagings, who pins them | URNs, receipts, a ledger | +| does the API do X | the same, plus gaps | the same | +| what is a cart | **bedrock being a repository you can clone** | any of it | + +> **The write-side machinery is for time, not for space.** *Space is paths and +> prose. **Time is what costs**, and time has not started yet.* + +**We spent two days on minting, receipts, immutability, genesis, rehoming and +ledgers — and about ten minutes on what a person actually reads.** The +justification for all of it is real: **it is what makes the read side trustworthy +after things change.** *Nothing has changed yet.* + +**Today's mechanism for "did it change" is a git diff**, which is what +`must-revalidate` has meant all along and which cost loom two minutes the one +time it mattered. + +> **The trigger: when a consumer is burned by a change they did not see.** Not a +> date, not a count of repositories. **One person surprised once.** + +*What this demotes: entries 13, 31, 32, 33, 34, 40, 47 and 50 remain decided and +move behind that trigger. **They are not retracted** — a deferral with a stated +condition is what §8 asks for, and re-deriving them later is the cost entry 12 +says we keep paying.* + +--- + +## 54. Is this WSDL? Five ways it is not, and one way it becomes it + +**Finding** (2026-09-06, loom + claude-substrate — `cart: almanac`): loom asked +directly and the question deserves the direct version, since **the answer is +mostly no and the exception is the thing loom just noticed.** + +**What made WSDL bad**, so we are measuring against something rather than a +feeling: *it described shape rather than meaning; it was generated so nobody read +it; it produced codegen and therefore coupling; it grew to describe everything +and so said nothing; and it existed for machines, so humans stopped +understanding the system.* + +**Five ways this is not that:** + +- **It records meaning, not shape.** *"There is only a notion of a current + course; VMs are destroyed at the end of a session"* — **an interface document + carries none of those three facts**, and they are the ones a developer needs. +- **Nothing is generated from it.** No clients, no stubs, no coupling. **A pin is + a fact, not a binding.** +- **Only the delta from ambient knowledge.** WSDL described everything including + the obvious; **this refuses to document Kafka.** +- **It has a place for not knowing.** *Looked-at-and-empty*, gaps, uncast roles. + **An interface description has no way to say "we checked and there is nothing + there."** +- **Being wrong out loud is the mechanism.** WSDL was authoritative by + construction; **here a wrong entry that provokes a correction is a success.** + +**The one way it becomes WSDL, and it is the live risk:** + +> **If the write-side apparatus arrives before anyone reads anything.** + +*Machinery built ahead of use is exactly how a description format becomes +ceremony — and the correction is entry 53, which loom prompted by noticing the +imbalance before it had a name.* + +*Second-order risk, worth naming once: **receipts are generated lock files**, and +lock files are the closest thing here to WSDL's shape. **They stay safe only while +nothing generates code from them.** If anyone ever builds a client from a +receipt, this became the thing we were avoiding.* + +--- + +## 55. A staging lives in the repository that implements it, not in a registry + +**Correction** (2026-09-06, claude-substrate — `cart: almanac`): entry 43 said +*"each registry owns its staging"* and entry 52 asked whether *"two +implementations"* differ in their roles. **Both readings put implementations +inside registries, and nothing does.** + +> **A repository implements. A staging lives with the implementation. A registry +> serves published descriptions of it, and differences from the shared thing are +> recorded in a venue file.** + +**Nothing in the design was wrong; the phrasing was**, and it produced exactly +the confusion it should have. *Entry 52's test is unchanged and still holds — it +is a test about two **repositories**, not two registries.* + +--- + +## 56. An affordance is a description; a staging is a table; a bead is a document + +**Decided** (2026-09-06, loom + claude-substrate — `discovered`, `cart: almanac`): +a staging is **one markdown file containing a table** — roles as rows, beads as +cells. Not a directory of role files. + +``` +affordances//affordance.md description, and links to its stagings +affordances//.md a table +beads/.md a document +``` + +**Advances** the thing being stored in the shape it already has, and the read +scenarios from entry 53 being served by a file somebody can actually read. + +**Because** loom asked whether an affordance could be *a named markdown file, a +description, and a table of roles and beads* — and **a table is what a staging +already is.** *A list of roles that must be cast.* **We had been storing a table +as a directory:** thirteen files for the cart affordance, where two and a +handful of beads say the same thing. + +### It repairs the bootstrap's worst finding rather than routing around it + +That finding was **role and bead are indistinguishable — every role file is a +name followed by a nominalisation of its own bead.** *They were indistinguishable +because they were the same file.* + +> **A role is a label. A bead is a document. The table is the staging.** + +**A row has nowhere to hide prose**, so the prose goes where it belongs, and the +distinction becomes structural instead of aspirational. + +### Three things improved that were not aimed at + +**Uncast roles are visible at a glance.** *Eight cast, four yours* is a column you +scan. **The measurement separating a protocol from a specification stopped being a +count and became a look.** + +**The comparison view falls out.** Two stagings are two tables with the same row +labels — **which is exactly the tailnet-versus-public table in TAILSCALE.md**, the +thing identified on day one as the most valuable content in that document. + +**The receipt is the same document with a different column.** Entry 34 called a +receipt *the staging with every role carried by an identity* — **so publishing +renders a column and restructures nothing.** *Names now, identities later, one +table throughout — which also means the deferral in entry 53 costs no rework.* + +### And it dissolves the bootstrap's third finding + +`affordances//staging/` **could hold exactly one staging** and could not +express a catalogue. **A file per staging can**, and it is also required by entry +13: *a staging is addressed and `410`s, so it must be its own thing.* + +*Cross-affordance dependencies become a link in a cell — cart's* **holds the +durable record** *points at the event-log affordance from inside the table, which +is more legible than the prose note it replaces.* + +--- + +## 57. A consumer's file is the complement of the staging, not a copy of it + +**Decided** (2026-09-06, loom + claude-substrate — `discovered`, `cart: almanac`): +a consuming repository holds **a table containing only the rows the publisher +left uncast.** Nothing the publisher cast appears in it. + +``` +venues////.md +``` + +**Advances** the completeness of an adoption becoming checkable, which is a +freshness case we had already found and had no mechanism for. + +**Because** loom asked whether a consumer's *cast with* column would just be the +bead URNs — **and running it through our own rules gives something better than a +copy.** *Say it once, where it is owned:* the eight cast roles belong to bedrock, +so reproducing them here is duplication. *Only the delta from ambient knowledge:* +the delta **is** the four casts. **Entry 30 already said this and this is its +shape.** + +> **Their table has holes. Ours fills them. Put them side by side and you have +> the whole thing, with nothing said twice.** + +### And that makes an open freshness case mechanical + +We recorded that **a new *yours to cast* role appearing upstream leaves an +adoption incomplete, and neither `200` nor `410` says so** — because completeness +is not identity. + +**With two tables it is a set difference.** *A role marked yours-to-cast upstream +with no row here is an incomplete adoption*, and it is one comparison rather than +a reading. + +*Role labels are the join key, which is entry 3's carve-out working across a +publisher/consumer boundary rather than within a catalogue: **the publisher +imposes the vocabulary, so the labels collide for free.** A retyped label shows up +as **one unmatched row on each side**, which is a visible signature and needs no +new machinery.* + +### What actually fills the column + +**Not URNs, mostly.** A consumer's casts are *its own* beads — a presence, a path, +a branch — **and most of them are never published**, because nobody else depends +on them. + +> **The column holds a link to a bead. Whether that bead has a URN depends on +> whether it was published, and a consumer's casts usually are not.** + +*Which is `affordances/` versus `published/` showing up on the consumer side, and +it is the asymmetry that makes bedrock bedrock: **its beads exist to be depended +on; a consumer's exist to be used.*** + +*Small finding: **you adopt a staging, not an affordance** — a specific way of +doing the thing, not the thing. So the consumer's file is named for the staging, +which is also why the path can stop at the repository and does not need the +affordance slug.* + +--- + +## 58. A derived rendering is a third category, and provenance is not the field we removed + +**Decided** (2026-09-06, loom + claude-substrate — `discovered`, `cart: almanac`): +a consuming repository may hold **a readable rendering of an adopted staging — +theirs and ours together — which is derived, carries what it was derived from, +and is never authored.** + +**Advances** entry 53's third scenario — *what is a cart?* — being answerable +**with no network**, which the complement table alone does not do. + +**Because** loom asked for the affordance to be readable as a markdown page, the +way a README projects `published/`. **The complement table is correct and it is +not legible**: four rows of our casts tell a stranger nothing about what a cart +*is*. + +**And a derived rendering is not the duplication we forbade.** *Say it once, +where it is owned* prohibits **two authored copies that can drift silently.** A +rendering has one author — the derivation — and **says what it came from**, so +drift is detectable rather than silent. + +> **Copy, reference, rendering. The first is forbidden, the second needs the +> network, and the third is what makes an offline reader possible.** + +*And provenance is not `checked:` returning. **`checked:` recorded when we last +verified something we could ask about; provenance records what a derived artifact +was derived from.** The first is a note about diligence and became pointless when +asking got cheap. **The second is required by any derived artifact** — you cannot +tell a rendering is behind without knowing its source.* + +--- + +## 59. Uncast roles borrow the annotating vocabulary, but must not borrow its file + +**Decided** (2026-09-06, loom + claude-substrate — `discovered`, `cart: almanac`): +**an uncast role may be a prompt** — `Select:` with castable options, `Question:`, +`Affirm:` — and a consumer's answer may be `Skipped.` **The answers live in the +consumer's own file, never in a notes file.** + +**Advances** a distinction the complement table could not make, using a +vocabulary both parties already read. + +**Because** loom noticed the fit and it is close: **`cart.md`'s "Roles you must +cast" is already prompt-shaped**, and this repository answered it with `Select:` +and `Question:` headings weeks ago. + +**What the vocabulary buys, and it is a real gap being closed:** + +> **`Skipped.` — considered and passed — is not the same fact as an uncast role, +> and the complement table could not tell them apart.** + +*Entry 22 gave four meanings for a role with no bead on the publisher's side. +**This is the fifth, and it is the consumer's**: we looked at what you left for +us, and chose not to fill it. **An unfilled role and a declined one must not read +alike**, and until now they did.* + +**And `Select:` lets a publisher offer castings** — *"where does a round live? +`.loom/cart/`, elsewhere under `.loom/`, outside it entirely"* — **with +`annotating`'s rule attached: never enumerate an escape hatch.** *An option on +the list can be chosen and you are done; an answer that is not on the list has to +be written, so it arrives with a reason.* + +### What must not be borrowed, and it is the half that would break + +**Not the notes file.** *Casting by writing `cart.notes.md` beside an adopted +staging recreates the deadlock entry 30 documented:* **notes freeze their source, +the response is to rewrite the source, and the source is not ours to rewrite.** + +**We are living inside that failure right now** — `cart.md` and `annotating.md` +have been frozen since 2026-08-21 for exactly this reason. + +> **Take the prompts and the answer conventions. Leave the file.** *The +> consumer's casts belong in a file the consumer owns.* + +--- + +## 60. A projection erases the machinery, and the diff of two projections is the change report + +**Decided** (2026-09-06, loom + claude-substrate — `discovered`, `cart: almanac`): +a projection of a staging is **prose with no vocabulary in it.** A heading, the +description, then a section per role carrying the body of its bead. **No links, +no tables, no mention of beads, roles, stagings or affordances.** + +**And it must be pure: the same source renders the same bytes**, so that +re-fetching and diffing is the whole change-detection mechanism. + +**Advances** the read scenarios costing what they are worth, instead of costing a +vocabulary lesson first. + +**Because** I built the wrong thing and loom corrected it: my rendering had a +table, links and a *whose* column — **the ontology leaking into the output.** +loom's point is exact: ***in 99% of the places I'd use cart, I don't want to waste +context on "WTF are beads? Stagings?"*** + +> **The ontology is an authoring tool, not a reading format.** *It is how the +> document is assembled. It should not survive assembly.* + +**And purity is a real constraint, which I had already broken.** My first +rendering carried *"derived … @ 2026-09-06"* — **a timestamp, so every re-render +diffs even when nothing changed.** *Provenance must be content-derived: a source +hash is pure, a date is not.* + +### The consequence, which is larger than the formatting + +**Re-GET and diff is the change report.** + +*You do not need identity, `410`s or a ledger to know that something changed — +you need them to know **what kind** of change it was.* **For most uses, "the text +changed, and here is which paragraph" is the whole answer**, and it costs a `GET` +and a `diff`. + +**That strengthens entry 53's deferral considerably.** The machinery was already +behind a trigger; **this shows the read side does not merely survive without it, +it has a good answer without it.** + +### And several projections come off one source + +**Prose** — *what is this and how do I use it.* **Comparison** — *this staging +against that one*, which is the table shape. **Precedent** — *who else casts +this.* **Each drops what the others need**, which is entry 5's *a projection is +audience-shaped* arriving with three concrete audiences instead of one abstract +claim. + +--- + +## 61. The round trip: `cart.md` was always this projection, written by hand + +**Finding** (2026-09-06, claude-substrate — `cart: almanac`): the prose +projection of the cart staging **reconstructs something very close to `cart.md` +itself** — the agreement's own sections map almost one-to-one onto the roles. + +**Which makes the round trip a test we can actually run:** *decompose a document +into roles and beads, project it back, and compare.* **Where they differ is what +the decomposition lost**, and that is a better check on the ontology than any +argument about it. + +**What it lost, and it is one section:** `cart.md` ends with **"What we know is +unsettled"** — *whether size is the only conversion trigger; whether specimen and +polad stay two roles.* + +> **A published thing's own open questions have no home.** + +**Not a role** — nothing casts them. **Not a gap** — nothing is needed from +anyone. **Not a decline** — nothing was refused. **Not a write-ahead entry** — +that is the author's private workspace, and these were published deliberately, to +be argued with. + +*This is the fourth or fifth time this cart has found a place for not-knowing and +been right to. **This one is the publisher's own uncertainty, published on +purpose**, and I do not have a home for it. Recorded rather than solved.* + +--- + +## 62. Casting by annotation, and the freeze is the feature + +**Decided** (2026-09-06, loom + claude-substrate — `discovered`, `cart: almanac`): +**a consumer may cast a staging's open roles by annotating the projection.** The +resulting freeze is **deliberate**, and an upstream change arrives as **a polad**. + +**Advances** a fetch that would overwrite becoming a decision somebody makes, +rather than a file that changes underneath them. + +**Because** loom proposed it and **it dissolves the objection I raised one round +earlier.** I said notes would freeze a source we cannot rewrite — *and that was +true of `cart.md`, which was adopted by copy from somebody else.* **The +projection is ours**: derived into our own tree, and regenerable. **The deadlock +does not reach it.** + +### The freeze is the point, not the cost + +**Once notes exist the projection is fixed**, so **a re-fetch cannot silently +overwrite the text the casts are anchored to.** + +> **Without the freeze, `fetch` clobbers. With it, `fetch` has to become a +> decision.** + +*That is entry 11's failure — a divergence nobody announces is indistinguishable +from a plan that was followed — and the freeze is what makes this one announce +itself.* + +**And the new projection is a polad by definition rather than by analogy:** *a +candidate artifact, shaped exactly as it would be if kept.* **Two exits.** +Applying means dissolving — carry the casts into the record, delete the notes, +re-render, and the projection is released. + +### Where the casts live, which is the part that had to be got right + +**A projection is derived from *their staging* plus *our casts*, and is pure +given both.** So the casts **cannot live only in the notes**: the notes die at +dissolve, and purity breaks if a projection carries authored content nothing else +holds. + +> **Annotations are how a cast is made. The complement table is where it is +> kept.** + +*Which is the two-stage shape this project keeps arriving at without designing +it: **cart → event log**, **write-ahead log → event log**, **cart gaps → durable +gaps**, and now **notes → complement table.** **Four instances.** At this point it +is a property of the thing rather than a coincidence.* + +*Proportionality, stated so this does not become ceremony: **available, not +mandatory.** If you know your casts, write the table and be done. **Annotate when +there is something to work out** — which is the same test the cart itself uses.* + +--- + +## 63. Content negotiation is required, not convenient — and the ETag finishes the freshness story + +**Decided** (2026-09-06, loom + claude-substrate — `discovered`, `cart: almanac`): +**one address per staging, several representations.** `Accept` selects between +the browsing surface and the prose projection. **The server returns an `ETag`, +and `If-None-Match` is the freshness check.** + +**Advances** the read side's change detection being entirely stock HTTP, which is +what entry 53's deferral was betting on. + +**Because** loom asked whether a projection could come back from the same place — +**and it must, rather than merely may.** + +> **Two URLs would be two identities for one staging.** *And then which one +> `410`s?* **Identity is the thing this design has been most careful about, and +> separate addresses for representations would spend it for nothing.** + +*It is also `410`/`303` again: lean on what the web already does, because an +agent knows `Accept` without being taught — entry 9, at zero bytes.* + +### The three status codes cover the whole read-side freshness story + +| | meaning | cost | +|---|---|---| +| **`304`** | nothing changed | **no body** | +| **`200`** | changed — here it is, diff it | one fetch | +| **`410`** | replaced — go to the affordance | one fetch, then a `303` | + +**No custom machinery anywhere in that table.** *loom's "basically a hash of what +is at the server" is an `ETag`, and every HTTP client already implements the rest +of it.* + +**Provenance carries the source `ETag`**, so *is my projection behind?* is a +**`HEAD` with `If-None-Match`** — no body transferred, which is about as cheap as +freshness gets. + +### The catch, which would otherwise be found painfully + +**A projection is derived from *their staging* plus *our casts*. The server can +only hash its half.** + +> **The server's `ETag` answers *has upstream changed*. It cannot answer *is my +> rendered file current*, because it has never seen our casts.** + +**Two hashes, two questions:** the source `ETag` in the provenance comment, and — +if we want it — a local hash over *source `ETag` + our casts*. *Conflating them +would report a projection as current after we had edited our own half.* + +*And `ETag`s are per-representation: the browsing surface and the prose +projection have different bytes and therefore different tags. **`Vary: Accept`**, +or a cache serves one where the other was asked for.* + +--- + +## 64. Confidence in the representation, stated as a split rather than a number + +**Finding** (2026-09-06, claude-substrate — `cart: almanac`): loom asked how +confident I am that an affordance can be represented meaningfully. **The honest +answer has three parts and only one of them is high.** + +**High — agreements and protocols.** *Three done on real content: `cart`, +`annotating`, `record a decision`.* **None had to be forced**, the table shape +improved legibility rather than costing it, and **entry 61's round trip +reconstructs something close to the source document** — which is a real check on +faithfulness rather than an argument about it. + +**Untested — code and running services.** *Not one of the three is code, a +cluster, or an API.* **The Hypertheory.API case has never been attempted**, and it +is the one loom actually needs. + +**Known chafe — the gotchas.** *`ingressClassName` is not optional; CNPG cron has +six fields.* **Three times now these have had nowhere to live** — not roles, not +beads, not stagings — and the best home found is a note on a role, which works +and still feels wrong. + +### And the caveat that matters more than the count + +**The three successes are correlated.** *Same author, same kind of artifact, and +all three were already well-organised documents with numbered sections that map +onto roles.* + +> **We may have demonstrated that a well-structured document decomposes, rather +> than that the ontology is right.** *And everything real is badly structured.* + +**The test that would move this:** *project something nobody wrote with this in +mind.* **The cluster was half of that** — projected from documents I had read but +did not write — **and it produced the gotchas problem immediately**, which is +exactly the signal to expect. + +*So: high confidence for the case we have done repeatedly, and **the number I +would not give is one for the case loom most needs.*** + +--- + +## 65. A new or updated external starts life in the cart as a polad + +**Decided** (2026-09-06, loom + claude-substrate — `discovered`, `cart: almanac`): +**nothing is written directly into `venues/`.** A candidate external — new or +updated — **lands in the cart as a polad**, and *applying* is the move out. + +**Advances** taking on a dependency being an act somebody performs rather than a +file that appears. + +**Because** loom proposed it, and **the strongest reason is one I would not have +given: the cart has two parties.** + +> **An external is a claim about somebody else's thing, and the other presence in +> the round may know it is wrong.** + +*loom's own example from days ago: **"ohhh — that's a leftover from an old way of +thinking."*** **A polad gives them the chance to say it.** *Written straight into +`venues/`, an external is a claim nobody reviewed — and entry 8 says being wrong +out loud is the mechanism, which requires somebody to be there when you are +wrong.* + +**And it makes one path where there were two.** Entry 62 already routed *updates* +through a polad. **A new adoption bypassing that would be the same destination +with no review step**, which is how the reviewed path becomes the optional one. + +**It is also a polad by definition rather than analogy** — *a candidate artifact, +shaped exactly as it would be if kept* — **and applying is a `mv`.** + +### Two cases, one path + +**First adoption:** everything starts in the polad, is cast there, and applies +out. + +**Update:** the candidate sits in the polad **while the current one stays live**. +*You do not take the live projection down in order to look at its replacement*, +and applying replaces it. + +*Which makes `polad/venues/` an inbox: **everything in it is something we are +about to depend on and have not yet committed to.** That is a useful listing even +working alone.* + +### And the case where there is no open round needs no new rule + +**No `current/` means no cart.** So an external arriving outside a round has +nowhere to land — *which reads like friction and is the existing test in +disguise.* + +> **If you know, write it. If you are unsure, that is a WTF, and the correct +> answer to any WTF is create a cart.** + +*Adopting an external you are confident about is not a conversation. Adopting one +you are not **is**, and the rule that already covers it is the one loom wrote.* + +--- + +## 66. The dyad assumption is inherited, not introduced — and here is what degrades + +**Finding** (2026-09-06, claude-substrate — `cart: almanac`): loom noted that +entry 65 assumes two parties working together, and that this will not always +hold. **True, and worth separating from where it came from.** + +**The assumption is `cart.md`'s, not entry 65's.** *One daily per presence; there +is never a third; when one party cannot feel a cost the decision belongs to the +party who can.* **And `cart.md` already names the hole it leaves:** *"if neither +presence can feel it — two agents with no human — size is not an available +trigger, and we do not have a good answer for that case."* + +**So entry 65 inherits a known limitation rather than adding one.** *Which is the +useful thing to know: fixing it is upstream work on the agreement, not a patch +here.* + +### What survives a single party, and what does not + +**Lost: the review.** *An external written straight into `venues/` is a claim +nobody checked* — and entry 8's *being wrong out loud* needs somebody there when +you are wrong. **Alone, that reason evaporates.** + +**Kept, and these are not small:** + +- **The inbox.** `polad/venues/` lists everything you are about to depend on and + have not committed to. *Useful with nobody else in the room.* +- **The act.** Applying is a `mv`, so taking on a dependency stays something + performed rather than a file that appeared. +- **Continuity across sessions**, which is the one I would not have listed and is + probably the strongest here. **The agent in this dyad is discontinuous** — a + fresh instance has none of the conversation. *A polad is a surface a later + instance can read: **here is what was about to be committed to, and why it had + not been.** The conversation does not survive the round; the polad does.* + +> **So the rule is worth keeping solo, for weaker and different reasons — and +> those reasons should be written down, or somebody alone will reasonably +> conclude the ceremony is for nothing.** + +*And on the specific case loom raised: **the current arrangement is not solo.** +loom is a person and an agent; claude-substrate is another. The genuinely +uncovered case is a human with no agent, or agents with no human — **and the +second is the one `cart.md` says it cannot handle.** + +--- + +## 67. `event-log.md` moves to the root of `.loom/`, and the legacy path is not inherited + +**Decided** (2026-09-06, loom + claude-substrate — `discovered`, `cart: almanac`): +**there is no `internal/` directory.** The event log sits at `.loom/event-log.md`. + +**Advances** a word not meaning two things in one document, and a known-bad path +not being carried into repositories that have no reason to have it. + +**Because** loom caught a collision I had written myself: **the structure +document said *"there is no `internal/` beside `published/`"* in one paragraph and +described `internal/` two paragraphs later.** *Same word, two meanings — one a +refused category, the other an actual directory — which is exactly how a word +becomes shadow policy.* + +**And the directory was wrong on its own merits, separately from the collision:** + +- **It held one file.** *A directory holding one file is usually a directory that + should not exist.* +- **It named an audience distinction that does not apply.** *Nothing here is + hidden from anybody; `internal` describes a disposition, and a false one.* +- **The original handoff already said so:** *"that path is not where we would have + put it. A tool has it hardcoded, from before we understood §4… Consider it a + live demonstration — the price of an emitted path is real, nameable, and gets + paid by someone who wasn't there."* + +> **The point of that note was to stop the cost being paid forward. Copying the +> path into every new repository would have been paying it forward.** + +*So: **this repository keeps `.loom/internal/event-log.md`**, because a tool reads +it and breaking that today buys nothing. **`bedrock`, `registry` and everything +after use `.loom/event-log.md`.** The mistake stops here rather than +propagating.* + +### And a rule fell out that was worth more than the fix + +> **A stream is a file. A collection of things with identity is a directory.** + +*The event log is appended to and no entry is ever addressed; a bead is one of +many and each is referred to. **`gaps/` stays a directory** because a gap is +reported, homed or withdrawn individually — it has identity, where a log entry +has only a position.* + +--- + +## 68. `external` and `venues` have different units, which is the weak spot — one file per thing + +**Decided** (2026-09-06, loom + claude-substrate — `discovered`, `cart: almanac`): +**the unit is the thing.** `external/` and `venues/` merge into **one directory, +one file per thing we do not own**, with what-we-understand and what-we-decided +as sections rather than as separate trees. + +**Advances** a reader not having to learn a distinction before they can find +anything. + +**Because** loom asked what the unit of an external is, and **tracing it exposes +that the two directories never had the same one:** + +| | unit | example | +|---|---|---| +| `external/` | **the thing** | *"there is only a notion of a current course; VMs are destroyed at the end of a session"* | +| `venues/` | **the staging** (entry 57) | *the four roles we cast on `cart`* | + +**The content settles it.** *VMs are destroyed at the end of a session* is not a +fact about one affordance — **it is a fact about the whole thing**, and it is +exactly the sort of sentence that makes an external worth having. **So the unit +is the thing, and `venues/` having a different one is the inconsistency loom +could feel and I had not named.** + +### The deeper problem, which is worse than the mismatch + +**One fact splits across two files.** + +- *"`traefik` is the default IngressClass, so omitting it publishes publicly"* — + **understanding.** +- *"we always set `ingressClassName` explicitly"* — **a decision.** + +**Two sentences about one thing, in two trees, and a reader needs both.** *The +existence conditions from entry 6 are genuinely different — a file exists when +the reader cannot be assumed to know the thing, or when we decided something — +but **different existence conditions do not require different directories.** A +file may exist for either reason and carry both.* + +### What the split was protecting, and how it survives a merge + +**The two go stale from different directions**, which is the one real thing the +separation encoded: + +> **Understanding goes stale when *they* change. A decision goes stale when *we* +> change our minds.** + +**A heading carries that.** *It does not need a tree.* + +### The shape + +``` +external/ +├── kafka.md no address: what we decided about it +└── /// + ├── .md what we understand about the thing + ├── .md what we cast, per adopted staging + └── .projection.md derived +``` + +**Things with an address are filed under it; things without one sit at the top by +name.** *Which also stops `venues/` carrying two shapes for two kinds of thing — +it was doing that because the things differ, not because we do.* + +*Confidence, stated because loom named this as the weak spot and I do not want to +sound more certain than I am: **the diagnosis I believe** — different units, one +fact split in two. **The merge is the fix I would choose and it is a layout loom +has to live in**, so it is theirs to reject. *What would change my mind: a real +case where somebody wants to read every decision without reading any +understanding.* I could not construct one.* + +--- + +## 69. Three problems, and the ontology is the third — bracket it + +**Decided** (2026-09-07, loom + claude-substrate — `discovered`, `cart: almanac`): +what is being built is **a documentation service whose goal is agent +orientation.** Affordances, stagings, roles and beads are **a later layer and are +bracketed.** + +| | | +|---|---| +| **1** | pull descriptions of infrastructure and practice into a repository; keep them fresh; declare gaps against them | +| **2** | name a thing and get an overview, its role, and a link to its repository | +| **3** | affordances, stagings, roles, beads — **bracketed** | + +**Advances** the two problems loom actually has being solvable now, instead of +waiting on a model built for a third. + +**Because** loom's diagnosis is right and it is about mixing altitudes: **we were +fitting high-level infrastructure and practice description into a model built for +spans of source.** *A square peg into a bead-shaped hole.* + +**And this is not a reversal.** *Entry 53 already deferred the write-side +machinery behind a trigger, for the same reason — the read scenarios did not need +it.* **This goes one step further and defers the model as well.** Same direction, +one more step. + +### What relaxes, and each of these was a constraint of the third problem + +**`external/` may hold a copy.** *Not a violation of refer-never-contain, because +that rule forbids **a copy that reads as authoritative and can drift with nothing +to notice.*** **A copy pinned to the publisher's `ETag`, with a conditional `GET` +to check it, cannot drift silently.** *The rule was never "do not cache."* + +**No decomposition, so no projection.** *Entry 60's prose rendering existed to +reassemble readable text from decomposed parts.* **If you never decompose, you +already have their document.** *Entry 60 is not wrong; it is unneeded until +problem 3.* + +**The mutation API may be ordinary resource-oriented REST.** *The prohibition +existed because a submitted document had to declare **replace this staging** +versus **edit this staging**, which produce different outcomes.* **With no +stagings there is nothing to distinguish**, and a description document is a +document. *It comes back when problem 3 does.* + +**The delta rule moves upstream rather than weakening.** *Publishers still write +only what a reader cannot be assumed to know; **consumers cache what they wrote, +whole.*** + +### Three things I would check before building on it + +**The lock must be the publisher's `ETag`, verbatim — never a hash we compute.** +*A fetch that normalises whitespace, or a proxy that re-encodes, breaks a locally +computed digest and reports a change that did not happen.* + +**Unfreezing is by replacement, not by rewriting**, and this is the third time it +has come up. *The annotating protocol says dissolve by rewriting the source, and +the source is theirs.* **loom's answer is right: the new copy arrives as a polad, +the annotations are reconciled against it, and the pair is replaced wholesale.** +*Worth writing into the agreement, since it is the move `annotating` does not +have.* + +**Problem 2 is a name lookup, not the search entry 24 holds.** *That hold was +about finding an affordance by what it does, across everything.* **Looking up +`hall-monitor` by name, among things you own, is a directory** — cheap, and it +needs none of the machinery the hold was protecting us from. + +--- + +## 70. What bracketing problem 3 defers — recorded as a decline, not forgotten + +**Decided** (2026-09-07, claude-substrate — `discovered`, `cart: almanac`): +the payoffs that require decomposition are **deferred with the model**, and named +here so that later they are recognised as deferred rather than rediscovered as +missing. + +**Advances** the difference between *we chose not to* and *we never thought of +it*, which is the whole reason declines exist. + +**What goes with problem 3:** + +- ***How many ways do we verify credit cards?*** — needs stagings to compare, and + entry 3 already said comparison is authored at the affordance. +- ***If I change this, what breaks?*** — needs pins onto beads. +- ***Precedent as the unit of teaching*** — entry 20's *three applications already + do this*, which needs something to pin to. +- **Alternative stagings of one affordance**, and the catalogue/commitment split + that entry 1 built. + +> **All three payoffs need a scale we do not have.** *One person, two clusters, +> five applications. **The credit-card question is an enterprise question and we +> are not an enterprise yet.*** + +*What survives into problems 1 and 2 unchanged, because it was never about the +model: **gaps**, **gap-first ordering**, **gaps kept in the reporting repository**, +**being wrong out loud**, **the asked-not-enforcing posture**, **a place for not +knowing**, **the cart and annotating agreements**, and **`ETag`-based freshness.*** + +*And loom's own last line is the test to keep: **a bead should refer, not +contain.** It is still true where beads are — **and where there are no beads, it +was never the question.*** + +--- + +## 71. The dissolve is inverted, and an annotation is an index from their text to our artifacts + +**Decided** (2026-09-07, loom + claude-substrate — `discovered`, `cart: almanac`): +when an adopted description changes, **the annotations mostly do not.** The +question is not *what do we rewrite here*, it is: + +> **Given what changed in their document, what do we change in our code?** + +**An annotation should therefore name the artifacts it justifies.** + +**Advances** reconciliation having a direction, instead of being a rewrite that +cannot happen because the document is not ours. + +**Because** loom put it exactly: *"we can't dissolve our annotations into their +document — so it is the same thing, sort of on its head."* **In the normal +protocol the notes are about a source and the response modifies that source. +Here the response modifies something else entirely** — the manifests, the config, +the code we built from it. *The annotations may survive unchanged apart from line +numbers.* + +### Which makes an annotation more than commentary + +> ***"We are using the tailscale local cluster approach here, and the manifests +> in our repo are based on that."*** + +**That is anchored to a span of their document and names something of ours. So it +answers, in the only direction that matters: *if this span changed, what of ours +should be looked at?*** + +**An annotation is an index from their text to our artifacts**, and that is what +makes reconciliation tractable rather than a re-read of everything. + +**Authoring rule that falls out:** *"we use the tailnet approach"* is weak. +***"we use the tailnet approach — see `manifests/ingress.yaml`"*** is the whole +value. **Name what it justifies.** *Referring to our own paths from our own files +publishes nothing.* + +### And it partially un-defers something entry 70 had just written off + +**Entry 70 deferred *if I change this, what breaks?* on the grounds that it needs +pins onto beads.** *That is the **provider's** question and it is still +deferred.* + +**The consumer's version survives the bracketing and costs nothing:** *their +document changed — which of our things did we say were based on it?* **The +annotation already answers it.** + +*Coarse rather than mechanical — somebody reads and judges — and that is the +right resolution for one person with two clusters.* + +--- + +## 72. The form is etcd-shaped: kinds and paths, not pages and links + +**Decided** (2026-09-07, loom + claude-substrate — `discovered`, `cart: almanac`): +**the structure is the schema.** A small fixed set of kinds, each a directory, +enumerable at every level, with nothing living outside them. **No watch.** + +**Advances** the thing having a form loom can name, rather than being defended as +*not a wiki* every time somebody asks. + +**Because** loom reached for *etcd as wrapped by the Kubernetes API, but +browsable*, and the analogy is load-bearing rather than decorative. **What that +API gives is a total addressing scheme with enumeration at every level** — kind, +then namespace, then name — so you find things **by knowing the shape** rather +than by following links from somewhere. + +> **A wiki's unit is a page and its structure is links. Here the unit is a kind +> and the structure is the path.** + +**Type-by-location already is that.** *The directory is the kind; the path is the +address; `ls` is `LIST`.* **We had the mechanism and not the name for what it was +buying.** + +### The kind-set, and why the bracketing is evidence for itself + +**Under problem 3 there were eight kinds.** Bracketing it leaves **five**: +`external/`, `venues/`, `gaps/`, `cart/`, `event-log.md`. + +> **A set small enough to hold in your head is what makes it a schema rather than +> a wiki.** *Halving it was the bracketing paying out, and I did not expect the +> payment to arrive as legibility.* + +### No watch, and the reason is one we already gave + +**Kubernetes `watch` is a client-initiated long poll** — pull-shaped, even though +it feels like push. **We still do not want it.** + +*It would be the first thing that makes the registry hold persistent knowledge of +its consumers, which is the line drawn at pins-are-not-subscriptions.* **And a +conditional `GET` is sufficient for documents that change monthly.** + +*Strictness is asymmetric and that is deliberate: **the write side owns what is +valid and accepts only known kinds; the read side shows what it does not +recognise rather than failing on it.** Files are edited outside the API +constantly, so the reader must be generous — but nothing arriving **through** the +API gets to invent a kind.* + +--- + +## 73. Siblings, not sections — because one of the files is not ours + +**Decided** (2026-09-07, loom + claude-substrate — `discovered`, `cart: almanac`): +the sibling-file pattern generalises beyond notes. + +``` +externals/homelab-cluster.md theirs — a pulled copy, frozen, replaced whole +externals/homelab-cluster.usages.md ours — what we use, and what of ours depends on it +externals/homelab-cluster.gaps.md ours — what we expected here and did not find +``` + +**Advances** everything about one thing sitting together in a listing, while the +thing we do not own stays a single replaceable file. + +**Because** loom has thought for a long time that the annotating pattern is +broader than the cart, and it is. **But the decisive reason is structural and it +arrived with yesterday's reframe:** + +> **`homelab-cluster.md` is *theirs*. Everything beside it is *ours*.** + +**Entry 68's merge is now impossible and I should say so plainly.** *I proposed +merging `external/` and `venues/` into one file with sections — and that was +written when an external was **our writing about their thing**.* **Entry 69 made +it a copy of their document**, and a merged file would put their content and ours +in one place, **which is the thing this entire round has been avoiding.** *The +reframe invalidated my fix a day after I wrote it.* + +### Two facets, not three, and the test is what reconciliation asks + +I nearly split deviations out as a third. **They answer the same question as +usages:** + +| facet | on a new version, ask | +|---|---| +| **usages** | *did their change affect what we do — and which of our artifacts?* | +| **gaps** | *did their change fill this?* | + +**A deviation is a usage that departs, and reconciliation treats them +identically.** *Facets are distinguished by the question reconciliation asks of +them, not by how they read.* + +### And this dissolves the weak spot mechanically + +loom named `external` versus `venues` as the weak spot and was right. **Under the +sibling pattern the line stops being conceptual and becomes checkable:** + +> **`externals/` is for things you can fetch and re-fetch. `venues/` is for +> things you cannot.** + +*Kafka, React and HTTP have no document to pull, no `ETag`, and nothing to +reconcile against — **so they have no stem to be a sibling of.*** **The +distinction is now "is there an upstream?" rather than "understanding or +decision", and nobody has to adjudicate a sentence.** + +--- + +## 74. Gaps need reconciling too, and moving one *is* homing it + +**Decided** (2026-09-07, loom + claude-substrate — `discovered`, `cart: almanac`): +**a gap is reconciled against a new version like everything else**, and a gap +beside an external is a **homed** gap. + +**Advances** the good half of reconciliation — *a thing you needed now exists* — +being noticed rather than waited on. + +**Because** loom asked whether gaps also need reconciling. **Yes, and it is the +half nobody builds for:** *the cluster publishes a new version that adds +publishing a container image, and our workaround has been unnecessary since.* +**Entry 24 said that when a gap is homed every workaround that should be retired +is findable — but assumed somebody tells us.** *Reconciling on fetch is how we +find out without being told.* + +### The naming collision, and why it turns out not to be one + +**`externals/.gaps.md` and `gaps/` are the same word in one tree**, which +is the `internal/` mistake loom caught yesterday — *except here the two meanings +are two positions of one thing.* + +- **`gaps/`** — *unhomed.* **Addressed to nobody**, because you could not say + whose job it is. +- **`externals/.gaps.md`** — *homed.* **You can name whose job it is**, so + it is a claim against them and belongs beside them. + +> **Moving a file from `gaps/` to `externals/.gaps.md` is the homing +> operation.** *Entry 40 said a gap stays unhomed until somebody claims it, at +> which point it resolves or moves into that external's file. **That `mv` is the +> claim.*** + +*Type-by-location carrying a state transition rather than a type — and it is the +one case where that is right, because **the position is the claim**, not a label +on it.* + +--- + +## 75. An external is always somebody's document, never the technology + +**Decided** (2026-09-07, loom + claude-substrate — `discovered`, `cart: almanac`): +**Kafka can be both**, and the apparent conflict comes from a slip in what the +external names. + +> **You never fetch Kafka. You fetch your platform team's document about Kafka.** + +`externals/acme.com/platform/kafka.md` is **a document with an owner and an +`ETag`.** *Apache publishes nothing into your registry; your platform team does, +and at a company that document is where the choices already made for you live — +auth, topic policy, what you may not turn on.* + +**And `venues/kafka.md` still exists** for choices with **no upstream document to +hang them on.** *Both can be true of one technology, because they are two +different things: theirs and ours.* + +**Advances** entry 73's line surviving the case that looked like a +counter-example. + +**Because** loom is right that at a company much is decided for you, and **that +decision lives in a document somebody owns.** *It was never the technology that +was fetchable.* + +*Operational tie-break, since the boundary is otherwise a judgement per sentence +and those decay: **put a choice where reconciliation will look for it.** If there +is a document it is about, it goes beside that document. **When in doubt, the +sibling** — a choice filed in `venues/` is a choice nobody will re-examine when +the upstream moves.* + +--- + +## 76. The annotating "abstract class" is not one — it is roles nobody cast, and that is the deadlock + +**Finding** (2026-09-07, loom + claude-substrate — `cart: almanac`): loom noticed +the annotating pattern appears in several forms and reached for *an abstract +class with subclasses.* **It is better than that, and it explains a bug we have +hit three times.** + +**The three appearances, with what differs:** + +| | source owned by | freezes when | dissolves by | +|---|---|---|---| +| **cart dailies** | us | notes exist | rewrite the source, delete notes | +| **an adopted agreement** | them | notes exist | ***nothing*** | +| **an external** | them | the `ETag` lock | replace whole, reconcile | + +**Same roles throughout. Different beads.** *Which by entry 52's own test means +this is **one protocol with roles left to the adopter**, not an affordance with +alternative stagings.* + +> **`annotating` publishes a staging in which *freezes the source* and +> *dissolves* are yours to cast — and it does not say so.** + +### And that is why `cart.md` has been frozen since 2026-08-21 + +**The adopted-agreement case never cast *dissolves*.** *Not because anyone chose +badly — because the agreement presents the role as already filled, so nobody +looked for a decision they had to make.* + +**The bedrock content records `annotating` as casting 6 of 6 roles, "a complete +protocol with no holes." That is wrong**, and it is wrong in the way that +matters: **an uncast role presented as cast is invisible**, which is the exact +failure entry 22's *yours to cast* state was added to prevent. *We built the +state and then mis-declared the first document we applied it to.* + +*Concrete and small: **when `annotating` is next published, those two roles are +yours to cast**, and the external casting — `ETag` lock, replace-and-reconcile — +is the one an adopted agreement should use. **Which unfreezes both agreements as +a side effect**, three weeks after entry 30 first said they were stuck.* + +### Recorded, not built on + +**This is the strongest evidence so far that the bracketed model earns its +keep** — *a real bug, hit three times, diagnosed by the ontology in one table.* + +**It changes nothing today.** loom said not to get lost in it and that is right: +**the finding is written down so it is waiting when problem 3 unbrackets**, and +**the fix it implies is a two-line change to one document, not a project.** + +--- + +## 77. "Roles you must cast" is already a prompt block — no ontology required + +**Decided** (2026-09-07, loom + claude-substrate — `discovered`, `cart: almanac`): +what loom described — **loom owns `annotating`; `cart` and `externals` each cast +the filename, the lock and the dissolution** — is expressible **with the +mechanism `annotating` already has**, and needs nothing from problem 3. + +**Advances** delivering, which is the thing at risk. + +**Because** loom said it was sneaking back in. **It is not, and the reason is +that we already built this before any of it.** `annotating` says a file may ask +for structured answers with `Question:`, `Select:` and `Affirm:` headings, and +that answers land in the notes file. + +> **"Roles you must cast" is a prompt block. "Yours to cast" is an unanswered +> prompt.** *`cart.md` has done this since before this round started, and the +> only thing missing is that `annotating` does not do it to itself.* + +**So the whole structure is:** + +```markdown +## Select: What is the sibling filename? +- `.notes.md` +- `.usages.md` and `.gaps.md` + +## Select: What fixes the source? +- the existence of the notes file +- a lock on the publisher's ETag + +## Question: How is it dissolved? +``` + +**`cart` answers one way. `externals` answers another.** *No beads, no stagings, +no roles — a document with questions, and adopters with answers.* + +*And it fixes entry 76's bug without touching the model: **the adopted-agreement +case failed because nobody was asked.** A prompt asks.* + +**Deliverable and small: `annotating/v1` gains three prompts; `cart` and +`externals` answer them.** *Two documents, no code, and both agreements unfreeze.* + +--- + +## 78. The first useful version needs no server: a git host already does conditional GET + +**Decided** (2026-09-07, loom + claude-substrate — `discovered`, `cart: almanac`): +**problems 1 and 2 ship against a git host and two conventions.** The registry as +a service is what comes after, when browsing and gap aggregation are worth +building. + +**Advances** something of value existing, which after three days of this is the +only remaining risk worth managing. + +**Because** everything problem 1 needs, gitea already serves: + +| need | already there | +|---|---| +| a document with an owner | a repository | +| an address | a raw URL | +| an `ETag` | conditional `GET` on raw files, or the commit sha | +| history | git | +| browsable rendering | gitea renders markdown | + +**And the conventions are two:** *`externals//.md` holds the pulled +copy; `.usages.md` and `.gaps.md` sit beside it.* + +**Problem 2 is one file in bedrock** — *a list of things and their repositories.* +**A name lookup is a grep.** + +> **What is genuinely not covered: aggregating gaps across repositories, and an +> entry point that explains itself.** *The first wants a service. The second is a +> README.* + +*So the honest scope of the server, stated now rather than discovered later: **it +buys aggregation and a self-describing surface, and it buys them after the +conventions have been used enough to know their shape.** Entry 12 said there is a +server and that is unchanged — **this says what it is for, and that it is not +first.*** diff --git a/.loom/published/loom-directory.md b/.loom/published/loom-directory.md new file mode 100644 index 0000000..ee0f49c --- /dev/null +++ b/.loom/published/loom-directory.md @@ -0,0 +1,22 @@ +# The `.loom` directory + +**A repository that uses loom keeps everything in `.loom/` at its root.** + +> **Type is determined by location.** *The directory a file is in says what it +> is.* **So frontmatter carries almost nothing, and often nothing at all.** + +**A stream is a file. A collection of things with identity is a directory.** *A +log is appended to and no entry is ever addressed; a thing that is referred to +individually gets its own file.* + +**Everything not recognised is shown rather than rejected.** *These files are +edited by hand and by agents, constantly, and that is supported. A reader that +fails on an unfamiliar directory is wrong.* + +## What is fixed + +**Only `.loom/` itself, and that a directory means a kind.** *Which kinds exist +is not fixed here — conventions built on this one declare their own, and a +repository has only the ones it uses.* + +**Nothing outside `.loom/` is loom's business.** diff --git a/.loom/published/publication.md b/.loom/published/publication.md new file mode 100644 index 0000000..eb0cb63 --- /dev/null +++ b/.loom/published/publication.md @@ -0,0 +1,31 @@ +# Publication + +**What a repository has handed over for others to depend on lives in +`.loom/published/`.** + +> **Publishing is a change of kind, not a change of status.** *An unpublished +> document is an implementation detail you may rename at will. **A published one +> is a contract.*** + +**So publication is an act somebody performs** — a file moves in — **and the move +is a diff that shows up in review.** *A flag in frontmatter is a line nobody +notices; this is the moment a thing becomes somebody else's problem, and it +should be unmissable.* + +**`ls .loom/published/` is *what have we committed to?*** — and it is the question +to ask before changing anything. + +## What is published is a document + +**Prose, in whatever form its readers need.** *If a repository organises its +thinking some other way internally, that is its own business:* **a consumer +cannot tell whether it did, and must not need to.** + +> **What is not exported is not hidden.** *The rest of the repository is there to +> read. It is simply not what you depend on.* + +## There is no directory for what is unpublished + +**Everything else is simply elsewhere in the repository.** *Naming the complement +invents a category — and a directory called `internal/` would look like a +security boundary and not be one.* diff --git a/.loom/published/recording-decisions.md b/.loom/published/recording-decisions.md new file mode 100644 index 0000000..970d4b4 --- /dev/null +++ b/.loom/published/recording-decisions.md @@ -0,0 +1,29 @@ +# Recording decisions + +**An entry says what was decided, what it is believed to advance, and the belief +that could turn out false.** + +> **Only a stated belief can later be shown wrong.** *"To satisfy §4" is a +> citation, not a reason — it points at a label.* **A constraint whose reason +> cannot be found gets re-litigated as a defect by whoever inherits it.** + +**Appended, newest last, never revised.** *A later entry supersedes an earlier +one by saying so.* **A revised record cannot show that anybody changed their +mind, which is most of what a reader wants from it.** + +**No status, no assignee, nothing to process.** *The moment an entry can be "not +yet applied", somebody must track which ones are — and the record becomes a work +tracker, which is how a thing that outlives its authors turns into a queue nobody +reads.* + +## Declines belong here too + +**What you considered and did not do, with the reason you did not.** + +*Almost nobody writes these, and three weeks later somebody proposes the thing +you already rejected — because the rejection lived somewhere that did not +survive.* + +## Where it lives + +`.loom/event-log.md`. **A stream, so a file.** diff --git a/.loom/published/sibling-facets.md b/.loom/published/sibling-facets.md new file mode 100644 index 0000000..a46446a --- /dev/null +++ b/.loom/published/sibling-facets.md @@ -0,0 +1,28 @@ +# Sibling facets + +**For any file `x.md`, other things said about it live beside it as +`x..md`.** + +> **A facet is owned by whoever wrote the facet, never by whoever wrote `x.md`.** + +**That is the whole of it.** *It exists so that two parties can both write about +one document without either of them reconciling two versions of it.* + +## What a convention using this must decide + +**Three things, and it must say all three or its adopters will not know they had +a choice.** + +- **What the facet is called.** *`notes`, `usages`, `gaps` — the name is the + contract; a reader recognises the pair on sight.* +- **What fixes `x.md` while the facet exists.** *Something must, or the facet + comes loose from what it is about.* +- **How the pair is released.** *There must be an answer. **A convention that + does not say leaves its adopters stuck**, and they will not notice, because a + question nobody asks looks like a question already answered.* + +## Why the facet is never owned by the source's author + +**Because the source may not be theirs to change.** *An adopted document belongs +to whoever published it; a facet beside it is the only place a reader has to put +what they think.* diff --git a/README.md b/README.md new file mode 100644 index 0000000..fd034dc --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +# bedrock + +**The primitives.** Four documents, in `.loom/published/`: + +- [The `.loom` directory](.loom/published/loom-directory.md) — everything lives + there, and a directory means a kind +- [Sibling facets](.loom/published/sibling-facets.md) — `x..md` beside + `x.md`, owned by whoever wrote the facet +- [Recording decisions](.loom/published/recording-decisions.md) — what an entry + says, and why it is never revised +- [Publication](.loom/published/publication.md) — what `.loom/published/` means + +**Nothing here says what to *do*.** *`loom/annotating`, `loom/cart` and +`loom/externals` are built on these and are where the practices live.* + +**Most repositories will never read this.** *Its audience is people building +conventions, not people using them — and it is a good sign that a primitive is +one nobody needs.* + +**Publishers are the exception**, because publication is defined here, and the +people whose choices land on somebody else should have read the primitives.