docs(specs): align S17 phase 8 statistics with the design

Why

@rchanila audited the design prototype against the eight statistics endpoints S17 Phase 8 specs, and reported the result on S17 Phase 8: statistics (#316) • Hayley Swimelar. Two findings change the phase:

  • No screen calls a per-item size endpoint. Four screens work off the namespace, repository, and version scopes. The image, container tag, package, and npm dist-tag scopes have no caller: the tag scopes return what the row beside them already carries, and the image page has no summary panel, so its counts belong one level up.
  • Every artifact table in the design renders a size and a child count on each row. A per-item endpoint answers one row per request, and the spec treated this as a sorting problem. Showing the number at all needs the same data.

Left alone, the phase ships eight endpoints, four of them unused, and the screens the design actually draws stay unserved.

What

The parts a reviewer would not get from the diff:

  • The scope decision. The four unused scopes are withdrawn. This deviates from ADR-009's route list, so the ADR-009 amendment item now carries the withdrawal, flagged as a scope reversal that owes an escalation rather than a spelling fix.
  • The version scope survives on files_count, which the Files tab renders and no list carries. Its size_bytes duplicates the version resource's stored size, which is the reason the tag scopes were withdrawn, so the spec now states the one difference that keeps it: it recomputes, so it is exact while the stored column's maintenance window is open. If that does not earn its query, the field drops and the scope keeps the count.
  • It collides with #551 (closed), merged into main on 2026-08-20. !1730 (closed) assigned the image-list manifest count and size to the image statistics scope this MR withdraws, naming two triggers that would reopen the decision. The audit fires a case neither trigger anticipated: the prototype renders both as columns, without sorting by either, and a column needs the same per-row data a sort does. The follow-up records that rather than overwriting it, and splits the two figures, because a per-row count is a batched read while a per-row size is a blob walk with no page-bounded form.
  • Three claims the research corrected. maven_versions.size_bytes has landed, so the versions Size column already works and only its keyset sort is blocked. container_images, maven_packages, and npm_packages all carry created_at, so the Published column is a resource decision, not schema work. The two version size indexes stop at size_bytes DESC with no id tiebreaker, which every repositories keyset index over a non-unique column carries, so that is the sort's real blocker.
  • Downloads and the parent/child direction are the audit's other two follow-ups. Downloads is widened to record that the design draws it on six screens with one column behind it. The container-redesign entry asked for the parent digests a child sits under; the prototype renders the opposite, an index's children.
  • The plan is revised in the same MR because no Phase 8 step has opened an MR. Steps 12-18 and 21 are removed, numbering stays gapped, and the acceptance-criteria citations are remapped against the renumbered list.
  • Phase 8's last external gate has closed, and the plan said otherwise. The plan recorded S22 plan Steps 2a and 2b as unopened, gating Step 11 and through it the two version steps. Both have merged since, 2a as !1510 (merged) on 2026-08-17 and 2b as !1652 (merged) on 2026-08-19, so blob_storage_blobs_by_namespace, its triggers and seed, and its store are all on main. Every external gate this plan named is now closed and all four roots are open.

Diff size: 359 added lines across two files, 549 removed. The removals are the eight plan step bodies; the spec side is 174 changed lines. Splitting spec from plan would leave a merged plan describing endpoints its own spec no longer declares.

Test plan

  • mise run lint:docs passes: markdownlint 0 errors, lychee 0 errors with fragments checked, Vale at its existing warning baseline.
  • The withdrawn routes are asserted, not just deleted: AC #118 (closed) requires that all four answer the bare mux 404 and that the OpenAPI document declares no operation for any of them. Step 3's test list carries it.
  • Acceptance-criteria renumbering verified by extracting the auto-numbered list from origin/main and from this branch and diffing: 123 to 121, unchanged through #115 (closed), with every plan citation past that point remapped and re-checked against what the criterion it lands on actually says.
  • Two independent falsification passes ran over the diff, and their findings are applied. Between them they caught an inverted ADR-007 cost comparison, four over-broad quantifiers, a contradiction between two acceptance criteria, and the stale S22 gate above. Every schema claim in the new text was checked against internal/datastore/migrations/sql/ rather than against the spec's own prose.

Related to #316

Context for LLM agents

Design rationale and rejected alternatives

  • Keeping all eight routes for ADR-009 conformance was rejected. It is the position the superseded resolution took, and it held on its own terms: the scopes carry no speculative content. The design check falsified the premise underneath it, that a complete surface is a used one. Each unused route costs a reader, a handler, a contract entry, and a test suite.
  • Dropping the figures rather than relocating them was rejected. The design renders them, so the obligation survives the route.
  • Serving the per-row figures by read-time derivation was rejected on ADR-007's own measurement: it precomputes the version size_bytes column precisely because deriving per row of a list does not scale, while deriving one version is a bounded few-file join.
  • Specifying the missing list columns now, as nullable fields, was rejected under the existing resolution on per-artifact download counts: a field specced ahead of its column fixes another spec's schema by implication and reads null indefinitely.

Non-goals

  • This MR adds no list fields. Every gap it records waits on a column or a resource decision owned by a format slice, and the table names which.
  • It does not open the handbook ADR-009 amendment MR. The item is recorded with its escalation; the amendment is a separate change against the handbook repo.
  • It does not spec the S18 download counts. S18: per-artifact download counts the design re... (#757) • Unassigned carries that requirement into S18, and this MR only records the gap and points at it.
  • It does not relitigate #551 (closed). The decision stands as merged; this records the case that reopens it and leaves the call with its owner.
Edited by Hayley Swimelar

Merge request reports

Loading
Loading