Files
ReleaseKit/docs/roadmap/implementation.md

23 KiB

Computational Efficiency Implementation Plan

Status: Ready for implementation; all stages pending.

Purpose And Baseline

This plan turns the performance roadmap into ordered work for a Luna coding agent. The inspected baseline is commit ff90510c380b47a23d0ac1cd8c314b1c4366a15b; this planning change updates only the two roadmap documents. Recheck the worktree and relevant code before each stage because subsequent stages build on earlier changes.

Execute Stage 1 through Stage 11 in order. Each stage is one focused implementation prompt. Complete its changes, applicable validation, canonical documentation updates, and local commit under the agent policy, then proceed. The sequence ends with a results report for review. Consumer pilots, hosted operations, and intermediate releases are outside this implementation sequence.

Read development.md and its applicable policy guide. Apply the architecture policy, testing policy, and documentation policy. Use graph tools for code discovery as instructed by AGENTS.md.

Selected Design And Scope

Area Selected implementation
Child process A small piped-process helper in internal/process, sharing existing command setup and error wrapping.
Git blobs One lazy git cat-file --batch session per candidate tree, with one request/response exchange at a time.
Raw file bytes Caller-owned bytes for regular-file reads; immutable symlink strings cached by object ID.
Repeated checks Compact formatting outcomes, parsed Markdown documents, and replacement summaries cached by object ID within their check.
Artifact hashing Data-only declarations followed by one existing local preflight verification; retain build hashing and missing-upload verification.
Go output Separate status and captured-output methods; transfer completed buffers without copying.
Runtime version A standard-library sync.OnceValue cache for the fallback, with an immediate injected-version return.

These are internal implementation choices. Preserve public facade signatures, configuration, CLI semantics, selected check ordering, callback sequencing, findings, failure causes, publication guards, and delivery recovery. Existing public contracts remain authoritative. New helpers expose only the internal operations needed here. File-size selection and existing supported inputs keep their current semantics.

The Git session and checker caches belong to one validation operation. Existing sequential check execution remains sufficient for this design. The runtime fallback is immutable process data and uses the standard library's concurrent initialization mechanism.

Stage 1: Measure Candidate Content And Validation

Owners: Benchmark files in internal/validation and root facade fixtures.

  1. Add benchmarks named with the BenchmarkPerformance prefix. Reuse local Git fixture patterns; adapt shared helper parameters to testing.TB only where a benchmark needs them.
  2. Measure content access and formatting for 1,000 unique regular Go blobs. Include loose and packed objects and a repeated-object-ID case. Measure Markdown checking with a 1 MiB document, repeated links to it, and identical contents under different directories.
  3. Measure a complete facade Check using a temporary committed Go module with a valid release note, Go formatting, local replacements, and Markdown selected. Keep its Go suite real and offline. Prepare fixtures outside timed work. Report allocations and bytes processed, separating content access, tree metadata, and Go test/compile work.
  4. Add one diagnostic retained-memory observation after file processing, with the candidate and document cache kept alive, to expose raw-content retention. Keep GC/profile collection outside timing loops.
  5. Create a measurement record in this document for baseline and final results. Include commit, Go/Git versions, fixture sizes, packed/loose state, and cache state. Keep raw benchmark output or profiles in temporary storage.

Validation and completion: Run these benchmarks with -run '^$', -bench '^BenchmarkPerformance', -benchmem, and -count=3. Verify the fixtures inspect the intended bytes. Synthetic modules use test-owned module caches under the developer guide. Timing ratios remain reported measurements; behavioral tests protect content, failures, and ownership. This stage changes measurement support only.

Stage 2: Measure Delivery, Output, And Runtime Lookup

Owners: Benchmark files in internal/delivery, internal/gotool, internal/runtimeinfo, and root delivery fixtures.

  1. Extend the same benchmark prefix to declaration/preflight processing of two deterministic 16 MiB artifact files. Measure prebuilt facade Deliver against a local HTTP fixture for fresh uploads and matching retries. Prepare files outside timed work and reset hosted state for each fresh run. Stream binary bodies through the fixture's digest/size checks and serve matching files incrementally so fixture buffering does not obscure allocation improvements. Describe fixture processing costs in the workflow results.
  2. Benchmark completed Go-output capture with modest output and 8 MiB diagnostics, including forwarding to io.Discard. Reuse the combined writer for component measurements and existing child fixtures for any command measurements.
  3. Measure injected version access and repeated real metadata-fallback access. Preserve benchmark coverage of initialization and repeated access when the implementation later adds caching.
  4. Record time, allocations, bytes processed, and workload/cache settings in the measurement record. Run the benchmark set with the same flags and repetitions as Stage 1. Verify delivered bytes and matching-retry behavior.

Validation and completion: Measurements are repeatable offline using local collaborators and temporary files. Fixture state changes occur outside timed work where they are setup. These two measurement stages establish the baseline before production refactoring begins. Default tests remain independent of real credentials and mutable external services.

Stage 3: Add The Piped Child-Process Boundary

Owner: internal/process and its tests.

  1. Factor existing command construction and command-error wrapping into small private helpers shared by Run and a new internal StartPiped operation. Preserve arguments, directory, environment overlay/removal, stderr routing, context handling, and wrapped exit errors.
  2. StartPiped accepts an Invocation for command settings and stderr; stdin and stdout are supplied by pipes. Return a small process handle containing the input writer and output reader, plus Cancel and Wait operations. Keep the underlying exec.Cmd private. Use a derived cancellable context so the owner can abort a blocked exchange without cancelling its caller.
  3. Close acquired pipes on setup/start failure. The caller owns input closure, stdout consumption, and one wait/reap after a successful start. Cancel interrupts the child; Wait retains the current context/exit error semantics. Read stdout to completion before a normal wait. Release the derived context when the process is reaped; abort paths close active pipes before waiting.
  4. Extend the existing test executable helper for an exchange that returns output before stdin EOF. Test real argument/environment propagation, normal EOF completion, cancellation after a readiness signal, start failure, and a nonzero child exit. Synchronize cancellation with the child instead of depending on sleep duration.

Validation and completion: Run internal/process tests. Existing Run behavior still passes, and the piped helper supports repeated exchanges followed by deterministic cleanup. Update the process inventory description when needed.

Stage 4: Implement The Git Batch Reader

Owners: A focused reader file and tests under internal/git, reusing source.go.

  1. Add an internal BlobReader that stores a repository directory and parent context. Create its process lazily on the first content request through process.StartPiped with git cat-file --batch.
  2. Use the same child-only GIT_NO_LAZY_FETCH=1 and GIT_ALLOW_PROTOCOL= settings as readLocalObjects. Keep existing standalone ReadBlob, ReadCommitFile, and batched metadata functions for their current callers.
  3. Implement Read(ctx, objectID) ([]byte, error) as one serialized exchange: validate the ID using the existing helper, write its newline-terminated request, read the response header, validate object/type/size, read exactly the declared bytes, and consume the framing newline. Compare hexadecimal IDs without narrowing the existing accepted case conventions.
  4. Allocate the response body once and transfer it to the caller. Use buffered header reading and io.ReadFull; blob contents can contain arbitrary bytes and newlines. Read only requested bodies and avoid whole-session buffering. Size conversion errors reflect representability on the host.
  5. Treat Git's missing-object response as a contextual inspection error. Preserve stderr and process causes when the child fails. A broken protocol or I/O exchange aborts that session and reaps the child; return the original cause.
  6. Honor the read context during blocked I/O, for example by registering context.AfterFunc to cancel the child and stopping that registration after the exchange. Coordinate callback completion when the stop function reports it has begun; stopping alone does not wait for that callback. If cancellation races completion, preserve its cause and abort the session. Read captured stderr after reaping to avoid writer races.
  7. Add idempotent Close: an unused reader has nothing to close; a healthy reader closes stdin, finishes stdout consumption, and waits; an aborted reader closes pipes, cancels, and waits. Preserve unexpected exit failures; deliberate abort cleanup retains the original read failure.

Validation and completion: Test repeated reads from loose and packed Git objects, empty/binary bodies, and missing objects. Reuse the partial-clone test approach to verify absent objects cause errors with no transport or object-store population. Use a small child fixture for truncated framing and cancellation mid-response. Assert content and cleanup outcomes; benchmark process reuse without making a subprocess count a public contract. Run Git and process tests.

Stage 5: Give Candidate Validation A Scoped Reader

Owners: tree.go, validation.go, and tree fixtures.

  1. Give CandidateTree one lazy BlobReader and an internal Close method. ReadCandidateTree still reads metadata only. Route blob misses through the reader while temporarily retaining the existing byte-cache behavior.
  2. The shared validation executor owns closure on every return after loading a tree. Complete cleanup before product checks or build callbacks run. Metadata-only selections leave the content reader unstarted.
  3. Preserve an earlier check's Failure and cancellation cause during cleanup. If cleanup reveals an unexpected child failure after successful checks, return a contextual Failure attributed to the last content-reading check. Track that check in the executor; normal intentional cancellation during cleanup adds no independent failure classification.
  4. Update direct tree users in tests and benchmarks to register cleanup or close explicitly. Update synthetic tree fixtures to support lazy reader creation. Keep tree paths, source capture, symlink resolution, and check order intact.

Validation and completion: Run validation, candidate, publication, and facade candidate tests. Cover early check failure and cancellation with cleanup, and preserve workspace/file-size metadata behavior. Re-run content benchmarks with reader startup and closure included in the content-operation timing. Update validation mechanics for the landed lifecycle.

Stage 6: Release One-Use Source Bytes And Reuse Formatting Results

Owners: internal/validation/tree.go, validation.go, and destination tests.

  1. Remove the tree's general raw-blob cache. A regular-file ReadFile returns its newly read, caller-owned body without a cache copy. Its GoDoc describes that ownership and the scoped reader; independently repeated raw reads can request the same object again.
  2. Cache committed symlink targets as owned immutable strings keyed by object ID. Add a private target-reading helper used by destination resolution and module-symlink handling. Preserve original target bytes and traversal order. Explicit ReadFile of a symlink still returns independently owned bytes.
  3. In checkGoFormat, cache the formatted/unformatted result for successfully inspected objects by object ID. Release source and formatted bytes from retained references after comparing them. Emit a finding at every selected unformatted path, including duplicate-content paths. Parse/read errors still stop with the current path and cause; check cancellation for each entry.
  4. Adapt tests that construct the old cache directly. Preserve observable isolation: mutating returned bytes cannot alter later validation or reads. Reuse destination-cycle and repeated-symlink tests for shared target strings.

Validation and completion: Run validation tests and formatting/destination benchmarks. Confirm many unique Go files no longer remain as retained source buffers after formatting, and duplicate-content files retain per-path findings. Record the tradeoff for explicit repeated raw reads. Update validation mechanics with the new ownership and reuse choices.

Stage 7: Reuse Owned Markdown And Module Results

Owners: markdown.go, markdown_check.go, and local_replacements.go.

  1. Key parsed Markdown reuse by resolved regular blob object ID. Keep a path lookup when useful for callers, backed by the shared document records. Relative link resolution and findings use the current source path; a shared parsed document stores content-relative locations, links, and anchors only.
  2. Give retained link destinations and titles their own string storage, using strings.Clone at extraction where needed, so parser/source views cannot keep a large raw body alive. Cache parsed records, release the AST and source from retained references, and preserve heading collision rules, image behavior, and use-site locations.
  3. Refactor module-declaration selection to identify the resolved regular entry before reading its body. After successful parsing, cache a compact list of filesystem replacements by resolved object ID: target string and source line. Own retained target strings and release raw bytes and the parsed syntax tree from retained references.
  4. Apply each cached replacement summary to the current declaration path when constructing findings. Resolve each symlink declaration in its own path context. On read or parse failure, preserve the current declaration's path and underlying cause; preserve findings order and versioned-replacement rules.

Validation and completion: Run Markdown/module and facade validation tests. Add behavioral cases for identical Markdown in different directories and two module declarations resolving to one object, including multiple findings with correct paths/lines. Reuse parser and boundary/cycle coverage. Re-run large-document and repeated-content benchmarks and retained-memory observations. Update the validation document's cache description to match implemented ownership.

Stage 8: Consolidate Delivery Preflight Hashing

Owners: delivery declarations, delivery orchestration, and their tests.

  1. Make declaredBuildAssets perform structural declaration checks, stat files, reuse the recorded artifact SHA-256, and construct expected assets and the manifest. Remove its file-opening/hash pass. Keep target, filename, regular file, declared digest format, and manifest validation at the appropriate existing owner.
  2. Keep one gitea.VerifyLocalAssets call in Deliver after declaration and build-identity checks and before hosted requests. This verifies actual bytes and sizes against recorded identities for both managed and prebuilt assets.
  3. Keep builder hashing, CLI receipt verification, and missing-upload preflight in their current phases. Managed delivery reuses its fresh build metadata for declarations; prebuilt delivery reuses its caller's recorded digests only as expected identities until current bytes have been verified.
  4. Move the declaration helper's tampering assertion to delivery preflight's behavioral owner. Verify same-size changed contents fail before any hosted request. Reuse source-only, retained-prebuilt, managed cleanup, conflict, partial-upload, and retry coverage.

Validation and completion: Run delivery, Gitea, facade delivery, and CLI receipt/delivery tests. Re-run artifact and complete delivery benchmarks. A fresh missing binary has three hash passes including a managed build, or two within prebuilt delivery; these are explanatory measurements of the chosen flow. Streaming upload, exact remote comparison, and later upload verification remain intact. Update the inventory's delivery mechanics where needed.

Stage 9: Transfer Completed Go Output Without Copies

Owners: gotool.go, validation/build callers, and Go tool fixture tests.

  1. Give Tool a status-returning Run(...args) error and an output-returning Output(...args) ([]byte, error), with one private execution path. Keep their existing directory, mode, target, writer, and argument inputs.
  2. On completion, a status-only success releases its capture. An output-consuming call receives the completed buffer's backing bytes with ownership transferred from the private writer. Replace combinedWriter.Bytes copying with a small synchronized ownership-transfer operation used after child output has finished.
  3. Preserve complete failure diagnostics, original causes, combined-output synchronization, and forwarding errors. If Output fails, return its captured bytes and diagnostic error as before. Status failures format the same useful diagnostics without creating an unnecessary intermediate snapshot.
  4. Convert validation test/vet/compile and artifact-build calls to status-only Run. Use Output for compiler identification. Adapt preparation failure formatting to the same completed-buffer ownership discipline, preserving its network-capable environment. Leave configuration discovery's separate stdout JSON and stderr handling intact.
  5. Keep full capture in memory. Measure remaining large-log retention and record the disk-backed alternative as follow-up evidence, as scoped by the roadmap.

Validation and completion: Extend existing process/Go fixtures for successful capture, large failure output, cancellation, and a failing output writer. Verify forwarded bytes and meaningful error content/cause. Run Go tool, validation, build, preparation, and affected CLI tests. Re-run output benchmarks and update Go execution mechanics.

Stage 10: Cache The Runtime Metadata Fallback

Owners: runtimeinfo/version.go and runtime/buildinfo tests.

  1. Preserve injectedVersion and its linker identity. Version checks and returns injection first, then calls a fallback accessor created with sync.OnceValue.
  2. Keep the existing pure version(injected, readBuildInfo) selection helper. Add a small private fallback factory accepting the metadata reader and returning the cached accessor. Production supplies debug.ReadBuildInfo; tests create independent cache instances with controlled metadata.
  3. Reuse selection tests. Add repeated/concurrent access coverage with a counting reader to verify reuse and stable results. Test unavailable and development metadata fallback through isolated instances. Keep shared production state immutable during tests.

Validation and completion: Run runtime/buildinfo tests, the existing consumer build-and-run injection test, and focused race validation of the fallback cache. Run facade/dependency checks. Re-run version benchmarks after initializing the cache and report initialization separately from repeated access. The runtime facade and its dependency boundary retain their existing public shape.

Stage 11: Verify The Complete Implementation And Record Results

Owners: Affected tests and canonical internal documentation; roadmap status.

  1. Run the complete offline test suite and compiled facade/dependency checks using the developer guide. Resolve remaining failures within this performance scope. Reuse targeted race results where code is unchanged; broaden them only for a concrete remaining concurrency risk.
  2. Run the Stage 1 and Stage 2 benchmark set again with comparable toolchain, fixtures, cache state, repetitions, and measurement boundaries. Compare component and complete Check/Deliver results. Record memory and explicit reread tradeoffs as well as time and allocation improvements.
  3. Record final results alongside the baseline in this document. Explain the remaining Go compilation/test cost, remote-comparison work, and diagnostic retention where measurements show they matter. Separate measured effects from expectations and include machine/workload limitations.
  4. Check canonical internal docs describe landed reader lifetime, source/result ownership, verification flow, output ownership, and runtime reuse. Preserve existing public contracts; update a public document only if its explanation became inaccurate. Check local links and prose whitespace.
  5. Mark each stage's actual completion, update the feature roadmap status, and commit the completed work under repository policy. End with a concise report of changes, tests, performance results, and remaining follow-up opportunities. Retain these planning documents with their results for the final review.

Completion evidence: All five target improvements are implemented, relevant existing behavior passes, the public facade checks pass, comparable measurements are recorded, and the worktree is clean. Completion depends on observed behavior and reviewed diffs; benchmarks establish improvements within their measured scope without adding numerical runtime guarantees.

Open Questions

None required for this implementation. The plan selects scoped batch reading, result-based reuse, preserved verification boundaries, in-memory diagnostics, and cached runtime fallback. Disk-backed diagnostics remain a follow-up option whose benefit and lifecycle costs can be assessed from the recorded output measurements.