Skip to content

feat(repo): ship static repository showcase - #1791

Merged
ohdearquant merged 8 commits into
mainfrom
codex/repo-showcase
Aug 8, 2026
Merged

feat(repo): ship static repository showcase#1791
ohdearquant merged 8 commits into
mainfrom
codex/repo-showcase

Conversation

@ohdearquant

Copy link
Copy Markdown
Owner

Summary

  • implement ADR-147 as a closed khive.repo.v1 Rust model, read-only two-store exporter, generated JSON Schema, and canonical khive golden vector
  • add khive repo build and khive repo export; build performs bounded public clone resolution, exhaustive git.digest, Rust-only code.ingest, revision/provenance checks, derived commit-to-module linkage, and atomic export
  • add the static-first Next.js showcase at /, preserve the KG review workbench at /review, and render all ten D3 module/repository views from the precomputed bundle
  • add fail-closed URL, tracked-path, symlink, containment, file-size, cursor, coverage, and availability handling plus Linux Chromium dogfood coverage in CI

Golden and reproduction

scripts/generate-repo-showcase.sh

The command pins c2979d2443738a075e55a170c772d1dc86cf0f91, omits mutable forge/tag observations, regenerates the schema, and synchronizes the exact browser asset.

  • schema: 77,232 bytes, SHA-256 8afa2c23668e632808a70955d42139488d9d5d467134358b2a4afe2c41843b4c
  • golden/browser asset: 5,884,135 bytes, SHA-256 a243828287337d2dab2da5529fc9b28882d3b7794204cd5e7f4184c59f4d6d11
  • two independent fresh-store runs produced byte-identical output

Join coverage

Repository Language Files Derived keys Entity keys Matched Resolution
ohdearquant/khive Rust 658 658 658 658 100.0%

The historical join records 7,558 changed-path events: 4,344 Rust events in scope, 4,309 matched, 3,214 non-Rust events out of scope, and 35 deleted/renamed/unscanned Rust events retained as named residuals.

View coverage and honest deferrals

All ten D3 views are implemented at their declared module/repository granularity: structure graph, history-structure navigation, dependency topology, hotspot quadrant, hidden coupling, structure treemap, cadence timeline, ownership, de-facto API surface, and scorecard.

Symbol drill-down, symbol sizing, symbol ranking, and symbol count remain explicitly unavailable because the current ingest emits no function/datatype/interface nodes. Pull-request/issue-to-module facets remain unavailable without an explicit commit evidence chain. The reproducible golden requests commits only, so each forge cadence series carries its own unavailable disclosure rather than invented zeroes.

Verification

  • cargo check --workspace --all-targets
  • warning-denied Clippy for the exporter and kkernel slice
  • repository format, SQL, and ADR-reference lint
  • exporter/contract tests: 22 passed
  • CLI/security E2E tests: 9 passed
  • frontend Ajv/Zod/unit/interaction tests: 54 passed
  • ESLint, TypeScript, static-asset parity, and Next.js production build
  • three production-server Chromium scenarios wired into the Linux CI job

@ohdearquant

Copy link
Copy Markdown
Owner Author

Read through the bundle, the exporter, the renderer, and the shipped golden. The unavailable-vs-zero discipline in here is the best part of the design: every collection that was not collected says so with a specific reason, and CadenceSeries branches on disclosure.status === "unavailable" before it branches on rows.length === 0, so "not collected" and "counted zero" render differently. D5-JOIN-FEASIBILITY.md refusing to assign TypeScript and Python a zero rate, rather than reporting one, is the same discipline applied to a number that would have been easy to fabricate.

Two places where that discipline is not carried through. Both are small.

1. The symbol tier encodes "deferred" as complete with a count of 0

SymbolPage::empty() (crates/khive-repo-showcase/src/model.rs:427) builds functions, datatypes, and interfaces with:

total_count: Availability::available(0),
disclosure: Disclosure {
    status: DisclosureStatus::Complete,
    reason: Some("symbol-tier ingest is deferred in khive.repo.v1".into()),
},

Complete plus an available 0 is a positive claim that the count was taken and the answer is zero. The repository has 658 Rust modules, so the true statement is that the tier was not ingested, which is what the reason string says. The two fields disagree.

The same fact is encoded the other way one file over. crates/khive-repo-showcase/src/aggregate.rs:882 uses Availability::unavailable("symbol-tier ingest is deferred") for the aggregate-side counterpart. So this is not a judgement call about which encoding is right; the codebase already contains both, and one of them has to change.

Consequence at the reader, since that is the part that matters. In BoundDisclosure, total_count.status === "available" renders the formatted number, and the Unavailable · prefix is gated on disclosure.status === "unavailable". In the table renderer, the unavailableReason branch is likewise gated on unavailable, and the fallback is <strong>0</strong>. So these three collections render a bold 0 where issues, pull requests, history edges, release tags, and lead time all render Unavailable · <reason>. The deferral text still reaches the page through BoundDisclosure, so nothing is silent, but the primary numeral asserts something false.

It also affects consumers other than this UI. docs/schemas/khive-repo-v1.schema.json is published, so total_count: {status: "available", value: 0} is the contract any other reader gets. The zod validator at apps/kg-editor/src/lib/repo-bundle.ts:492 pins items.length === 0 for these three, which fixes the shape without touching the availability claim.

Marking the three unavailable with the reason they already carry makes them match their siblings and costs nothing else.

2. Byte parity between the two goldens is claimed but not checked

scripts/check-json-data.sh grants the 8 MiB exception with this justification:

the KG Studio contract job validates both JSON shape and byte parity

Shape, yes. Byte parity, no.

sync-showcase-assets.mjs has exactly the right mechanism: --check reads both files and throws unless actual.equals(expected). package.json exposes it as check:showcase. Nothing invokes it. The only two call sites in the tree are sync:showcase (write mode, wired to predev and prebuild) and scripts/generate-repo-showcase.sh:35 (write mode).

So the KG Studio job runs npm run build, prebuild fires sync:showcase, and the script overwrites public/showcase/khive-repo-v1-khive.json with the canonical copy in the runner. If the committed browser asset had drifted from the canonical golden, CI would repair it in the workspace, build green, and leave the drift in the repository. pretest:e2e is also npm run build, so the Playwright dogfood exercises the repaired copy rather than the committed one, which means the new e2e step cannot catch it either.

One step in the KG Studio job, placed before the build so it reads the committed bytes:

- name: Showcase asset parity
  run: npm run check:showcase

Worth fixing rather than rewording the comment, because the comment is load-bearing: it is the stated reason a 5.7 MB file is exempt from a 256 KB ceiling, and the next person to widen that exception will read it as established fact.

Two notes, not objections

Repository growth. The two goldens are byte-identical (same blob, 5,884,135 bytes each) and both are committed, so this adds 11.77 MB to permanent history, and each future regeneration adds roughly that again. Since prebuild regenerates the browser copy deterministically, committing it is a choice that buys the ability to serve public/ without a build. Reasonable either way, but the cost compounds and git history cannot be trimmed later.

Commit subjects are now a publication surface twice. The bundle exports commit subjects verbatim into a shipped asset that is also rendered in a browser. Everything in the current golden is already in the public log, so nothing new is exposed. It does mean commit-message hygiene now feeds a second published artifact on every regeneration.

The JSON/JSONL data-leak guard name is worth keeping in mind alongside that: the check is a size and path-shape ceiling, not a content check. That was fine when the largest JSON in the tree was a config file. This PR is the first thing to ship megabytes of exported repository data through it, which is the point at which the gap between the name and the mechanism starts to matter.

@ohdearquant

Copy link
Copy Markdown
Owner Author

Two additions to the above, both of which change what fixing the symbol-tier item costs. Neither is a new objection; they are the parts I had wrong about scope.

The contract test ratifies both encodings, side by side

crates/khive-repo-showcase/tests/export_contract.rs:265 is named symbol_pages_and_unavailable_issue_facets_are_explicit. It exists to assert that the bundle is explicit about what it does and does not have. Its body asserts both encodings of "we do not have this" within nine lines of each other:

assert_eq!(bundle.graph.functions.disclosure.status, DisclosureStatus::Complete);
assert!(matches!(bundle.graph.issues.total_count, Availability::Unavailable { .. }));

So the test does not miss the inconsistency, it pins it. That is worth more than the original finding: the test's own name is the argument for which direction to resolve it. Whatever else changes, Complete for a tier the same struct documents as deferred cannot be what "explicit" means when the line below it uses Unavailable for exactly the same situation.

Changing the exporter silently staleness the golden, because nothing compares them

scripts/generate-repo-showcase.sh reproduces the bundle deterministically: pinned source revision, pinned --generated-at, --tags none, explicit --default-branch. That is a good script. It is not invoked by CI, and no test regenerates the bundle and diffs it against the committed copy.

That is defensible on its own. Regenerating a 5.7 MB bundle per PR means a network clone and a full kkernel build, which is a lot to spend to catch a rare drift. The consequence is what matters here: disclosure.status accepts all three variants, so a golden carrying complete would still validate against the schema and still pass the zod check after the exporter started emitting unavailable. The committed bundle would contradict the code that generates it and every check would stay green.

So the symbol-tier fix is three parts, not one:

  1. SymbolPage::empty() in crates/khive-repo-showcase/src/model.rs:427Availability::unavailable(...) and DisclosureStatus::Unavailable, matching aggregate.rs:882.
  2. The contract test, flipped to assert Unavailable for the symbol pages.
  3. A regeneration run via scripts/generate-repo-showcase.sh, so the committed golden and its browser copy carry the corrected encoding.

Step 3 is the one worth being firm about. Hand-editing three fields inside a minified 5.7 MB artifact would produce a bundle that the generator does not reproduce, which is a worse version of the drift this PR's own guard comment claims to prevent.

Which makes the three validation claims worth stating together

Reading the tree, this PR asserts three things are verified. One is:

  • shape — verified, by the zod schema in repo-bundle.ts under vitest and the e2e;
  • byte parity between the two copies — the check exists (check:showcase) and nothing invokes it;
  • the golden matches what the generator produces — the script exists and nothing invokes it.

The first two are worth closing because the mechanisms are already written and the wiring is one line each. The third is a reasonable thing to leave to a manual step, as long as nothing claims otherwise in a comment.

`check:showcase` existed and nothing invoked it. The only two call sites of
sync-showcase-assets.mjs were write mode: `prebuild` and the regeneration
script. So CI ran the build, `prebuild` fired the writer, and the committed
browser asset was overwritten in the runner. Drift between the two goldens was
repaired in the workspace, the build went green, and the drift stayed in the
repository. `pretest:e2e` is the same build, so the Playwright run could not
catch it either.

Placement is the load-bearing part: a check placed after the build reads bytes
the build just rewrote and passes unconditionally. A gate that runs after the
self-repair is the same defect wearing a fix's clothes.

This also makes the size-guard exception in scripts/check-json-data.sh true as
written. That comment cites CI validation of byte parity as the reason a 5.7 MB
file is exempt from the 256 KB ceiling, and it is the text the next person
widening that exception will read.
… zero

SymbolPage::empty() built functions, datatypes and interfaces with
total_count: available(0) and disclosure: complete, while carrying a reason
string stating the tier was never ingested. Complete plus an available zero is
a positive claim that the count was taken. The repository has 658 Rust modules.

The renderer branches on disclosure.status == unavailable before it branches on
an empty row set, so these three displayed a confident 0 where issues, pull
requests, release tags and lead time all displayed their unavailable reason.
The published schema carried the same claim to every other consumer.

aggregate.rs already encoded the identical fact as Availability::unavailable,
and the suite already contained a test asserting that zero-history ownership
metrics are unavailable rather than zero, so the convention was established and
the symbol tier was the outlier.

The contract test asserted disclosure.status on one page and total_count on
another, which is how the pairing survived a test named for explicitness. It
now asserts both fields across all three pages. The reason string is bound once
and shared, so the two fields cannot disagree.

Golden regenerated through scripts/generate-repo-showcase.sh; the schema
regenerated byte-identical, so the contract shape is unchanged.
…spatch

moduleName() ran a linear find over all module items on every call, and the
showcase views call it for hundreds of rows per render. Build the id-to-module
map once per bundle with useMemo and pass it down, making each lookup O(1).
Replace the ActiveView if-else chain with a component lookup map keyed by view
id, matching the existing viewOrder/viewIcons structure.
@ohdearquant
ohdearquant merged commit 9938632 into main Aug 8, 2026
27 checks passed
@ohdearquant
ohdearquant deleted the codex/repo-showcase branch August 8, 2026 00:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant