refactor: move compile-check's records into a resultstore package

Merged
HA
1d ago
+696-321

Summary

  • compile-check's records now live in their own package, resultstore. Everything else in compile-check reaches them only through one store type, and the compiler enforces that.
  • No behaviour changes: the output JSON, the fallback reasons and their order, and the names and bytes of every record are the same.

User Impact

  • None directly. The next change, reusing the changed-files check's result, adds a fourth record. With this package it is one more file in the store plus a load and a record method, and Forget stays the single place that removes every record.

Changes

  • The store's API:
    func Open[D any](projectRoot, outputDir string) Store[D]
    func (s Store[D]) Reusable(assembly, key string) (CompileResult[D], bool)
    func (s Store[D]) RecordCompile(assembly, key string, result CompileResult[D], outputs []string) error
    func (s Store[D]) LoadBaseline(assembly string) (Baseline[D], string)
    func (s Store[D]) GeneratorOutputUnchanged(baseline Baseline[D]) bool
    func (s Store[D]) RecordBaseline(assembly string, inputs BaselineInputs) error
    func (s Store[D]) GeneratedDirectory(assembly string) string
    func (s Store[D]) Forget(assembly string) error
    
    The record formats, file names, version checks, atomic writes and hashing are unexported. The package comment states the contract: records are written through a temporary file, a baseline is handed out only while its compile result is still on disk, Forget removes every kind of record, and the reasons LoadBaseline gives.
  • Why the diagnostic type is a type parameter D:
    • The store never reads a diagnostic, and the type parameter lets the compiler guarantee that.
    • Diagnostics are not copied field by field at the boundary.
    • No extra package is needed for a single type.
    • compile-check uses one instantiation, resultstore.Open[Diagnostic].
  • The non-source key crosses the boundary as resultstore.KeyDigest. It has the same fields as compile-check's own nonSourceIdentity, so the two convert into each other with a plain type conversion and nothing is copied. compile-check keeps owning how the key is computed.
  • The store keeps private copies of hashFile (12 lines) and the project-relative path helper rather than sharing them through another package. resultstore imports only the standard library.
  • The store's own tests use a stand-in diagnostic type with the same field order, so the golden record bytes are unchanged. A golden test in compile-check pins the JSON form of the real diagnostic type, because the store records diagnostics exactly as it is given them. Swapping two of its fields, or adding a JSON tag, fails that test.
  • New tests at the store's boundary (separate commit). Coverage is measured per package, so the baseline paths that compile-check's tests used to exercise would otherwise count for nothing:
    • a recorded baseline comes back as recorded;
    • a changed, removed or added generated file stops the generator output matching;
    • a compile result that is gone, unreadable, of another key, failed, or whose output changed stops the baseline being handed out;
    • each reason a baseline is missing;
    • a failed baseline write.

Verification

  • go list -deps ./internal/compilecheck/resultstore | grep -c 'internal/compilecheck$': 0. Its direct imports are all standard library.
  • git grep -P over cli/dispatcher/internal/compilecheck/*.go (not descending into subpackages) for record internals (struct names, constants, paths, internal functions, record file names): no matches.
  • No expected reason, byte or count changed in the moved tests. The tests outside the store only switched to the public names.
  • Mutation checks, each restored afterwards:
    • swapping two fields of either record struct fails its golden test;
    • swapping the fields of KeyDigest stops the type conversion compiling and fails the baseline golden test;
    • checking the manifest before the baseline's version fails the order test;
    • moving the generator output check before the non-source checks fails the order test;
    • dropping the failed-compile check from LoadBaseline fails a parent test and a new store test;
    • dropping the file-count check from the generator output match fails the new store test.
  • cli/dispatcher:
    • golangci-lint fmt --diff is empty; go vet ./... passes; golangci-lint run and the complexity config both report 0 issues.
    • go test ./... and go test -race ./internal/compilecheck/... (including resultstore) pass.
    • Coverage with the CI exclusions: 94.25% (baseline 94.2). It was 93.74% before the boundary tests.
  • cli/release-automation: go test ./... passes. The file-length check passes.
  • Out of scope: keeping the previous baseline after a failed compile is still awaiting a decision. If it is done later, it only touches Forget and RecordCompile.