b3617ed195c14ef043e7fda1a54ed37f36002c81
8
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |