makeover-layout 0.41.1

The renderer-agnostic half of the make-family design system: what a thing IS, named as intents and relationships and never as values. Colour defers to makeover, spacing to makeover-geometry; what is left is composition.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
#!/usr/bin/env python3
"""Do the tree's in-house `version` requirements still resolve?

DO NOT EDIT IN PLACE. The master is _private/infra/bootstrap/githooks/internal-deps.py.

Usage:
    internal-deps.py <tree-root> [repo-root] [pushed-sha]

With a repo root, only pairs that repo is on either side of can fail the run;
everything else is reported as a note. Without one, every pair is graded, which
is the whole-tree report:

    python3 internal-deps.py ~/Code

The run grades FOUR views and fails on any of them:

  working copy   what this machine builds today. The historical check.
  as pushed      the same question asked of the pushing repo's manifests AS THEY
                 EXIST AT THAT COMMIT, against the rest of the tree on disk.
                 Needs a pushed sha, so it is skipped in the by-hand report.
  as published   every requirement against the version the provider has actually
                 PUSHED, read from its last-fetched remote-tracking ref. This is
                 the only view that predicts a build on a machine that is not
                 this one.
  as published to crates.io
                 the same question for the siblings that are consumed from the
                 REGISTRY rather than by git URL: the makeover suite, alloy_tui,
                 pter. A requirement only the local checkout satisfies is a
                 finding, and the remedy is to publish rather than to push.

WHY THE SECOND VIEW EXISTS. The first one reads manifests off the filesystem, so
an uncommitted forward-fix makes it grade text that git is not publishing. That
is not hypothetical: on 2026-08-24 mnw-cli's `synckit-client` requirement had
been advanced to "0.9" in the working copy and never committed, this gate printed
`internal deps coherent (42 requirements)`, the push went out, and Sando failed
to resolve `^0.8` against 0.9.0 minutes later. The gate was checking a tree that
was not the tree being published, and nothing distinguished that from real
coherence.

WHAT IT GRADES. Every dependency in the tree that carries both a `git` URL on one
of our forges and a `version` requirement, against the version in the working
copy of the crate that URL names. That is the pairing cargo enforces and the one
that broke: a requirement of "0.11" against a sibling that has moved to 0.14 is
not a warning, it is a graph that will not resolve on any machine.

WHY DISK AND REMOTES BOTH. `~/Code/.cargo/config.toml` patches every one of these
dependencies to the working copy in the tree, so what is on disk here is what
every local build reads: a bump that has not been pushed yet breaks its consumers
on this machine just as thoroughly, and finding that out at push time is the
point. But cargo resolves a git dependency against the branch head at the URL, so
the `[patch]` block also HIDES an unpushed sibling from every local check. That
gap cost a production build on 2026-08-26 (infra `5c4928c1`): MNW required quasi
"^0.63", quasi's working copy was 0.63.0 and its `mnw/main` was 0.56.0, both disk
views were clean, and Sando could not resolve. The three views answer three
different questions and all of them matter.

WHY THE FOURTH VIEW EXISTS. The three above all pair a requirement against the
sibling repo its git URL names, and a makeover crate has no git URL in a consumer
manifest: it is consumed from crates.io. So the failure class this gate exists to
stop -- bump a library, forget its consumers -- had a second half that was not
merely ungraded but confidently reported coherent. On 2026-08-28 quasi-basics
required makeover-layout "^0.38.0" while the index's newest was 0.37.0 and 0.38.0
existed in one working directory; `cargo check` could not select a version and
this gate printed `internal deps coherent [as published] (42 requirements)`. The
same afternoon, stripping the [patch.crates-io] blocks mid-publish put MNW/server
on makeover "^3.1" against an index whose newest was 3.0.0, with the same verdict.
That window -- manifest bumped, crate not yet on the index -- is a normal stage of
any correct publish order, short when the publish succeeds and unbounded when it
fails partway across a seven-crate suite. makeover-layout carries `links`, so one
unpublished crate in the suite takes down every consumer in the graph at once.

Blame for this view is CONSUMER-side only. Pushing the provider is how a crate
reaches the index in the first place (Bento publishes from a pushed tag), so
refusing that push because its own new version is unpublished would deadlock the
release. What is worth refusing is a push that carries a requirement nothing on
the index satisfies.

NETWORK. None on the happy path. The published view reads the last-fetched
remote-tracking ref, and only when a requirement FAILS against it does it fetch
that one repo's one branch and re-check, so a ref nobody has fetched since the
sibling was pushed cannot refuse a good push. A repo with no fetched remote at
all is reported as ungraded, never as passing. The crates.io view works the same
way one layer over: it reads cargo's own sparse-index cache under
~/.cargo/registry/index, and only a requirement that fails against the cache
costs one HTTPS fetch of that crate's index file, so a cache older than the
sibling's release cannot refuse a good push either. A crate whose index file can
be read from neither place is reported as ungraded.

WHAT IT DOES NOT GRADE, on purpose:

  third-party deps    a version requirement on somebody else's crate is not
                      something this tree can forward-fix.
  path deps           no version requirement to be wrong about.
  ranges and wildcards  `>=`, `<`, `*` and comma lists are deliberate statements
                      about a span, not a pin that drifts. Counted as unchecked.
"""

import glob
import json
import os
import re
import subprocess
import sys
import tomllib
import urllib.error
import urllib.request

# The forges that make a git URL ours. A dependency on somebody else's git repo
# is not something this tree can forward-fix.
OURS = re.compile(r"(makenot\.work|git\.sr\.ht/~maxmj)", re.I)

# Directories that hold code we do not grade: retired, staged for deletion, or
# not ours. Mirrors the sweep's exclusions rather than inventing a second list.
SKIP_DIRS = {
    "target", ".git", "node_modules", "dist", "vendor",
    "_archive", "_scratch", "trash", "_meta", "vtebench",
}
MAX_DEPTH = 4

DEP_SECTIONS = ("dependencies", "dev-dependencies", "build-dependencies")

# The crates.io view's label. Long on purpose: "as published" already means the
# git-URL siblings, and the remedy for the two is different.
REGISTRY_LABEL = "as published to crates.io"


def manifests(root):
    """Every Cargo.toml in the tree, shallow-walked."""
    out = []
    stack = [(root, 0)]
    while stack:
        d, depth = stack.pop()
        try:
            entries = list(os.scandir(d))
        except OSError:
            continue
        for e in entries:
            if e.is_file() and e.name == "Cargo.toml":
                out.append(e.path)
            elif e.is_dir() and e.name not in SKIP_DIRS and depth < MAX_DEPTH:
                stack.append((e.path, depth + 1))
    return out


def load(path):
    try:
        with open(path, "rb") as fh:
            return tomllib.load(fh)
    except (OSError, tomllib.TOMLDecodeError):
        return None


def dep_tables(doc):
    """Every dependency table in a manifest, including per-target and workspace."""
    for section in DEP_SECTIONS:
        table = doc.get(section)
        if isinstance(table, dict):
            yield table
    for cfg in (doc.get("target") or {}).values():
        if not isinstance(cfg, dict):
            continue
        for section in DEP_SECTIONS:
            table = cfg.get(section)
            if isinstance(table, dict):
                yield table
    ws = doc.get("workspace") or {}
    table = ws.get("dependencies")
    if isinstance(table, dict):
        yield table


def parse_version(v):
    """A version as a 3-tuple, prerelease dropped. Junk sorts as (0, 0, 0)."""
    core = str(v).split("+")[0].split("-")[0]
    parts = []
    for piece in core.split(".")[:3]:
        try:
            parts.append(int(piece))
        except ValueError:
            parts.append(0)
    while len(parts) < 3:
        parts.append(0)
    return tuple(parts)


def satisfies(req, version):
    """Cargo's default (caret) requirement semantics. None means 'not graded'.

    The rule that matters here is the 0.x one: under 0.1.0 and above, the MINOR
    is the compatibility boundary, which is why a 0.11 requirement rejects 0.14
    outright rather than treating it as a newer patch.
    """
    req = req.strip()
    if not req or any(c in req for c in "<>*,~"):
        return None
    # A prerelease satisfies nothing that does not ask for a prerelease of the
    # same version, so a plain requirement rejects it. This is the shape the
    # maturity ladder produces at beta entry: a sibling at 1.0.0-beta.1 does not
    # resolve for a consumer requiring "1.0", and cargo says so.
    if "-" in str(version).split("+")[0] and "-" not in req:
        return False
    exact = req.startswith("=")
    req = req.lstrip("^=").strip()
    if not req:
        return None
    given = req.split(".")
    try:
        r = [int(p) for p in given[:3]]
    except ValueError:
        return None
    v = parse_version(version)
    if exact:
        return tuple(v[: len(r)]) == tuple(r)
    if r[0] > 0:
        return v[0] == r[0] and v[1:] >= tuple(r[1:] + [0] * (2 - len(r[1:])))
    if len(r) == 1:
        return v[0] == 0
    if r[1] > 0:
        return v[0] == 0 and v[1] == r[1] and v[2] >= (r[2] if len(r) > 2 else 0)
    # 0.0.x: every patch is its own compatibility island.
    if len(r) > 2:
        return v[:3] == (0, 0, r[2])
    return v[0] == 0 and v[1] == 0


def git_lines(repo, *args):
    """Run git in `repo` and return stdout lines, or None if it failed."""
    try:
        out = subprocess.run(
            ["git", "-C", repo, *args],
            capture_output=True, text=True, check=True,
        )
    except (OSError, subprocess.CalledProcessError):
        return None
    return out.stdout.splitlines()


def git_manifests(repo, sha):
    """Repo-relative paths of every Cargo.toml at `sha`, or None if unreadable."""
    lines = git_lines(repo, "ls-tree", "-r", "--name-only", sha)
    if lines is None:
        return None
    out = []
    for rel in lines:
        if os.path.basename(rel) != "Cargo.toml":
            continue
        if any(part in SKIP_DIRS for part in rel.split("/")):
            continue
        out.append(rel)
    return out


def load_at(repo, sha, rel):
    """One manifest as it exists at `sha`. None if missing or unparseable."""
    lines = git_lines(repo, "show", f"{sha}:{rel}")
    if lines is None:
        return None
    try:
        return tomllib.loads("\n".join(lines))
    except tomllib.TOMLDecodeError:
        return None


def pushed_view(docs, repo, sha):
    """`docs` with everything under `repo` replaced by its content at `sha`.

    The rest of the tree stays as it is on disk, which is what a local build
    resolves against either way. Returns None if the commit cannot be read, so
    the caller can skip the view rather than invent a verdict about it.
    """
    rels = git_manifests(repo, sha)
    if rels is None:
        return None
    out = {k: v for k, v in docs.items() if not k.startswith(repo + os.sep)}
    for rel in rels:
        out[os.path.join(repo, rel)] = load_at(repo, sha, rel)
    return out


def repo_of(path, tree):
    """The git repo `path` belongs to, or None if it is not in one under `tree`."""
    d = os.path.dirname(path)
    while d.startswith(tree):
        if os.path.exists(os.path.join(d, ".git")):
            return d
        if d == tree:
            break
        parent = os.path.dirname(d)
        if parent == d:
            break
        d = parent
    return None


def publishing_ref(repo, cache):
    """The remote-tracking ref a git dependency on `repo` would resolve against.

    Cargo reads the branch head at the URL, and every in-house dependency URL is
    on one of our forges (CLAUDE.md, "what each remote is for": `mnw` is the
    public face, `srht` a backup, `astra` the private mirror). So prefer `mnw`,
    then any other remote whose URL is ours, and fall back to `origin`.

    Returns `<remote>/<branch>` or None when the repo has no such remote or the
    ref has never been fetched. None is not a verdict: the caller reports the
    repo as ungraded rather than inventing one.
    """
    if repo in cache:
        return cache[repo]
    ref = None
    lines = git_lines(repo, "remote", "-v") or []
    urls = {}
    for line in lines:
        parts = line.split()
        if len(parts) >= 2:
            urls.setdefault(parts[0], parts[1])
    order = [r for r in ("mnw",) if r in urls]
    order += [r for r, u in urls.items() if r not in order and OURS.search(u)]
    order += [r for r in ("origin",) if r in urls and r not in order]
    for remote in order:
        head = git_lines(repo, "symbolic-ref", "--quiet", f"refs/remotes/{remote}/HEAD")
        candidates = []
        if head:
            candidates.append(head[0].rsplit("/", 1)[-1])
        candidates += ["main", "master"]
        for branch in candidates:
            if git_lines(repo, "rev-parse", "--verify", "--quiet",
                         f"refs/remotes/{remote}/{branch}"):
                ref = f"{remote}/{branch}"
                break
        if ref:
            break
    cache[repo] = ref
    return ref


def version_at(repo, ref, rel, cache):
    """A crate's version in `repo` at `ref`, following a workspace inheritance.

    `rel` is the manifest's path relative to the repo. Returns None when the
    manifest is not at that ref at all, which is what a crate added since the
    last push looks like.
    """
    key = (repo, ref, rel)
    if key in cache:
        return cache[key]
    version = None
    doc = load_at(repo, ref, rel)
    if doc:
        pkg = doc.get("package")
        if isinstance(pkg, dict):
            v = pkg.get("version")
            if isinstance(v, str):
                version = v
            elif isinstance(v, dict) and v.get("workspace") is True:
                # Walk up to the workspace root as it exists at the same ref.
                d = os.path.dirname(rel)
                while True:
                    root_rel = os.path.join(d, "Cargo.toml") if d else "Cargo.toml"
                    root = load_at(repo, ref, root_rel) if root_rel != rel else None
                    inherited = (
                        ((root or {}).get("workspace") or {}).get("package") or {}
                    ).get("version")
                    if isinstance(inherited, str):
                        version = inherited
                        break
                    if not d:
                        break
                    d = os.path.dirname(d)
    cache[key] = version
    return version


def analyze_published(docs, disk_docs, tree, repo, sha):
    """Grade every requirement against what its provider has actually PUSHED.

    This is the view that predicts a build somewhere other than this machine.
    The other two read the provider's version off the filesystem, and the
    `[patch]` block in ~/Code/.cargo/config.toml means that is what a local
    build resolves -- but a git dependency resolves against the branch head at
    the URL, so an unpushed sibling passes both of them and fails everywhere
    else. That is exactly what happened on 2026-08-26: MNW required quasi
    "^0.63", quasi's working copy was 0.63.0 and `mnw/main` was 0.56.0, both
    existing views were clean, and Sando build 72 could not resolve.

    The repo being pushed is read at `sha` rather than at its remote, since what
    it is about to publish is the thing to grade. Every other repo is read at
    its last-fetched remote ref: no network on the happy path. A break is
    re-checked after fetching that one repo, so a stale ref cannot refuse a push
    on its own.

    Returns (broken, graded, ungraded), where ungraded maps a repo to why.
    """
    ref_cache, version_cache, fetched = {}, {}, set()
    versions_on_disk = crate_index(disk_docs, tree)
    broken, graded, ungraded = [], 0, {}

    for consumer_manifest, name, req in requirements(docs):
        known = versions_on_disk.get(name)
        if known is None:
            continue  # Not in this tree; the disk views already say so.
        provider_manifest = known[1]
        provider_repo = repo_of(provider_manifest, tree)
        if provider_repo is None:
            ungraded.setdefault(os.path.dirname(provider_manifest), "not a git repo")
            continue
        if repo is not None and provider_repo == repo and sha:
            # The repo under the hook: what it is about to publish is `sha`,
            # which the "as pushed" view already read off disk into `docs`.
            continue
        ref = publishing_ref(provider_repo, ref_cache)
        if ref is None:
            ungraded.setdefault(provider_repo, "no fetched remote to read")
            continue
        rel = os.path.relpath(provider_manifest, provider_repo)
        have = version_at(provider_repo, ref, rel, version_cache)
        if have is None:
            ungraded.setdefault(provider_repo, f"{name} is not at {ref} yet")
            continue
        verdict = satisfies(req, have)
        if verdict is None:
            continue
        if not verdict and provider_repo not in fetched:
            # Only now, and only for this one repo: a ref nobody has fetched
            # since the sibling was pushed would otherwise refuse a good push.
            fetched.add(provider_repo)
            remote, branch = ref.split("/", 1)
            git_lines(provider_repo, "fetch", "--quiet", remote, branch)
            version_cache.pop((provider_repo, ref, rel), None)
            have = version_at(provider_repo, ref, rel, version_cache) or have
            verdict = satisfies(req, have)
        graded += 1
        if not verdict:
            broken.append(
                (consumer_manifest, name, req, have, provider_manifest, ref)
            )
    return broken, graded, ungraded


def crate_index(docs, tree):
    """Crate name -> (version, manifest path), workspace inheritance resolved.

    A member saying `version.workspace = true` gets its number from the root,
    and reporting it as 0.0.0 would be a false break.
    """
    ws_version = {}
    for p, doc in docs.items():
        if not doc:
            continue
        v = ((doc.get("workspace") or {}).get("package") or {}).get("version")
        if isinstance(v, str):
            ws_version[os.path.dirname(p)] = v

    def resolve_version(manifest_path, pkg):
        v = pkg.get("version")
        if isinstance(v, str):
            return v
        d = os.path.dirname(manifest_path)
        while d.startswith(tree):
            if d in ws_version:
                return ws_version[d]
            parent = os.path.dirname(d)
            if parent == d:
                break
            d = parent
        return None

    versions = {}
    for p, doc in docs.items():
        if not doc:
            continue
        pkg = doc.get("package")
        if not isinstance(pkg, dict) or not isinstance(pkg.get("name"), str):
            continue
        v = resolve_version(p, pkg)
        if v:
            versions[pkg["name"]] = (v, p)
    return versions


def requirements(docs):
    """Every in-house git+version pair: (consumer manifest, crate, requirement).

    A dependency qualifies when it carries both a `git` URL on one of our forges
    and a `version`. That is the pairing cargo enforces and the one that breaks:
    a requirement of "0.11" against a sibling that has moved to 0.14 is not a
    warning, it is a graph that will not resolve on any machine.
    """
    for p, doc in docs.items():
        if not doc:
            continue
        for table in dep_tables(doc):
            for key, spec in table.items():
                if not isinstance(spec, dict):
                    continue
                git = spec.get("git")
                req = spec.get("version")
                if not isinstance(git, str) or not isinstance(req, str):
                    continue
                if not OURS.search(git):
                    continue
                name = spec.get("package") if isinstance(spec.get("package"), str) else key
                yield p, name, req


# --- the crates.io view -------------------------------------------------------

# Cargo's own sparse-index cache. One file per crate, holding the same
# newline-delimited JSON the registry serves, prefixed by a format byte and an
# etag and separated by NULs. Reading it is why the happy path costs no network.
INDEX_CACHE_GLOB = os.path.expanduser(
    "~/.cargo/registry/index/index.crates.io-*/.cache"
)
INDEX_URL = "https://index.crates.io"
INDEX_TIMEOUT = 5


def index_prefix(name):
    """The registry's directory prefix for a crate name (cargo's own scheme)."""
    n = name.lower()
    if len(n) == 1:
        return "1"
    if len(n) == 2:
        return "2"
    if len(n) == 3:
        return os.path.join("3", n[0])
    return os.path.join(n[:2], n[2:4])


def parse_index_blob(blob):
    """Every non-yanked version in an index file, newest first.

    Takes the cache format and the wire format both: the cache is the wire
    format with a header and NUL separators, so splitting on NUL and newline
    and keeping whatever parses as a version record covers each of them.
    """
    if isinstance(blob, bytes):
        blob = blob.decode("utf-8", "replace")
    out = []
    for chunk in blob.replace("\x00", "\n").splitlines():
        chunk = chunk.strip()
        if not chunk.startswith("{"):
            continue
        try:
            rec = json.loads(chunk)
        except ValueError:
            continue
        vers = rec.get("vers")
        if isinstance(vers, str) and not rec.get("yanked"):
            out.append(vers)
    out.sort(key=parse_version, reverse=True)
    return out


def index_from_cache(name):
    """Published versions from cargo's sparse-index cache, or None if absent."""
    for cache in glob.glob(INDEX_CACHE_GLOB):
        path = os.path.join(cache, index_prefix(name), name.lower())
        try:
            with open(path, "rb") as fh:
                return parse_index_blob(fh.read())
        except OSError:
            continue
    return None


def index_from_network(name):
    """Published versions from the sparse index itself, or None if unreachable."""
    url = f"{INDEX_URL}/{index_prefix(name)}/{name.lower()}"
    try:
        with urllib.request.urlopen(url, timeout=INDEX_TIMEOUT) as resp:
            return parse_index_blob(resp.read())
    except (urllib.error.URLError, OSError, ValueError):
        return None


def published_versions(name, cache, refresh=False):
    """Every non-yanked published version of `name`, newest first.

    Reads cargo's cache first and goes to the network only when the caller says
    the cached answer was not good enough, which is the same escalation the
    remote-ref view does: a cache older than the sibling's release must not be
    able to refuse a good push.
    """
    if not refresh and name in cache:
        return cache[name]
    versions = None if refresh else index_from_cache(name)
    if versions is None:
        versions = index_from_network(name)
    cache[name] = versions
    return versions


def publishable_index(docs, tree):
    """In-house crates that go to crates.io: name -> (version, manifest path).

    `publish = false` is the marker for everything that does not (quasi, shop,
    everycycle, wam), and it inherits from the workspace root the same way
    `version` does, so resolve it the same way rather than reading the member
    alone.
    """
    ws_publish = {}
    for p, doc in docs.items():
        if not doc:
            continue
        v = ((doc.get("workspace") or {}).get("package") or {}).get("publish")
        if isinstance(v, bool):
            ws_publish[os.path.dirname(p)] = v

    def publishes(manifest_path, pkg):
        v = pkg.get("publish")
        if isinstance(v, bool):
            return v
        if isinstance(v, list):
            return bool(v)
        if isinstance(v, dict) and v.get("workspace") is True:
            d = os.path.dirname(manifest_path)
            while d.startswith(tree):
                if d in ws_publish:
                    return ws_publish[d]
                parent = os.path.dirname(d)
                if parent == d:
                    break
                d = parent
        return True

    out = {}
    for name, (version, manifest) in crate_index(docs, tree).items():
        doc = docs.get(manifest) or {}
        pkg = doc.get("package")
        if isinstance(pkg, dict) and publishes(manifest, pkg):
            out[name] = (version, manifest)
    return out


def registry_requirements(docs, publishable):
    """Every requirement on an in-house crate taken from the registry.

    The pairing is the mirror image of `requirements()`: a `version` and NO git
    URL, naming a crate this tree both holds and publishes. A path dependency is
    excluded for the same reason it is everywhere else here -- what it resolves
    against is the file next to it, not a release.
    """
    for p, doc in docs.items():
        if not doc:
            continue
        for table in dep_tables(doc):
            for key, spec in table.items():
                req = None
                if isinstance(spec, str):
                    req = spec
                elif isinstance(spec, dict):
                    if spec.get("git") or spec.get("path"):
                        continue
                    if isinstance(spec.get("version"), str):
                        req = spec["version"]
                if req is None:
                    continue
                name = key
                if isinstance(spec, dict) and isinstance(spec.get("package"), str):
                    name = spec["package"]
                if name in publishable:
                    yield p, name, req


def analyze_registry(docs, tree):
    """Grade every registry requirement on an in-house crate against the index.

    The escalation is deliberate and is what keeps the happy path free: the
    local version is tried first, and the index is consulted only for a
    requirement the checkout is the one thing satisfying (or that nothing
    satisfies). Working ahead of a release is fine; requiring a version that
    exists in no published release is not, because no other machine can resolve
    it and `links` on makeover-layout makes that the whole graph at once.

    Returns (broken, graded, unchecked, ungraded).
    """
    publishable = publishable_index(docs, tree)
    index_cache = {}
    broken, graded, unchecked, ungraded = [], 0, 0, {}

    for consumer_manifest, name, req in registry_requirements(docs, publishable):
        local_version, provider_manifest = publishable[name]
        if satisfies(req, local_version) is None:
            unchecked += 1
            continue
        versions = published_versions(name, index_cache)
        if versions is None:
            ungraded.setdefault(name, "no index entry, cached or fetched")
            continue
        if not any(satisfies(req, v) for v in versions):
            # A cache older than the sibling's release would otherwise refuse a
            # good push, so pay for one fetch before calling it broken.
            versions = published_versions(name, index_cache, refresh=True) or versions
        graded += 1
        if not any(satisfies(req, v) for v in versions):
            newest = versions[0] if versions else "nothing published"
            broken.append(
                (consumer_manifest, name, req, newest, provider_manifest,
                 "the index")
            )
    return broken, graded, unchecked, ungraded


def analyze(docs, tree):
    """Grade every in-house git+version pair in `docs` against the tree on disk.

    Returns (broken, unchecked, absent, graded), where a broken entry is
    (consumer manifest, crate, requirement, version found, provider manifest,
    source label). The source label is None here: this view reads the version
    off a manifest, and naming the manifest already says where it came from.
    """
    versions = crate_index(docs, tree)
    broken, unchecked, absent, graded = [], 0, set(), 0
    for p, name, req in requirements(docs):
        known = versions.get(name)
        if known is None:
            # A repo that is not on this machine (ripgrow lives on mbp only).
            # Not a finding: nothing here can be wrong about it.
            absent.add(name)
            continue
        verdict = satisfies(req, known[0])
        if verdict is None:
            unchecked += 1
            continue
        graded += 1
        if not verdict:
            broken.append((p, name, req, known[0], known[1], None))
    return broken, unchecked, absent, graded


def split_blame(broken, repo, blame_provider=True):
    """Breaks this push owns, and breaks that were already there.

    `blame_provider` is off for the crates.io view: there, the provider side of
    a break is a crate that has been bumped and not released yet, and pushing it
    is how it reaches the index at all (Bento publishes from a pushed tag). Only
    the consumer side of that view is a push worth refusing.
    """
    ours, theirs = [], []
    for item in broken:
        consumer_manifest, _name, _req, _have, provider_manifest, _src = item
        mine = repo is not None and (
            consumer_manifest.startswith(repo + os.sep)
            or (blame_provider and provider_manifest.startswith(repo + os.sep))
        )
        (ours if mine else theirs).append(item)
    return ours, theirs


def report(broken, repo, tree, label, blame_provider=True):
    """Print one view's breaks. Returns True if this push has to be refused."""
    ours, theirs = split_blame(broken, repo, blame_provider)

    def rel(path):
        return os.path.relpath(path, tree)

    for consumer_manifest, name, req, have, provider_manifest, src in ours + theirs:
        where = (
            f"{src} has {have} ({rel(provider_manifest)})"
            if src
            else f"the tree has {have} ({rel(provider_manifest)})"
        )
        print(
            f"  [{label}] {rel(consumer_manifest)}: requires {name} \"{req}\", {where}",
            file=sys.stderr,
        )
    if repo is None:
        return bool(broken)
    if not ours:
        # Somebody else's skew. Worth seeing, never worth blocking this push on:
        # a gate that fails for a reason the pusher cannot fix is a gate that
        # gets bypassed by reflex, and then it is not a gate.
        if theirs:
            print(
                f"pre-push: [{label}] {len(theirs)} unresolvable requirements "
                "elsewhere in the tree (listed above, not this push's).",
            )
        return False
    return True


def main():
    if len(sys.argv) < 2:
        print(__doc__.strip(), file=sys.stderr)
        return 2
    tree = os.path.realpath(sys.argv[1])
    repo = os.path.realpath(sys.argv[2]) if len(sys.argv) > 2 else None
    sha = sys.argv[3] if len(sys.argv) > 3 else None

    docs = {p: load(p) for p in manifests(tree)}

    views = [("working copy", docs)]
    skipped_push_view = False
    if repo and sha:
        pushed = pushed_view(docs, repo, sha)
        if pushed is None:
            skipped_push_view = True
        else:
            views.append(("as pushed", pushed))

    refuse = False
    summaries = []
    for label, view in views:
        broken, unchecked, absent, graded = analyze(view, tree)
        summaries.append((label, graded, unchecked, absent, bool(broken)))
        if broken and report(broken, repo, tree, label):
            refuse = True

    # The third view: what the rest of the tree has actually PUSHED. Graded from
    # the most authoritative consumer view available, so the requirements read
    # are the ones about to be published.
    consumer_view = views[-1][1]
    pub_broken, pub_graded, ungraded = analyze_published(
        consumer_view, docs, tree, repo, sha
    )
    summaries.append(("as published", pub_graded, 0, set(), bool(pub_broken)))
    if pub_broken and report(pub_broken, repo, tree, "as published"):
        refuse = True

    # The fourth view: the siblings that are consumed from crates.io rather than
    # by git URL. Nothing above can see them, because the pairing every other
    # view makes is against the repo a git URL names.
    reg_broken, reg_graded, reg_unchecked, reg_ungraded = analyze_registry(
        consumer_view, tree
    )
    summaries.append(
        (REGISTRY_LABEL, reg_graded, reg_unchecked, set(), bool(reg_broken))
    )
    if reg_broken and report(
        reg_broken, repo, tree, REGISTRY_LABEL, blame_provider=False
    ):
        refuse = True

    if refuse:
        bad = {lbl for lbl, _g, _u, _a, broke in summaries if broke}
        clean = [lbl for lbl, _g, _u, _a, broke in summaries if not broke]
        print("", file=sys.stderr)
        print(
            "pre-push: this push leaves a dependency that cannot resolve.\n"
            "  A version requirement states which major a consumer was written against,\n"
            "  so bumping a library and fixing its consumers is one pass (CLAUDE.md,\n"
            "  \"a breaking bump of an in-house crate is forward-fixed, in the same pass\").\n"
            "  Fix: bump the requirement in the manifests above, make the consumers\n"
            "  compile, and push them with this one.",
            file=sys.stderr,
        )
        # Which view broke is the diagnosis, so say what the views disagree
        # about rather than only that they disagree.
        if REGISTRY_LABEL in bad:
            print(
                "  A requirement above resolves against no version on crates.io, so the\n"
                "  fix is to PUBLISH the sibling, not to push it. Until it is on the\n"
                "  index no machine can resolve the graph, this one included once the\n"
                "  [patch.crates-io] block is out of the way.",
                file=sys.stderr,
            )
        if "as published" in bad and "working copy" not in bad:
            print(
                "  The working copy is fine and the published tree is not, so the\n"
                "  difference is what has been PUSHED: a git dependency resolves against\n"
                "  the branch head at the URL, and ~/Code/.cargo/config.toml's [patch]\n"
                "  block hides that locally. Push the sibling named above first.",
                file=sys.stderr,
            )
        elif "as pushed" in bad and "working copy" not in bad:
            print(
                "  The working copy is fine and the commit is not, so the difference is\n"
                "  what is COMMITTED. An uncommitted manifest edit is the usual cause.",
                file=sys.stderr,
            )
        elif clean:
            print(
                f"  Note: the {clean[0]} view is clean, so the views disagree; the one\n"
                "  that failed is named on each line above.",
                file=sys.stderr,
            )
        return 1

    for label, graded, unchecked, absent, bad in summaries:
        # A view that found a break somewhere else in the tree is not a view
        # that passed. Reaching here means the break is not this push's to fix,
        # which is a reason not to refuse and never a reason to print coherent.
        verdict = "internal deps coherent" if not bad else "internal deps BROKEN elsewhere"
        print(
            f"pre-push: {verdict} [{label}] ({graded} requirements"
            + (f", {unchecked} unchecked" if unchecked else "")
            + (f", {len(absent)} crates not in this tree" if absent else "")
            + ")."
        )
    for where, why in sorted(ungraded.items()):
        # Never silently: a repo nobody could read is not a repo that passed.
        print(
            f"pre-push: [as published] {os.path.relpath(where, tree)} not graded ({why})."
        )
    for name, why in sorted(reg_ungraded.items()):
        print(f"pre-push: [{REGISTRY_LABEL}] {name} not graded ({why}).")
    if skipped_push_view:
        # Never silently: a view that did not run must not read as one that passed.
        print("pre-push: could not read the pushed commit; graded the working copy only.")
    return 0


if __name__ == "__main__":
    sys.exit(main())