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
This commit is contained in:
2026-09-07 14:57:57 -04:00
co-authored by Claude Opus 5
parent 57ed133021
commit 021bf63a11
5 changed files with 383 additions and 94 deletions
+16 -7
View File
@@ -42,18 +42,24 @@ type Record struct {
// Set is every lock, keyed by path.
type Set struct {
root string
file string
recs map[string]Record
}
func path(root string) string { return filepath.Join(root, Dir, File) }
// Path is the lock file for the repository at root.
func Path(root string) string { return filepath.Join(root, Dir, File) }
// Load reads the lock file for the repository at root. A missing file is an
// empty set, not an error: a repository whose externals were fetched by hand has
// no locks, and reporting that is the point.
func Load(root string) (*Set, error) {
s := &Set{root: root, recs: map[string]Record{}}
f, err := os.Open(path(root))
func Load(root string) (*Set, error) { return LoadFile(Path(root)) }
// LoadFile reads a lock file from an explicit path. A staged polad carries its
// own alongside it, so that applying it uses the ETag that was served with the
// bytes somebody reviewed, rather than whatever the publisher serves later.
func LoadFile(file string) (*Set, error) {
s := &Set{file: file, recs: map[string]Record{}}
f, err := os.Open(file)
if errors.Is(err, fs.ErrNotExist) {
return s, nil
}
@@ -70,13 +76,16 @@ func Load(root string) (*Set, error) {
}
parts := strings.Split(line, "\t")
if len(parts) != 3 {
return nil, fmt.Errorf("%s:%d: want 3 tab-separated fields, got %d", path(root), n, len(parts))
return nil, fmt.Errorf("%s:%d: want 3 tab-separated fields, got %d", file, n, len(parts))
}
s.recs[parts[0]] = Record{Path: parts[0], URL: parts[1], ETag: parts[2]}
}
return s, sc.Err()
}
// Remove drops a record.
func (s *Set) Remove(p string) { delete(s.recs, p) }
// Get returns the record for a path, and whether there was one.
func (s *Set) Get(p string) (Record, bool) { r, ok := s.recs[p]; return r, ok }
@@ -96,7 +105,7 @@ func (s *Set) All() []Record {
// Save writes the lock file, replacing it atomically so an interrupted write
// cannot leave a repository holding half a lock.
func (s *Set) Save() error {
p := path(s.root)
p := s.file
if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil {
return err
}