ci: publish this build's own coverage and benchmarks Serving a revision is two passes over the whole corpus — FromDocuments builds the archive, LinkPass renders every document to fill the link graph — and a proposal view is a third over two whole revisions. All three grow with the space rather than with the request, and this repository had no Benchmark at all; doc/ and prosediff/ now measure them over a synthetic 208-document space and a 24-chapter specification carrying one of every change kind. The manifest gains a coverage and a bench task, both after publish so a rejected upload cannot cost a shipped apk, and both guarded: the profile must be non-empty, the benchmark names must be in the file (an empty benchfmt body uploads and reports success), and a build handed no token says so and exits 0 with the artifact still attached.
refactor(doc): one route from a revision to an Archive (spec-wcr #2, #4) doc.Scan/DocumentSource were production-dead after service.Archive landed — their only consumer was doc's own test fixture — and they were the seam that made web's layering violation writable: a surface could reach past service/ into gitx and build its own archive. Deleted, so doc/ now owns no way to read a revision and service.Archive is the single route from rev to Archive. The fixture reads through ListDocuments + FromDocuments instead. TestScanReportsGitErrors covered an unknown revision failing rather than walking empty, which gitx did not test itself. Relocated there as TestWalkOfAnUnknownRevisionFails rather than dropped. linkHierarchy passed path.Dir(p.Path) where every other call site passes DirOf. Unobservable today: the two differ only at the space root, and there the section-proximity step is subsumed by the same-directory step above it, so "." only ever skipped a lookup that had already answered. Verified by reverting and re-running. Changed anyway — it stays unobservable only by coincidence of two ranking rules — with a test pinning the invariant that `parent:` resolves to whatever the same wikilink in the body resolves to. spec-wcr
feat: service.Archive — one accessor, one tree walk, one link graph web/ and mcpsrv/ both needed a *doc.Archive and each invented its own way to get one. web/ handed sp.Repo to doc.Scan — reaching past service/ into gitx, which the layering rule forbids — and then listed the documents a second time for their bodies, two tree walks per page view. mcpsrv/ converted []service.Document back into []gitx.Document, rebuilding hashes service/ had already stringified. Two workarounds around one missing accessor is how three agent-facing surfaces stop being identical. Service.Archive resolves the revision, walks the tree once, and returns the archive with the bodies. ArchiveFrom is the same construction over a Document set a caller already holds — the one place a hex object name is converted back to a hash, malformed ids refused rather than zeroed. doc.Page.Links and Page.WordCount were documented as "filled in by a render pass" and nothing filled them, so Archive.Backlinks always returned empty; web/ worked around that by re-rendering every document of the space on each page view. doc.Archive.LinkPass now owns the pass and the accessor runs it, so the link graph exists wherever the archive does. web/ and mcpsrv/ no longer import gitx or go-git, in tests either. doc.DirOf replaces the three copies of the same fromDir helper.
feat: doc — warren's vault+render absorbed onto the git-object read path warren scanned a directory (filepath.WalkDir + os.ReadFile). There is no checkout here, so Scan walks a git tree through gitx instead and feeds the FromPages seam: the approved head, a pinned sha and a proposal branch are one code path with a different revision, and nothing downstream of Archive knows where its pages came from. Frontmatter is core's, not warren's. Front embeds core.Frontmatter and adds only what core deliberately does not model — parent, aliases, planned, and the ordered key list used for display and search text. Two parsers that disagree about a document header is a bug that surfaces in the ID registry months later. Documents key on their frontmatter id, falling back to their path: paths move and ids do not, and a duplicated id resolves to neither document rather than letting one win silently, matching what the merge already does with the approved branch. A header core rejects degrades to "no frontmatter" instead of failing, because --push-option=skip-validation means such a document can exist and refusing to render it would turn a typo into an outage.