Skip to content

Commit a190359

Browse files
committed
Merge branch 'stroland02/m1-static-gate'
# Conflicts: # tests/test_decode_handlers.py
2 parents 8229432 + 10ad6ee commit a190359

8 files changed

Lines changed: 795 additions & 60 deletions

File tree

docs/superpowers/reports/2026-07-29-directory-skips-recorded.md

Lines changed: 290 additions & 0 deletions
Large diffs are not rendered by default.

src/sync/cli.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1574,9 +1574,9 @@ def intake(args: argparse.Namespace) -> int:
15741574
# The directory is a document somebody fetched, parsed here rather than downloaded: this
15751575
# command reports what the deployment already knows, and a fetch inside it would make a
15761576
# report of what is on disk quietly online.
1577-
directory = (
1577+
directory, directory_unreadable = (
15781578
parse_directory(json.loads(Path(args.registry_directory).read_text(encoding="utf-8")))
1579-
if args.registry_directory else []
1579+
if args.registry_directory else ([], ())
15801580
)
15811581
registry_apis = (
15821582
read_registry_apis(Path(args.registry_evidence)) if args.registry_evidence else {}
@@ -1585,6 +1585,7 @@ def intake(args: argparse.Namespace) -> int:
15851585
Path(args.repo),
15861586
generator_manifests=evidence,
15871587
registry_entries=directory,
1588+
registry_unreadable=directory_unreadable,
15881589
registry_apis=registry_apis,
15891590
registry_moved_since=args.registry_moved_since,
15901591
)
@@ -1609,8 +1610,10 @@ def intake(args: argparse.Namespace) -> int:
16091610

16101611
for problem in report.unreadable:
16111612
# To stderr, and never silently. A manifest that would not parse is not a repository
1612-
# with no dependencies, and reported as one it reads as a clean scan of an empty project.
1613-
print(f"unreadable manifest: {problem}", file=sys.stderr)
1613+
# with no dependencies, and reported as one it reads as a clean scan of an empty project;
1614+
# a declined catalogue entry is the same narrowing arriving from a different file. The
1615+
# prefix names neither, because each problem already names its own source.
1616+
print(f"unreadable: {problem}", file=sys.stderr)
16141617
return 0
16151618

16161619

src/sync/signals/intake.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,13 @@ class IntakeReport:
158158
`unreadable` is not an error channel. A manifest that does not parse is a fact a customer
159159
needs, because a repository whose manifest is unreadable is not a repository with no
160160
dependencies -- and reported as the latter it reads as a clean scan of an empty project.
161+
162+
It holds every input that would not read, not only the manifests. The vendor catalogue is one
163+
of them: a directory entry `parse_directory` declined is a package this report will call
164+
not-watchable for a reason that has nothing to do with the package, which is the same narrowing
165+
under a different name. They share the key rather than getting one each -- the three manifest
166+
readers already share it and tell themselves apart by the prefix on each string, so a reader
167+
needs one rule for this fact and not one per input.
161168
"""
162169

163170
assessments: tuple[Assessment, ...]
@@ -451,6 +458,7 @@ def assess_repository(
451458
bindings: Mapping[str, Mapping[str, Mapping[str, str]]] | None = None,
452459
configured_repos: Mapping[str, str] | None = None,
453460
registry_entries: Sequence[RegistryEntry] = (),
461+
registry_unreadable: Sequence[str] = (),
454462
registry_apis: Mapping[str, str] | None = None,
455463
registry_moved_since: str | None = None,
456464
) -> IntakeReport:
@@ -460,6 +468,12 @@ def assess_repository(
460468
actually has. That is what keeps the classification testable against committed fixtures and
461469
keeps the network out: `generator_manifests` is evidence somebody gathered, not something
462470
this function goes and looks up.
471+
472+
`registry_unreadable` is the other half of what `parse_directory` returns, and it arrives with
473+
the entries because a report built from a catalogue that partly would not read has to say so.
474+
It defaults empty beside `registry_entries` rather than being required: a deployment that
475+
passed no directory has no directory faults, so empty is the truth there rather than a silent
476+
claim.
463477
"""
464478
declared, unreadable = read_declared_dependencies(Path(root))
465479
resolved_bindings = vendor_sdk_bindings() if bindings is None else bindings
@@ -474,4 +488,6 @@ def assess_repository(
474488
_classify(dependency, packages[dependency.ecosystem], resolved_repos, evidence, registry)
475489
for dependency in declared
476490
)
477-
return IntakeReport(assessments=assessments, unreadable=unreadable)
491+
return IntakeReport(
492+
assessments=assessments, unreadable=(*unreadable, *registry_unreadable)
493+
)

src/sync/signals/registry_tier/directory.py

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,28 +65,76 @@ class RegistryEntry:
6565
versions: dict[str, RegistryVersion]
6666

6767

68-
def parse_directory(document: dict[str, Any]) -> list[RegistryEntry]:
69-
"""Every usable entry in a directory document.
68+
# The source every skip is attributed to, in the position `sync.signals.intake` puts a manifest's
69+
# filename. One `unreadable` list holds faults from several inputs, so each string has to say
70+
# which one it came from before it says what was wrong with it.
71+
_SOURCE = "registry directory"
72+
73+
74+
def _timestamp(detail: dict[str, Any]) -> str | None:
75+
"""When this version last moved, preferring `updated` and falling back on `added`.
76+
77+
The fallback triggers on a value this tier cannot use rather than on a falsy one. A numeric
78+
`updated` is truthy, so `detail.get("updated") or detail.get("added")` let it win and then
79+
failed the string check, skipping a version whose `added` was perfectly readable. Usable means
80+
a non-empty string: `versions_after` compares timestamps as strings and `""` compares less
81+
than every real one, so an empty value admitted here would be reported as a version that
82+
never moves.
83+
"""
84+
for key in ("updated", "added"):
85+
value = detail.get(key)
86+
if isinstance(value, str) and value:
87+
return value
88+
return None
89+
90+
91+
def parse_directory(document: dict[str, Any]) -> tuple[list[RegistryEntry], tuple[str, ...]]:
92+
"""Every usable entry in a directory document, and every entry or version it declined.
7093
7194
A public directory is untrusted input and it is large. A malformed entry is skipped rather
7295
than raised on: one bad row must not cost the other thousands, and an entry with no versions
7396
has nothing to say about what changed, so skipping loses nothing that could have been used.
97+
98+
What it does lose is the vendor. A skipped entry is one Sync will never offer to watch, and
99+
reported as an absence it is indistinguishable from a directory that never listed it -- so the
100+
second half of the return says what was declined and why, in the key and shape
101+
`IntakeReport.unreadable` established and `ReachabilityRanking` already carries. Empty rather
102+
than absent on a clean document, because a caller cannot otherwise tell a clean read from a
103+
parser that never recorded a fault.
104+
105+
An entry that kept none of its versions is recorded too, and is deliberately not a fifth
106+
cause: it follows from the version-scoped skips already recorded above it. It is there because
107+
it is the only record that says the vendor is gone rather than one of its versions.
74108
"""
75109
entries: list[RegistryEntry] = []
110+
unreadable: list[str] = []
76111
for api_id, body in document.items():
77112
if not isinstance(body, dict):
113+
unreadable.append(f"{_SOURCE}: '{api_id}' is not an object")
78114
continue
79115
raw_versions = body.get("versions")
80116
if not isinstance(raw_versions, dict) or not raw_versions:
117+
unreadable.append(f"{_SOURCE}: '{api_id}' declares no versions object")
81118
continue
82119

83120
versions: dict[str, RegistryVersion] = {}
84121
for version, detail in raw_versions.items():
85122
if not isinstance(detail, dict):
123+
unreadable.append(f"{_SOURCE}: '{api_id}' version '{version}' is not an object")
86124
continue
87125
spec_url = detail.get("swaggerUrl")
88-
updated = detail.get("updated") or detail.get("added")
89-
if not isinstance(spec_url, str) or not isinstance(updated, str):
126+
if not isinstance(spec_url, str):
127+
unreadable.append(
128+
f"{_SOURCE}: '{api_id}' version '{version}' declares no swaggerUrl string, "
129+
f"so there is nothing to download"
130+
)
131+
continue
132+
updated = _timestamp(detail)
133+
if updated is None:
134+
unreadable.append(
135+
f"{_SOURCE}: '{api_id}' version '{version}' declares no usable updated or "
136+
f"added timestamp, so nothing can compare it against a watermark"
137+
)
90138
continue
91139
versions[version] = RegistryVersion(
92140
version=version,
@@ -96,6 +144,10 @@ def parse_directory(document: dict[str, Any]) -> list[RegistryEntry]:
96144
)
97145

98146
if not versions:
147+
unreadable.append(
148+
f"{_SOURCE}: '{api_id}' is not discoverable -- none of the "
149+
f"{len(raw_versions)} version(s) it declares could be read, each recorded above"
150+
)
99151
continue
100152
preferred = body.get("preferred")
101153
entries.append(
@@ -105,7 +157,7 @@ def parse_directory(document: dict[str, Any]) -> list[RegistryEntry]:
105157
versions=versions,
106158
)
107159
)
108-
return entries
160+
return entries, tuple(unreadable)
109161

110162

111163
def versions_after(entries: list[RegistryEntry], watermark: str) -> list[tuple[RegistryEntry, str]]:

tests/test_cli_wiring_reachability.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,9 @@ def test_intake_ranked_puts_an_unreadable_manifest_in_the_artifact_and_not_only_
246246
payload = json.loads(captured.out)
247247
assert payload["rows"] == []
248248
assert any("package.json" in problem for problem in payload["unreadable"])
249-
assert "unreadable manifest:" in captured.err
249+
# The prefix names no source, because the list holds faults from several and each string
250+
# already names its own -- `package.json` here, a declined catalogue entry elsewhere.
251+
assert "unreadable: package.json" in captured.err
250252

251253

252254
# --- sync benchmark scores --------------------------------------------------------------

tests/test_decode_handlers.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -421,9 +421,9 @@ def _drive_ts_manifest(root: Path) -> None:
421421
"sync/remediate/property_omit.py:93": _drive_property_omit,
422422
"sync/remediate/tiered.py:174": _drive_tiered_literal,
423423
"sync/signals/feed/consumer.py:72": _drive_feed,
424-
"sync/signals/intake.py:288": _drive_intake_npm,
425-
"sync/signals/intake.py:324": _drive_intake_pyproject,
426-
"sync/signals/intake.py:335": _drive_intake_requirements,
424+
"sync/signals/intake.py:295": _drive_intake_npm,
425+
"sync/signals/intake.py:331": _drive_intake_pyproject,
426+
"sync/signals/intake.py:342": _drive_intake_requirements,
427427
}
428428

429429

0 commit comments

Comments
 (0)