b3617ed195c14ef043e7fda1a54ed37f36002c81
34
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
b3617ed195 |
the lock records what the source could be read as, and check audits it
Access is verified once, at fetch, and the copy is durable — so whether an adoption is still legitimate rests on the relative visibility of two repositories, which somebody can change with a checkbox a year later without ever seeing the adoption. The lock gains an optional fourth field and check turns that from a silent permanent hazard into something that runs. It costs nothing at add time, because the anonymous request already happened to decide whether to warn and the answer was being thrown away, and one request per run at check time rather than one per document, because only our own visibility has to be current. The stored value decays in both directions, so a source recorded not-public is re-probed only when the alarm would fire, and a source that has since gone public updates the lock and says nothing. Fixes a bug found while testing the alarm rather than after shipping it. sourceVisibility returned public whenever no credential was configured, which is sound at add time — the fetch had just succeeded anonymously — and wrong in the audit, where it is a probe and not a fetch: it would have silently cleared real alarms. Probing is now its own function that always asks with no credential, because what matters is what a stranger can read and not what we can. The value is recorded as public or not-public and never private: an anonymous request tells those apart and nothing finer, so it cannot see two repositories private to different people, which is the case that genuinely widens access. Three-field locks still load, and unknown visibility round-trips as absent rather than as a value. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018UTxuSizozEA8yDitPuris |
||
|
|
da6e8b9b51 |
loomctl orient: one table of contents, restating no rule
Generates .loom/orientation.md — what this repository depends on, where each copy came from, and which facets sit beside it — for whoever arrives next, of any make. One file rather than two, decided by loom's own rule. Generate what varies, adopt what does not: the publishing half varies not at all and is already adopted, so publication.md appears in the index like any other adopted document, in exactly the repositories that adopted it. A second command would emit a file whose whole content is a pointer to a file already in the tree, and would revive a noun declined when published check went. Restates no rule. The three moves are phrased as operations — what to run, what to write, where — and every rule stays in the document that owns it. The .usages.md is pointed at rather than summarised, because what depends on a document is free prose and anything extracting a claim from prose eventually extracts it wrong. It says nothing recorded when a document has no usages file, which is a finding rather than an omission and is currently true of six of nine. Byte-deterministic, pinned by a test that inserts records out of order and generates three times. Written to .loom/orientation.md rather than inside .loom/externals/, where check would report it as an unlocked external in every repository using the feature. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018UTxuSizozEA8yDitPuris |
||
|
|
1cc528c8aa |
sorrel: reconcile bedrock 82 and 83, and answer both prompts
publication.md and starting.md both came back 200 and are applied. Step 4 — fetch it, from somewhere that is not your machine and not your account — is now the procedure our declined command was an argument for. Answers the Select for a fixed path but against .loom/externals/README.md, with a measured reason: check walks that directory for adopted documents, so a generated file there reports as unlocked in every repository using the feature, forever. The deeper reason is the invariant — everything under externals/ is somebody else's document, which is what makes do-not-edit a rule you can state in four words. Proposes .loom/orientation.md, beside event-log.md, since .loom/README.md is already given to what is in published/ and why. Answers the Question yes. The visibility check was built as a moment and loom is right that it is a fact that decays: access is verified once and the copy is durable. It costs nothing at add time, since the anonymous request already happens and the answer is thrown away, and one request per run at check time rather than per document, since only our own visibility must be current. Adds the refinement that the stored value decays both ways, so a source recorded private is re-checked only when the alarm would fire. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018UTxuSizozEA8yDitPuris |
||
|
|
fb8b8d01d3 |
rowan converts: four entries, no shelf, nothing blocked
All three prompts answered and extracted, the polad tree empty, and check clean before the rm — which under v2 is the audit that has to happen, because the cart is the only copy of everything in it. The round's substance: three gaps closed at reconciliation, and in every case what survived was a local workaround becoming specified behaviour — wal.md, which v2 named and cited our own sentence as the reason for; "a fetch you did not perform", which had been a declined command and an insight without an implementation; and the compaction rule, which came back with an addition we had not thought of. Declines fixing annotating, because two of its four gaps are not gaps in the agreement but a rule living in a local editor protocol instead of the document, and deciding where a rule lives is not an amendment and not ours. Records that a casting file goes stale on amendments that add no role — twice in one day, v1 and v2 — and that the mechanism fired correctly both times and can only point. And declines opening a round on the third-file question, on the grounds that ours arrived as a conversion-timing failure the agreement already forbids, so it may be a symptom rather than a category. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018UTxuSizozEA8yDitPuris |
||
|
|
fb96b2f90b |
the private path is exercised, and the guardrail stops crying wolf
Closes the last unexercised assumption with a throwaway token: partial clone over HTTPS with a token against a private repository, list through the tool, the short-form raw URL redirecting to a resolved branch under auth, and a conditional request returning 304 across that redirect. A private repository can adopt from another private one, by name or by URL, and check works off the lock afterwards. Fixes what would have shipped as noise. The warning fired whenever a fetch needed a credential, which in a private repository adopting from a private repository is every time and legitimate. add now resolves origin and makes one anonymous request to learn whether this repository is public, so the warning fires when the source is private and the destination is not. The limit is stated in the message rather than implied away: the signal tells public from not-public and nothing finer, so two repositories private to different people is the case that genuinely widens access and the one this cannot see. No origin means cannot tell, which warns — cannot tell must never read as not public. Notes when an adopted document did not come from .loom/published/, without refusing. What is not exported is not hidden, but a lock against it records a dependency on something that was never a contract. And records the framing that settles all of this: the tool is a mast, not a lock. It grants no access, everything it does is possible with copy and paste, and the locks mean nothing outside the tool and the discipline of the agreement — so it makes the wrong thing deliberate rather than impossible. What it adds over a paste is not restriction but provenance. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018UTxuSizozEA8yDitPuris |
||
|
|
73b3a97dce |
contexts, and adopting by name: loomctl external add loom/cart cart
The config becomes kubectl-shaped — named contexts with one current, each holding a host, its flavor and a read-only token — and a bare owner/repo resolves against it. The point is where the details live: a host's raw-file route belongs to the host so it sits in the context, the published directory belongs to the convention so it sits in the code, and what is left is which repository and which document, which is the only part a person knows. Measured rather than assumed, because the three hosts differ. Gitea redirects its short raw form to the resolved branch, so the lock records a branch without anybody naming one. GitHub and GitLab accept HEAD and do not redirect, which would put a moving ref in the lock — the hazard already recorded and nearly built anyway — so those resolve the default branch with git ls-remote --symref first, one round trip and no clone. That supersedes the claim that list is the only command needing git, which is now wrong for two of three flavors and would otherwise read as still true. Adds loomctl config, which says which context is current and where the credential came from without printing it, so that "my token is not being used" is answerable. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018UTxuSizozEA8yDitPuris |
||
|
|
104cbd050c |
rowan: reconcile three amendments, close three gaps, stage the answers
check found three documents moved and they were the three the gaps were filed against, so the loop ran the other way for the first time: gaps filed, documents amended, and the tool reported it rather than somebody saying so. publication now carries publishing is not an act you can complete alone as its own section, with the part we asked for — the failure is invisible from the inside, every check available to the publisher passes — and says publication has a visibility, which is what externals' confidentiality rule had been depending on. recording-decisions permits compaction with the four kinds that may never be dropped, adding that a compaction is itself a decision and gets an entry. cart is v2 and says converting destroys, so extract first, naming the write-ahead log. Both closed gap files are deleted at reconciliation rather than at repair, since each stayed true of the copy held until check reported 200. cart.gaps.md keeps four of five and gains a reframing of the third-file case: ours appeared because a round was converted while somebody was still writing, which the agreement already forbids, so it may be a symptom rather than a category and the fix is upstream. cart.usages.md gains a v2 section and a note that the cart arrived headed rowan in a file named loom-osprey.md with no name file — the name is what tags durable entries, so a disagreement mis-files the record silently. Answers staged in wal.md as the round runs, which is v2's rule applied to the round that produced it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018UTxuSizozEA8yDitPuris |
||
|
|
49c7297d08 |
the README had drifted into being wrong; make it short and true
It still said Not built, listed pull and reachable — neither of which exists — and claimed everything was decided in one round. Written at a conversion and never touched again. Deliberately does not enumerate the commands, because a prose command list is exactly what went stale. It points at --help, which is the documentation surface we already decided on and which cannot drift from the binary. Worth noticing rather than logging: check catches documents that drift from their publisher, and nothing catches ours drifting from our own code. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018UTxuSizozEA8yDitPuris |
||
|
|
e0d7afecae |
gaps against all four conventions, and reference-only declined for now
Writes the first real gaps: what an adopter could not tell from the documents, recorded while the misunderstandings are fresh rather than after they stop being visible. cart — whether a response is an annotation or a reply in your own file, and the discriminator that resolves it appearing nowhere; what to do when an open item has no fallback, which breaks the mechanism rather than omitting a nicety; what a third file means once one exists; and what must be extracted before converting, now that v1 makes conversion destroy. annotating — how a reader detects the freeze was violated, since the hash we check is a local invention; that responding means a rewrite, which is named only in our local protocol; and that under cart v1 a prompt's answer has no archive at all. recording-decisions — the one that matters most: never revised and somebody must be able to read it both hold and eventually conflict, and the document does not say which gives. Provisional answer recorded, with the line compaction may not cross — never drop a decline, a measurement, a falsified belief, or a supersession, because those are the record of somebody changing their mind. publication — whether publishing implies the audience can read it, and where confidentiality lives given that what is safe to publish depends on who can read the repository it sits in. Also declines reference-only adoption with its reason: it is adoption minus reconciliation-by-diff, not adoption minus offline reading, and the convention's other exit is the one that actually occurred. The warning now names both exits including the unbuilt one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018UTxuSizozEA8yDitPuris |
||
|
|
c09a1b7570 |
merge marmalade: loomctl external, built and exercised on real changes
The round that authorised this work converted into the log; this is the work. list, add, check and apply, in Go with no dependencies outside the standard library and git shelled out for list alone. Exercised rather than demonstrated. Eight documents adopted by hand before the tool existed were locked without rewriting any of them, because identical bytes make a lock's assertion verified rather than assumed. Two real upstream changes were found that nobody would have noticed, and the second of them — the rule that confidentiality does not travel with the copy — arrived through the tool rather than through somebody saying so, and is now half-implemented with the tool saying which half it can see. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018UTxuSizozEA8yDitPuris |
||
|
|
f89f7c25c6 |
quince converts: five entries, and the first conversion that destroys
Under cart/v1 the cart is untracked, so converting deletes the dailies outright rather than leaving them in history. That makes the write-ahead log the mechanism rather than a convenience, and it sharpens the conflict already recorded in cart.usages.md: the annotation protocol here says commit before dissolving because git is the only archive, and an ignored cart has no archive. Answers whether the cluster split removes the need for a PAT. For this repository yes, and in general no: the confidentiality rule sorts the cases, so a public repository may only adopt public documents and never needs a credential, while a private one adopting private documents legitimately does. Needing a credential stopped being a capability and became a signal, which is why add warns rather than deciding — it sees that the source is private and cannot see who may read the repository the copy lands in. Downgrades the git-over-HTTPS assumption from load-bearing to unexercised. The code did not change and its status did, which is the kind of thing only a log says. Records that the completeness case has a mechanism after all — the usages facet the tool points at when it stages a polad — and that it failed the first time because the facet did not exist rather than because nothing could work. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018UTxuSizozEA8yDitPuris |
||
|
|
6a5966ba99 |
quince: reconcile through the tool, write the facets, and adopt gitea.md
The whole loop ran on a real change with a cart open for the first time: check staged externals.md as a polad, apply moved it and its lock, and the new confidentiality rule arrived through the tool rather than through somebody saying so. Answers loom's completeness case. A 200 says the document moved and nothing about whether the casting still covers it — but the mechanism exists and we had not built the thing it needs. check prints the document's .usages.md when it stages a polad, and for an agreement that is the file the roles are cast in. It failed here because externals.md had no facet at all. So both are written: externals.usages.md naming which Go file implements which rule, and a v1 section on cart.usages.md recording the casting, that we got it wrong before it was written down, and that osprey and marmalade stay in history unrewritten. Implements the adopted confidentiality rule as far as it can be implemented. add warns when a fetch needed a credential, and says plainly that it cannot see who may read the repository the copy lands in. Reference-only adoption is recorded as not yet implemented rather than as a gap, because the gap is ours. Adopts gitea.md, now that homelab-cluster is public — the document that cost three tool calls and a guess this morning, with a facet recording that its :2222 fact is a fact for people and not for the tool, whose every transport is HTTPS on 443. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018UTxuSizozEA8yDitPuris |
||
|
|
c0ae0e892f |
declined a separate adopt verb, and say that add will not rewrite
Both parties reached the same deadlock independently: add refusing an existing path, and check unable to ask about a document with no recorded origin. adopt was the other way out and is not obviously worse. Not taken because the byte comparison makes the verb unnecessary. Whatever the command is called it must not trust the claim — it has to fetch and compare, because the point is that nobody knows whether the local copy is still a copy — and once it does that, it is add with an origin supplied. A verb earns its place by naming an act rather than a state, and present-but-unlocked is a condition a repository is temporarily in. Recorded with the falsifier: if people and agents keep dropping documents into externals by hand, and they will, it is a recurring category rather than a migration, and then it deserves its own word in --help rather than a hint. Also makes check's hint say that add supplies the origin without rewriting the local copy, since the previous wording left that to be inferred. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018UTxuSizozEA8yDitPuris |
||
|
|
021bf63a11 |
a changed external is a polad, and add no longer overwrites anything
Restores something the specimen said and the round that discarded the specimen lost with it: a changed external becomes a polad in the cart, and somebody decides. check now stages what moved into .loom/cart/current/polad/ with the ETag that was served alongside the bytes, and prints the .usages.md beside it, because reconciliation runs the other way — the facets usually survive and what moves is the code a usage named. It says so when there is no usages file, which is its own finding. With no cart open, check reports and stages nothing. The tool does not open a round: a cart is a bounded exchange between two presences and starting one is somebody's act, not a side effect of asking about freshness. add now adopts what is not here and refuses what is already adopted, superseding the entry that had it announce an overwrite — it no longer overwrites at all. The one exception is the only way out of a dead end: a document present but unlocked was fetched by hand, nothing records its origin, and the path does not round-trip, so check cannot ask about it and a refusal would strand it forever. add accepts it and the bytes decide — identical locks it without rewriting anything, which makes the lock's assertion verified rather than assumed, and different stages a polad and leaves the local copy alone because it is the only evidence anything moved. apply exists because the lock is the half a person forgets: moving a polad by hand leaves a lock describing the copy you just replaced. Recorded with its limit — for an external, discard does not mean the change goes away, so discarding is really knowingly stale and nothing yet records that choice. Measured end to end on this repository: eight hand-fetched documents, all eight locked, nothing rewritten. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018UTxuSizozEA8yDitPuris |
||
|
|
57ed133021 |
add says what it replaced, and the planted change proved less than it looked like
The upstream cart.md change was planted to test whether the tool would notice, and the honest sequence is that check did not catch it. Every document was unlocked, and unlocked means I cannot tell you. add revealed the change by overwriting the file, and the only reason the diff was visible is that git happened to be watching the working tree. So the mechanism is proven and the workflow is not: a document nobody has locked is a document nobody is checking, silently. Fixes the defect that exposed. add reported only "adopted" while overwriting local content that differed from what the publisher serves — and a differing copy is the only evidence that something moved while the document was unlocked. It now says so. Recorded with its own limit: add still overwrites rather than stashing, so outside a git working tree the note says something was lost without letting you see it. Also records the unowned half. Nothing decides when check runs, and every answer — a hook, CI, an agent's session start, a schedule — is outside the tool. Filed as a need with no owner rather than built, because choosing one would put a scheduler inside a fetcher and a comparator, with the falsifier that if nobody ever runs check unaided then a tool that reports only when asked reports nothing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018UTxuSizozEA8yDitPuris |
||
|
|
1543df0a0c |
loomctl external: list, add, check — and its first run found two changed documents
Go, standard library only, with git shelled out for list alone. list enumerates a publisher's .loom/published by partial clone and ls-tree; add fetches one document, writes it under .loom/externals and records the resolved origin and the publisher's ETag in .loom/externals/.locks; check asks conditionally and reports. The first real run did what the tool exists for. All eight documents adopted by hand before it existed reported unlocked — the tool refuses to invent a lock by adopting whatever the remote currently serves, since that would assert the local copy is the one being served, which is the thing it was about to check. Locking them fetched two that had moved: bedrock/starting.md, which now says the worked example is private and will not link to something you cannot fetch, and cart.md, which went to v1. cart v1 changes a role we cast: a cart is not committed, because a committed cart grows a third file by itself — version control does not require anybody to ask, so the two-file rule is never invoked — and because ignored, gone means gone. Adds .loom/cart/ to .gitignore and supersedes the isolation entry rather than editing it. osprey and marmalade are already in history and are left there: rewriting to honour a rule adopted afterwards costs more than it buys. Records the conflict this creates rather than settling it: the annotation protocol here says commit before dissolving because git is the only archive, and an ignored cart has no archive, so dissolving would destroy the annotations outright. Credentials are read-only, per host, and passed to git through the environment rather than argv, because argv is visible to every process on the machine. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018UTxuSizozEA8yDitPuris |
||
|
|
f0b3269610 |
marmalade converts: eleven entries, and no shelf
Moves the wal into the log and closes the round. Nothing was unresolved: the one open item — where the credential lives — is recorded as decided by fallback rather than shelved, since cart says silence is a usable reply and that a fallback which quietly becomes the decision is worth being able to find later. The round decided the shape (loomctl, nouns, no plugin seam until somebody reports a gap, no audience declaration), declined published check on the registry argument with both its falsifiers, kept the insight that outlives it, settled list on git with the three-host measurement behind it, established that nothing needs ssh and nothing needs write access anywhere, chose Go, and recorded git-on-PATH as the consequence. Two entries carry things nobody decided. One is the untested assumption that a read-scoped PAT authenticates git over HTTPS and not only the API and raw files, which is the first thing to run against the next token and the only argument that would bring ssh back. The other is a wrinkle in the isolation role: it assumed work happens during a round, and both rounds so far have decided work that happens after, so a branch is named for the round that authorised it and outlives the cart. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018UTxuSizozEA8yDitPuris |
||
|
|
a419222477 |
marmalade: decline published check, choose Go, and open a wal
Adds .loom/cart/current/wal.md as staging for the event log, so conversion moves entries rather than rewriting them. published check is declined on the registry argument rather than on not needing it yet: the command simulates a consumer and a registry with named consumers has real ones, so an observed fetch beats a synthetic probe and building the probe first builds a worse version of what is coming. Two falsifiers recorded — registry never materialising, or registry recording declared consumers rather than observed fetches, since who says they depend on us is not who can actually read us. Dropping it also removes the only thing complicating the credential story, because --as existed almost entirely to serve it. The insight is kept separately because it outlives the command: a check performed with your own credential proves nothing, and whoever builds registry's consumer view needs that before they design it, since the tempting implementation is for the publisher to verify their own publication. Go, for a single static binary with no runtime, on a tool that gets installed on other people's machines and run casually by humans and in loops by agents. Recorded with its own falsifier: if it only ever runs in one or two places, the choice was made on a cost nobody pays. And the consequence — list shells out to git, so git must be on PATH, which makes list the seam if that ever bites. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018UTxuSizozEA8yDitPuris |
||
|
|
76f5aef17a |
marmalade: reviewed — ssh is needed nowhere, and write access nowhere either
Every operation is HTTPS on 443. Measured that partial clone with the blob filter works over gitea's smart HTTP, so list never needed ssh; I had built and measured it over ssh out of habit. The tool writes only to the working tree — a fetched document, a lock, a facet — and never commits or pushes, so it needs no write credential anywhere, including this repository. Same reason cart gives for an agent committing and not pushing: the consequences of a push land on people it cannot experience. write:repository should never appear on a token this tool holds. The exception is published check, which needs an identity that is not the owner's. That is not an ssh problem and not solvable by scoping — it is the command's point. Anonymous or a reader credential, both HTTPS, and checked-as-owner-proves-nothing when neither is held. One untested assumption, and it is load-bearing: whether a read-scoped PAT authenticates git over HTTPS and not only the API and raw files. Those are different paths in gitea and only the second was ever measured. If it needs a broader scope, that is the sole argument for keeping ssh for list. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018UTxuSizozEA8yDitPuris |
||
|
|
5f87d8dd72 |
marmalade: dissolve the annotations, and the file is released
All five annotations incorporated and claude-marmalade.notes.md deleted, which reopens the daily. Dissolve is all-or-nothing, so this is a rewrite rather than an appended pass — the five appended passes are collapsed into one document, which is most of the point of dissolving. Three prompts are now answers. Subcommands yes, and no plugin boundary until somebody reports a gap, which is a better formulation than my reasoning was — the seam gets built when it is reported, not designed. No audience declaration, with his better future version recorded: project it from the other side by reading everybody's externals to derive who actually depends on you, which is registry's shape and registry is deliberately not built. And --help is the documentation surface because that is what a person or an agent actually runs, with a .loom/published for loomctl deferred rather than declined. Reframes the conversion-timing note. I had it as a defect in how cart phrases its question; he says the tolerance is the design, and that a mess made occasionally inside a cart is cheaper than rules strict enough to prevent it. The shelf existed for it and was not needed. Leaves one thing open with a fallback: where the credential lives, now that the config is not only a secret but how you talk to a host at all. Collects the three corrections I made this round in one place, because they are the same shape — a result that agreed with me and no test of the case that would not have — which is the failure osprey identified in the ETag story and which I have now committed three times while writing it down. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018UTxuSizozEA8yDitPuris |
||
|
|
a43255709d |
marmalade: loom annotates claude-marmalade.md
Five annotations, three of them answering prompts. Committed before dissolving, because git is the only archive of the annotation conversation — the notes file is deleted by the response and the answers survive only inside the rewrite. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018UTxuSizozEA8yDitPuris |
||
|
|
24610ba353 |
marmalade: publication is not publicness, and a token tells you by refusing
Jeff's correction breaks published check rather than denting it. homelab-cluster publishes six documents and is private deliberately; publication.md says others, not everyone, and I read one word as the other. The tool as logged would have reported a correct repository as broken, so a superseding entry is added rather than revising the osprey entry. What survives is that bedrock's public page promising a private repository as the better answer was a real defect, and it was never about homelab-cluster. So published check must not report a verdict. It reports the identity it used, and warns when that identity is the repository's own owner — publishing is not an act you can complete alone, and a tool that verifies with your own credential has helped you complete it alone and told you it was fine. Also corrects me: I confirmed the specimen's token claim enthusiastically without testing the case that would show it wrong. A refused write names the token's scopes in the error body, so a token cannot be asked what it may do but can be told by being refused. Verified that no other channel exists — the tokens endpoint rejects token auth, and no scope header comes back on success. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018UTxuSizozEA8yDitPuris |
||
|
|
aa20eaff89 |
marmalade: git wins list, measured — and I overclaimed the auth saving
git archive --remote is out: gitea allows it, github 422s, gitlab 404s. Partial clone with --filter=blob:none --depth=1 --no-checkout is uniform across gitea, github and gitlab, and lists homelab-cluster's six published documents over ssh in under a second — the list I could not get this morning. REST would be three URL shapes, three JSON shapes and three auth schemes, and it 404s on the private repo, which is the case that matters. Corrects my claim that git collapses the auth section. It does not: list goes over git, but add and check still want HTTP, because a 304 is a few hundred bytes and a partial clone is 124KB, so checking eight externals over git costs a megabyte to learn nothing. Private raw HTTP needs a token. Two credentials per host, not one. The argument for git is consistency, not thrift, and that argument stands alone. Records the failure mode: --filter is a server capability and git's fallback is silent, so detect the warning rather than letting list become a full clone. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018UTxuSizozEA8yDitPuris |
||
|
|
92529fc5f2 |
marmalade: the third file was my conversion, not his timing
cart says do not convert while someone is mid-thought, ask whether they are ready, and say so if you convert anyway. I asked and got approval of a plan, which is not the same as having nothing left to write — so the check passed and was still wrong. Reported upward: are you ready to convert and is there anything you are still writing are different questions, and only the second would have caught it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018UTxuSizozEA8yDitPuris |
||
|
|
c51655f626 |
marmalade opens: a CLI shape, and reachable has no home under external
Jeff's notes arrived as jeff-osprey.md, into a cart that had already converted. Moved unedited to loom-marmalade.md: cart allows two files and gives the test — what happens to this file when the person changes — and jeff-… dies with the person, so it is a person and not a stake. loom renamed itself for this exact reason last round and said that if Jeff wrote here he would write into that file. The shape predicted it. His subcommand grouping exposes something osprey got wrong. pull, check and reachable were logged as one family and they are two: external is what somebody else published and we depend on, published is our surface and somebody else's dependency. reachable looks at .loom/published/ and therefore has no home under external — so it becomes loomctl published check, and check means one thing in both nouns. external list is the first command that must know what a host is, and it is worth it: the origin path stops being parsed because the tool builds the URL, and it is discovery, which the log records as the thing a fetcher cannot do. Asked as a Select with a lean toward git itself as the one API every host already speaks, with a fallback so silence still builds something. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018UTxuSizozEA8yDitPuris |
||
|
|
d3701c7722 |
osprey converts: the design is the log, and the cart is gone
The round is over and its artifact is .loom/event-log.md — fifteen entries, each carrying the belief that could show it wrong, all tagged osprey so it stays findable what else was in the room. No spec is written. A spec flattens everything to equal confidence, which is how the specimen managed to be wrong with a straight face within a day of being proposed. The specimen is discarded rather than promoted; the story of its being wrong is in the log, where it can be found, and the artifact would only mislead somebody who read it without the round beside it. Reconciles externals.md, which check flagged as the one drifted document of eight. The new copy carries the 404 row, so the gap filed against it is closed and externals.gaps.md is deleted — at reconciliation rather than at repair, because a facet describes the local pair and the gap stayed true of the copy we held until we fetched. The entry recording that our 404 behaviour is no longer provisional is the one that would have been easiest to skip and is the reason the log exists. No shelf: nothing was unresolved. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018UTxuSizozEA8yDitPuris |
||
|
|
bacb3698cf |
osprey: fifth pass — all three reachability failures fixed
The builder measured what we published and none of it could be fetched by anybody who is not us. Acted rather than agreed: the settled page moved to loom/.loom, bedrock's starting page no longer links a private example and says why, and externals.md retracts the claim that the path records the origin. The settled one stated plainly: the defect was "the justification is inside the private thing" and my fix put a page inside the private thing. Same repository, same problem, one layer in, and I called it fixed without anyone able to read it. Publishing is not moving a file into published/; it is the file becoming fetchable by somebody who is not you. Yes to .locks, two fields, resolved URL, and the rename — the old name was chosen when we believed there would be one field, on a claim that has failed twice. Yes to unlocked being a reported state rather than a thing check silently adopts. And they are right that check must not resolve the 404 over ssh: it would be fixing rather than reporting, and the ssh sentence belongs in the document telling a person what to do next. Their boundary on reachable is better than mine — it answers can anybody fetch this and not will anybody find it, and the second is not testable by a fetcher. So the .profile landing-page finding goes to loom rather than into the tool. The :2222 story is left for loom to write in their own words. It is the strongest evidence produced this week and it belongs in a log rather than a daily. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
27e5c07fdf |
osprey: fifth pass, measured against the real gitea
Runs the design against the thing it describes, with an ssh key and anonymous HTTP, and corrects a claim I made confidently and wrongly. The 404 I offered last pass as the ambiguity "live" was my own bad URL. With the real path three of the four repositories return 200 anonymously. I built a URL out of a claim I was in the middle of disproving and read its failure as evidence for something else. The path finding is worse than stated: the stored external path drops raw/branch/main and .loom/published. The second is not routing — publication.md makes .loom/published the whole contract, and our tree erases exactly that segment, so nothing records whether a copy came from a published surface. check ran by hand over all eight externals and found exactly one drift: externals.md, whose diff is this round. Conditional requests confirmed, 304 on match and 200 on stale, and seven of eight are byte-identical, so gitea's raw serving normalises nothing. reachable is validated three times over, each a real failure: settled publishes a page nobody can fetch, .loom publishes one nobody will land on, and bedrock's public starting page names a private repository as the better answer. The second is the boundary of what the tool can test and is reported as such. Records that the :2222 fact was published in homelab-cluster all along, on a page that predicted the exact failure mode I hit, and that I could not read it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018UTxuSizozEA8yDitPuris |
||
|
|
678cfdffd4 |
osprey: fourth pass
Built the settled page they proposed, and credits the finding: they found the option in publication.md, a document I wrote and had stopped reading as something that could answer a question. Their sharper framing made it obvious — ls published/ is "what have we committed to", and settled had committed to nothing while holding authority over four repositories. Accepts their correction that they are not an independent data point on the annotating question, since they arrived into a repository where correspondence was the observed form. Two parties, one of them convenient for me. But the tension they found is not there, and it is a reading rather than a ruling: cart says only dailies get annotated, which is a restriction on what may be annotated rather than a prescription that responses must be annotations. With loom's discriminator — the freeze creates an obligation, so annotation is the blocking form and correspondence the non-blocking one. What survives is practical: dissolving a multi-pass daily is expensive, so the cost of annotating rises with the length of the thing annotated. Confirms their conversion reading with no polad. The artifact is the design and it lives in the event log, and a spec for a tool nobody has started is the specimen again. One thing the log has that a spec does not: every entry carries a belief that could be shown wrong, so a reader sees which parts are load-bearing — a spec flattens that to equal confidence, which is how the specimen managed to be wrong with a straight face. Caveat recorded: a log is ordered by time and not topic, fine at eight entries and not eighty. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
31ba7e1389 |
osprey: third pass
Takes the correction that matters: a gap is true of a copy, not of an upstream. I was about to delete their .gaps.md because I fixed my document, and their copy of externals.md still lacks the 404 row, so the gap remains true of what they hold. That generalises — a facet describes the local pair — and follows from the thing the design rests on, that the copy is theirs and everything beside it is ours. It goes into the convention rather than staying an exchange. Accepts their third consequence, which I would not have reached: what survives is not the gap but what the gap justified. Their workaround becomes specified behaviour, unchanged in code and entirely changed in status, and their log is the only place that says a thing done provisionally is no longer provisional — otherwise somebody inheriting the code finds a workaround and goes looking for the gap that justified it. Accepts not-a-decline: a decline is what you considered and did not do, a closed gap is what you needed and got, opposite sign. Admits acting on a file that did not exist. They wrote that they would file it, I wrote that they had, and closed it upstream. The shape is that I read a stated intention as a completed act. Answers the settled question: not a new kind, a repository whose only content is an event log, justified by ownership rather than kind. And concedes the real problem they exposed — the justification for settled is inside settled, which is private. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
e031972143 |
osprey: second pass, and my daily was misnamed
Renamed claude-substrate-osprey.md to loom-osprey.md. In this repository the builder owns the work and I am everyone else collapsed, so I am loom — and naming myself by instance would have grown a third daily the first time Jeff wrote here, which is the thing cart forbids. Accepts the branch split, the two rows leaving check, the flat .etags file, and all three declines — especially init as declined rather than deferred, which is the sharper reading. Takes the correction on the ETag rule, which is worse than they put it: I wrote "never a hash you compute" in externals.md and then wrote a specimen whose central claim is to compute a hash and compare, two days apart, same author. Their reframing is the finding and mine was not — the GitHub test re-derived a rule rather than discovering one. And their diagnosis of how is the useful part: a verified fact is much louder than a rule. Accepts the 404 gap as correctly filed and mine to close, and their friction finding, where the diagnosis is better than the catch — cart.usages.md hands over an uncast role without restating the fallback rule at the point of handover. On publish: the fault is the name rather than the command. It performs no publication; it asks whether somebody who is not you can fetch a thing, which is a fetch with a different subject. Discards the specimen at conversion, and asks the one thing neither of us knows: whether a closed gap leaves a record or just goes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
f84a55ea6d |
the ETag is not universally a blob hash — verified against GitHub
We left an Affirm asking whether the assumption was load-bearing enough to check first. Checked it an hour later and it fails: GitHub's raw ETag is 64 hex, a sha-256 of something that is not the git object, where gitea's is the blob sha. The API's sha field is the blob sha and matches git hash-object on the fetched bytes, so the content is identical — only the header differs. What survives: conditional requests work on both, and If-None-Match returns 304 from GitHub exactly as from gitea. What does not: "hash the local copy and compare to the remote's ETag" was a gitea coincidence, so there is something to record after all — the ETag itself, opaque, per document. Where it lives is the builder's call, with one steer: not a .md facet, since it is machine state rather than prose. And the check we thought we were getting free is free from elsewhere anyway — an edited local copy shows up in git status, because the copy is committed. The specimen still describes the old idea and is left as written. A specimen is a snapshot of what was proposed, and correcting it in place would hide that we were wrong within a day of proposing it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
81c4917b9f |
a round, four externals, and the spec as a specimen
The repository is furnished rather than scaffolded — every file has content and a reason, which is the distinction the spec itself draws when it says to resist building init as a scaffolder. Four externals fetched and locked under .loom/externals/: bedrock, externals, annotating, cart. Copies, not to be edited, with a usages file beside cart casting three of its four roles and leaving one open. The one left open is what isolates work in progress, and the usages file says how we cast it wrong once and why the belief was more useful than a preference would have been. The spec moves out of the README and into the cart as a specimen, which makes it discard-by-default — that is a stronger statement of "argue with this" than a sentence asking them to. bedrock and externals stay non-discardable, since accommodating them is what makes this a loom tool. The daily says where we expect to be wrong: whether check can say anything useful about a 404, and whether hash-as-lock survives a fetch that normalises anything, which we have only tested against gitea. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
304b854dbd |
the spec, written while it was fresh
Not built. Recorded now because the reasoning is a day old and would be expensive to reconstruct. The idea that makes it small: on gitea a raw file's ETag is the git blob hash, verified on both a public and a private repository. So there is nothing to record — git hash-object on the local copy is the lock, and comparing it to the remote's ETag is the whole mechanism. No lock file and no state to drift. It also catches a case we had not considered: an edited local copy stops matching, which is correct, because an adopted copy that has been edited is no longer a copy. check reports and does not fix; a changed external becomes a polad and somebody decides. pull fetches one document and deliberately does not create an empty facet. publish verifies the document is fetchable by somebody who is not you, because publishing is not an act you can complete alone. And init is flagged to resist: its temptation is to scaffold, and four empty directories assert four things nobody has decided. Credentials split by operation rather than by visibility, with the token named for its scope, since a token cannot be asked what it may do. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |