diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md new file mode 100644 index 0000000..ef3abdb --- /dev/null +++ b/docs/roadmap/implementation.md @@ -0,0 +1,1024 @@ +# ReleaseKit staged implementation plan + +Status: Proposed. All stages are pending; this document introduces no implemented +features or published contracts. + +Date: 2026-10-05 + +## Purpose and baseline + +This plan gives a Luna coding agent bounded implementation assignments, in +execution order, for the target state in the [feature roadmap](releasekit.md). +The roadmap owns product intent and selected release conventions. This document +owns implementation steps, dependencies, acceptance evidence, and progress. + +The baseline is ReleaseKit commit `43c20b0`. It contains documentation, adopted +policies, licensing notices, and [ADR 0001](../adr/0001-public-facade.md). It has +no Go module, packages, executable, tests, or publication automation. The +worktree was clean when this plan was prepared. The available tools were Go +1.26.8 on linux/amd64 and Git 2.53.0; these observations are not minimum support +requirements. + +The six sibling checkouts are consumer evidence and future migration targets. +They are independent repositories. Their source and instructions must be +rechecked before each migration; the earlier line counts are historical review +measurements. Consult their code through the knowledge graph, indexing a +repository when necessary. Documents, scripts, and CI configuration may be read +directly under the discovery instructions. + +## Execution instructions + +- Implement stages in integer order. Each stage is one assignment; finish its + acceptance checks and local commit before starting the next stage. Record + completed work and evidence here. Read the stage's dependencies and relevant + decisions before editing. +- Start with the [developer guide](../development.md) and applicable policies. + Architecture, testing, documentation, and release policies apply to the core + implementation. Read the OSS policy when incorporating third-party material. +- Follow the [architecture policy](../policy/architecture.md) and ADR 0001. + Add exported declarations as their behavior is implemented, with GoDoc. + Implement the selected contract with proportionate internal collaborators. +- The design choices below specify this draft's recommended implementation. + They become implementation instructions when the plan is approved. Later + agent-authored code or tests provide evidence of implementation, rather than + independent authority to strengthen the contract. +- For each stage, run focused behavioral checks for its changed risks and the + established offline default suite. Reuse unchanged evidence where appropriate. + Stage acceptance checks are development checks; they add no publication gates + beyond the release policy and a repository's selected candidate checks. +- Use temporary repositories, local bare remotes, test programs, and local HTTP + servers. Keep fixtures independent of the six consumer checkouts and real + services. Temporary fixture module replacements may connect test programs to + this checkout; keep them outside committed production module configuration. +- Update canonical documentation with each implemented responsibility. Create + consumer, CLI, configuration, integration, and internal documents when they + have substantive content. Keep future behavior and stage progress here. +- Consumer migration stages change the named sibling repository as well as any + corresponding ReleaseKit examples. Follow that repository's policies, preserve + unrelated work, and commit each repository separately. When adding a runtime + dependency, update its module metadata and OSS attribution/companion license + texts in the same change using the actual selected source pin. A migration + needs a reachable immutable ReleaseKit pin, as described under Open Questions. +- Local implementation commits and tests against temporary remotes complete + stages. Real release publication, remote pushes, CI activation, and credential + provisioning require the maintainer's corresponding authorization. Do not + equate prepared integration with verified live delivery. + +## Implementation decisions + +### Module, runtime access, and public ownership + +Use one module, `gitea.maximumdirect.net/eric/ReleaseKit`, preserving the case +advertised by the forge's Go import metadata. Set the initial Go language +baseline to `go 1.25.0`, matching the oldest consumer module line. Use APIs +available on that baseline. Actual execution uses a prepared local toolchain +capable of building the consumer; Narratio already needs Go 1.26. There is no +mandatory multi-toolchain release matrix. + +The supported packages are the root `releasekit` facade and `buildinfo`. Put +the executable at `cmd/releasekit`. Start with standard-library implementations +and the installed Git and Go executables. Introduce internal packages as their +responsibilities land: runtime information, process execution, release engine, +Git operations, builds, Gitea transport, and CLI adaptation. Put command-owned +parsing and file-format helpers under `cmd/releasekit/internal/cli`; keep facade +calls in the `cmd/releasekit` controller. Those helpers return their own settings +for explicit conversion by the controller and import neither public facade. +The executable therefore consumes the supported APIs while library internal +packages retain inward dependencies. There are no public transport clients or +assembly APIs. + +The initial runtime API is `buildinfo.Version() string`. It returns an injected +release identifier when supplied; otherwise it uses the running executable's +main-module version when present and different from `(devel)`, then `dev` when +that metadata is unavailable. This supports source installs as well as explicit +release builds. It describes the application version, not the ReleaseKit +dependency version. [Go build information](https://pkg.go.dev/runtime/debug#ReadBuildInfo) +documents the main-module metadata available to implement this fallback. + +Keep the injected string private in the internal runtime implementation. Keep +the linker symbol identifier in a small internal contract used by the builder. +The builder and runtime implementation change together. Consumers call the +accessor; their configuration supplies no linker symbol. The runtime import +graph uses the standard library and small runtime implementation, independent +of orchestration. A tool/runtime version mismatch must be exposed by the native +version smoke check; do not introduce an exact dependency-version equality gate. + +### Orchestration API + +Implement these root operations as the corresponding stages land: + +| Operation | Request and result responsibilities | +| --- | --- | +| `Check(ctx, ReleaseRequest)` | Capture and validate a local committed candidate; return its commit and version. This diagnostic operation does not query publication services, create a tag, or claim remote readiness. | +| `Publish(ctx, ReleaseRequest)` | Validate once, check authoritative state, create and publish the selected annotated tag, and return publication state and retained recovery facts. | +| `Build(ctx, BuildRequest)` | Build the selected targets with the requested version, verify a native version invocation, and return source identity and generated artifacts. | +| `Deliver(ctx, DeliveryRequest)` | Verify tagged source and supplied artifact identity, create or find a hosted release, compare content, and add missing assets with explicit partial or uncertain outcomes. | + +Each operation returns a facade-owned result and an error. Requests carry the +repository directory explicitly. `ReleaseRequest` also carries the display +product name, version, selected remote, optional validation callback, optional +build specification, and optional output writer. The remote defaults to +`origin`; publication uses `main`. The callback receives a facade-owned +`Candidate` containing directory, captured commit, and selected version. A nil +callback selects the default Go test invocation. A supplied callback replaces +that invocation with the repository's complete selected validation; it is +called once. The CLI adapts configured commands into this callback. + +A build specification carries main package, binary name, and targets. Each +facade-owned `Target` contains Go OS and architecture strings. A build request +adds version and output directory. A delivery request identifies version, +product, repository directory, Gitea destination, and optional build result. +The Gitea destination carries base URL, owner, repository name, and caller-supplied +token. Library code obtains no credentials from ambient configuration. Before +the first remote write, an inspection failure reports that this invocation has +not attempted mutation. Reserve uncertain-mutation classification and exit 3 +for writes whose authoritative outcome remains unresolved. + +Publication results identify version, candidate commit, captured endpoint, +phase, and tag object when created. Use distinct states for `not_attempted`, +`not_published`, `published`, `conflict`, and `unknown`. These describe this +invocation; a pre-existing remote release is not described as unpublished. +Delivery results identify release URL and the matching, uploaded, pending, and +uncertain assets, with `not_attempted`, `complete`, `partial`, `conflict`, or +`unknown` state. Ordinary errors preserve useful causes through wrapping. +Document exactly when nonzero results accompany errors. Add only the named +types needed to represent these responsibilities; field spelling and internal +package layout can follow idiomatic Go without changing the decisions here. + +### CLI and configuration + +Use standard-library flag parsing and JSON configuration. The default profile +is `/releasekit.json`; `--config` selects another file. Configuration +paths are resolved from the selected repository, whose default is the current +directory. A profile supplies product identity, remote, candidate commands, +optional build specification, and optional Gitea delivery declaration. The +command supplies the version rather than persisting it in the profile. + +Use this eventual profile shape; add each optional responsibility only when its +stage implements it: + +```json +{ + "product": "Scriptorium", + "remote": "origin", + "checks": [{"args": ["sh", "scripts/check-release-candidate.sh"]}], + "build": { + "main_package": "./cmd/scriptorium", + "binary_name": "scriptorium", + "targets": ["linux/amd64", "linux/arm64"] + }, + "delivery": { + "base_url": "https://gitea.maximumdirect.net", + "owner": "eric", + "repository": "scriptorium", + "token_env": "GITEA_RELEASE_TOKEN" + } +} +``` + +This is a planned example, not an existing file or working invocation. Command +entries also accept an `env` map. Run argument vectors directly, without +implicit shell parsing. A repository that needs shell syntax selects `sh` or +`bash` explicitly. Run commands in the captured repository directory and supply +`RELEASEKIT_VERSION` and `RELEASEKIT_CANDIDATE` for candidate-specific checks. +Those captured values take precedence over conflicting command environment +entries. Inherit the remaining environment and overlay explicitly owned values +without mutating the caller process. + +An absent or empty check list selects `go test -count=1 ./...`; a nonempty list +is the repository's complete validation sequence. Decode profile fields +strictly so unsupported build or delivery settings cannot silently disappear +from the selected checks. Validate required values for the operation being run; +a source-only profile needs neither a main package nor credentials. +The CLI loads the token named by `delivery.token_env`, defaulting to +`RELEASEKIT_GITEA_TOKEN`, only for delivery. Build receipt identity covers resolved +build inputs; delivery credentials, destination, and unrelated check settings +are excluded from that identity. + +The eventual command forms are: + +```text +releasekit version +releasekit check [--repo DIR] [--config FILE] VERSION +releasekit publish [--repo DIR] [--config FILE] VERSION +releasekit build [--repo DIR] [--config FILE] [--output DIR] VERSION +releasekit deliver [--repo DIR] [--config FILE] [--assets DIR] VERSION +``` + +Flags precede the positional version. `version` prints `releasekit `. +Use exit 0 for completed success, 1 for a known failure or conflict, 2 for usage +or invalid configuration, and 3 for an uncertain remote mutation. Library +outcomes retain more detail. Send validation progress and diagnostics to stderr; +stdout presents the command's final result. Signals cancel the operation through +its context. There is no interactive confirmation loop. + +### Candidate checks and Git publication + +Parse stable versions using the roadmap's exact syntax. Validate decimal +components as strings without introducing a machine-integer size limit. Read +the release note from the captured commit at `docs/releases/.md`. Its +first line must be a Markdown H1 containing the configured product name and +exact version as complete title elements. Preserve the note content for the annotated tag. +The shared tool imposes no additional note section inventory. + +`Check` checks a clean worktree and index on `main`, captures HEAD, checks the +committed note and unused local version, runs validation and selected builds, +and rechecks local mutable state. `Publish` adds authoritative endpoint and +remote branch/tag checks before validation and immediately before publication. +Cleanliness includes untracked files; ordinary Git ignore rules still apply. +Snapshot resolved request values and CLI profile content for the invocation so +configuration is not reloaded into different checks midway through the run. +Source changes remain subject to the ordinary candidate rechecks. The Go API +accepts caller-supplied values without requiring a particular profile file. + +Resolve the selected remote's effective push destination using Git's own remote +configuration semantics. A missing destination or more than one distinct +effective publication destination is an actionable configuration error because +this operation needs one authoritative endpoint. A separate fetch URL is +supported. Inspect the publication endpoint, +rather than assuming the selected remote's normal fetch URL is authoritative. + +Use a transient, command-scoped named remote with explicit read and push URLs +and mirror mode disabled. Verify that both effective URLs equal the captured +publication destination after Git URL rewriting. Reuse that scoped remote for +queries and push; persist no new remote configuration. Retain normal Git +credential and transport settings, carrying remote-specific transport settings +into the scoped remote as needed. If rewriting prevents establishing this +equality, report the unresolved destination before mutation. +Recheck the original remote resolution before pushing. Test ordinary +`insteadOf`, `pushInsteadOf`, and explicit push-URL cases using local remotes. +[Git's remote documentation](https://git-scm.com/docs/git-remote) describes the +effective URL lookup on which this relies. + +Use exact ref names in remote queries. Require authoritative `main` to equal +the captured candidate and the selected tag to be absent. Create the annotated +tag from the committed note with signing disabled for this invocation, inspect +its object type and peeled commit, then freeze its object ID. Push +`:refs/tags/` with automatic following of tags and mirror +defaults disabled. Publish no branch updates, unrelated tags, or force updates. + +A successful push returns immediately as published. After a failed or ambiguous +push, make one authoritative tag query: an exact object match confirms success; +absence confirms not published at observation; a different object is conflict; +an unavailable or uninterpretable answer is unknown. Retain the local tag in +all failed or uncertain cases. Caller cancellation after push starts also needs +this bounded reconciliation attempt using a separate finite recovery context. +Use a 30-second recovery deadline as an operational default, not a guarantee +that the endpoint will respond. Cancellation before push does not initiate it. + +The last remote branch check is an observation before the tag push. This design +provides no transaction locking remote `main` against concurrent writers. Do not +add branch pushes or assert stronger concurrency guarantees in tests. + +### Go execution, builds, and assets + +For ReleaseKit-owned candidate Go commands and builds, use prepared dependencies +and a local toolchain. Set child environment values `GOWORK=off`, `GOPROXY=off`, +`GONOPROXY=none`, `GOSUMDB=off`, and `GOTOOLCHAIN=local`. Set module mode to +readonly, preserving a repository's deliberately selected testing flags. +ReleaseKit-owned builds replace ambient `GOFLAGS` with `-mod=readonly` and use +their explicit build arguments; custom validation commands retain their +deliberately configured flags. +Preparation of caches and tools occurs separately. These settings do not +sandbox arbitrary repository callbacks or establish a general network firewall. + +Build with `CGO_ENABLED=0`, `-mod=readonly`, `-trimpath`, `-buildvcs=false`, and +linker flags `-s -w` plus the shared version injection. This selects the pure-Go +build convention already used by the current asset builders. Additional cgo +toolchains remain outside this implementation. The injected string must use a +linker-supported initializer. [Go's linker documentation](https://pkg.go.dev/cmd/link) +defines the `-X` mechanism. + +Initially verify linux/amd64, linux/arm64, and darwin/arm64. Represent targets +as values rather than a closed enum; other explicit Go OS/architecture pairs +can be passed to the prepared compiler and may fail according to its support. +This establishes no native-execution promise for other platforms. + +Asset names are `---`, with `.exe` when the +requested Go OS needs it. Binary names and target components must be nonempty +filename components without path separators, backslashes, CR, or LF, so names +stay in the output directory and have unambiguous manifest entries. Normalize +repeated target pairs to one target, retaining their first position. Distinguish +the display product name used by notes from this binary name. Preserve selected +target order for progress; generate manifests in filename order. + +For nonempty targets, invoke the built application with `version` and require +` ` plus a final newline. Reuse a target artifact when it +matches `GOHOSTOS/GOHOSTARCH`; otherwise build a temporary native executable +with the same flags and injection. Determine the host independently of ambient +`GOOS/GOARCH`. Native smoke binaries are temporary and are not extra assets. +An empty target set performs no Go build or version invocation. Source-only +projects may retain an explicitly selected application check; no binary check +is silently imposed on them. + +Candidate build checks use a temporary output directory outside the worktree +and discard it. Artifact builds record the commit, version, main package, +targets, compiler identity, and generated filename/hash pairs. Return these +facts in `BuildResult`. The CLI persists them in an internal local receipt, +`.releasekit-build.json`, alongside the generated files after a successful +complete build and smoke check. A partial build returns progress without a +receipt authorizing delivery. This receipt is for tool coordination, with no +public format or attestation promise. It contains no credentials and is not a +hosted asset. Build output defaults to `dist`; create +the directory as needed, regenerate owned local filenames, and preserve +unrelated files. Report generated files on partial local failure and clean up +only tool-owned temporary paths. + +Delivery uses a clean checkout whose HEAD equals the selected local tag's peeled +commit; detached HEAD is valid. Read the note from that commit and snapshot the +selected settings. Repository-local tracked profiles come from that source; +explicit caller-supplied settings are identified in the build result. Verify +the hosted repository already has the same tag targeting that commit before +creating a release. Existing lightweight historical tags may be inspected and +delivered; new publication follows the annotated-tag standard. Do not require +current remote `main` to remain at an old release commit during delivery retries. + +The CLI's `deliver --assets DIR` consumes a verified build receipt and checks +file hashes, selected filenames, and source/build-input identity before remote +mutation. When no directory is supplied, build into a temporary directory from +the tagged checkout and then deliver. With zero targets, optional hosted delivery +creates a note page without a binary manifest. Neither mode reruns the candidate +test suite or changes source-publication status. + +### Hosted delivery and Woodpecker + +Use a small internal standard-library Gitea REST client. On 2026-10-05 the host's +[version endpoint](https://gitea.maximumdirect.net/api/v1/version) reported Gitea +1.25.4; its [served API schema](https://gitea.maximumdirect.net/swagger.v1.json) +confirmed release-by-tag lookup, JSON release creation, asset listing, and +multipart attachment upload. Consult the host's schema during implementation; +record used operations in the integration guide. This observation is not an +all-version Gitea compatibility promise. + +Read the tag through `/api/v1/repos/{owner}/{repo}/tags/{tag}` and verify its +`commit.sha`. Use the repository API prefix for each following operation. Find +the release through `/releases/tags/{tag}`. Create it through +`POST /releases` with the existing tag, captured commit, title from the note's +H1, complete committed note as body, and stable, non-draft settings. A found +release must match the note body and tag and be stable/non-draft; retain its +existing title, including a historical title chosen by other tooling. A content +conflict is reported; its body, title, assets, and tag are not edited. Existing +historical pages remain intact. + +List attachments through `/releases/{id}/assets`. Identify expected filenames +and stream their downloads to compare SHA-256 content; size and name alone do +not prove a match. Use multipart field `attachment` and the filename query +parameter for missing uploads. The client obtains no automatic tag-creation +responsibility. Keep authorization on the configured service origin when +following attachment URLs or redirects; never include token values in receipts +or diagnostics. + +For nonempty binary sets, generate `SHA256SUMS` with lowercase hashes, two spaces, +filenames, and LF-terminated lines, sorted by filename. It describes the complete +declared binary set and excludes itself and the private receipt. An existing +manifest must match that expected content. Fill missing assets covered by that +manifest only with matching bytes. A manifest describing a different or smaller +set cannot be replaced to extend that version. Leave unrelated attachments +untouched. + +Inspect all known declared asset and manifest conflicts before uploading. +Upload missing binaries in filename order, then a missing manifest. HTTP writes +are separate operations. After an ambiguous creation or upload, re-read the +affected resource once; classify matching content as completed, conflict as +conflict, and inaccessible or still-indeterminate content as unknown. Handle +concurrent creation by finding and verifying the created resource. Do not +blindly retry writes or delete attachments. Record partial progress so an +operator can rerun against the same tag and retained assets. + +Woodpecker uses tag-triggered repository-local YAML with a pinned Go/tool image +and a pinned ReleaseKit source install. Provide an example using an official Go +image plus a separate provisioning step that installs ReleaseKit from its +immutable release pin and prepares consumer module caches. Build and deliver +then run with offline Go execution. This honors ReleaseKit's source-only initial +distribution; a dedicated ReleaseKit container image would require a separate +deliverable decision. Use the repository's selected Go toolchain image, which +must satisfy its own module requirements. + +Use `CI_COMMIT_TAG` and `CI_COMMIT_SHA` to bind the workflow to tagged source, +with an explicit tag fetch when the checkout omits local tag refs. The build step +writes to ignored `dist`; the delivery step shares the workspace and supplies +the Gitea token from a Woodpecker secret. Pin actual image digests and source +versions when adopting the example. Pipeline preparation may access dependency +services; candidate validation and builds use the prepared caches. CI does not +call `publish`, push refs, or gate source completion. The supported retry is a +rerun from the same tagged source with content comparison; reproducible bytes +across arbitrary toolchains or elapsed time are not promised. + +## Stage 1: Establish the Go module and development baseline + +Status: Pending. Depends on the baseline only. + +1. Add the module with the selected path and language baseline. Add root package + documentation for the orchestration facade and establish ordinary Go layout. +2. Keep committed module state independent of sibling checkouts and workspaces. + Start without external runtime dependencies or a generic plugin framework. +3. Add the working offline developer test command and initial implemented-package + inventory. Update README to its actual implemented boundary. + +Acceptance: module loading and `go test ./...` work with the prepared local +toolchain and network disabled. Review module identity and licensing notices. +Do not invent tests merely to assert that an empty package exists. + +## Stage 2: Implement the runtime version facade + +Status: Pending. Depends on Stage 1. + +1. Implement `buildinfo.Version` and its internal injected-string and main-module + fallback logic. Keep runtime state private and the public accessor small. +2. Add the internal injection-symbol contract without importing orchestration + into the runtime path. Test injected precedence and missing/development + metadata through stable internal seams and a small executable fixture. +3. Add `cmd/releasekit` with a working `version` command using this accessor. + Record its implemented command contract and the runtime consumer guidance. + +Acceptance: a source-built fixture reports `dev`; a fixture linked with a stable +version reports that version. Fallback metadata uses the executable main module. +CLI version output is correct and no runtime orchestration initialization occurs. + +## Stage 3: Protect public contracts and dependency direction + +Status: Pending. Depends on Stage 2. + +1. Add a compiled-export boundary checker for both facades using `go list` export + data and `go/types`. Inspect exported signatures, fields, callbacks, aliases, + generic constraints, and pointer/value method sets recursively. +2. Assert facade ownership of public named types. Permit deliberately selected + standard-library contracts. Protect the private-state forwarding approach + against leaked internal types and promoted implementation methods. +3. Add import-direction checks: internal production packages use internal + contracts; the runtime facade's dependency closure excludes orchestration, + process, build, Gitea, and CLI implementation. +4. Use small negative fixtures proving that the checker catches a leaked type + and promoted internal method, and accepts a private implementation field. + +Acceptance: actual exports pass and meaningful negative fixtures fail. Tests +inspect contracts and imports, with no fixed inventory of future internal +package names. Extend these checks as subsequent exports land. + +## Stage 4: Implement profile loading and command adaptation + +Status: Pending. Depends on Stage 3. + +1. Implement JSON product, remote, and command settings under an internal CLI + adapter. Add strict field decoding, directory resolution, required-value + diagnostics, direct argument execution, and per-child environment overlays. +2. Implement an internal cancellable process runner with directory, argument, + environment, stdout, stderr, and exit-error support. Use `os/exec` directly; + expose no public runner abstraction solely for test assembly. +3. Add CLI parsing infrastructure and selected usage/exit behavior. Advertise + commands only when their following stages implement them. +4. Add small profile examples and `docs/config.md` for implemented fields. + +Acceptance: paths and arguments containing spaces remain separate arguments; +selected environment values reach the child without changing process globals; +missing executables, invalid configuration, and cancellation report useful +errors. Tests use controlled local child commands and temporary directories. + +## Stage 5: Capture and validate one local candidate + +Status: Pending. Depends on Stage 4. + +1. Implement stable-version syntax, clean `main` and index/worktree inspection, + captured HEAD, profile snapshot/note loading, title identity, and unused local + tag checks. Preserve immutable candidate facts in internal values. +2. Implement default offline Go validation and callback adaptation. Bind the + callback to the captured candidate; pass captured version/commit to CLI + commands. Call the complete validation callback once, stopping on failure. +3. Recheck branch, HEAD, and cleanliness after validation. Retain the captured + settings throughout the operation. Implement the public `Check` operation + and local diagnostic `check` command. +4. Document that local check success provides local candidate evidence and makes + no authoritative remote availability claim. + +Acceptance: temporary repositories cover invalid versions, missing or mismatched +committed notes, dirty tracked/untracked state, existing local tags, detached +HEAD, callback failure, and a callback changing HEAD or the worktree. Count a +successful callback once. No test needs a publication service, and `Check` +creates no tag or remote ref. + +## Stage 6: Resolve and inspect the authoritative endpoint + +Status: Pending. Depends on Stage 5. + +1. Implement effective push-URL capture, command-scoped remote configuration, + matching read/write resolution, and endpoint rechecks described above. +2. Implement exact remote main/tag queries with distinct present, absent, + conflicting, and unreadable results. Query failures never establish absence. +3. Compose the pre-validation and pre-publication remote guards around captured + local state. Require main equality, rather than ancestor-only publication. + +Acceptance: local remotes cover a separate fetch endpoint, explicit push URL, +ordinary URL rewrites, multiple push destinations, missing main, conflicting +tags, main ahead/behind the candidate, endpoint changes, and remote failure. +Queries leave local tags, persistent remote configuration, and remote refs intact. + +## Stage 7: Create and push the exact annotated tag + +Status: Pending. Depends on Stage 6. + +1. Implement public publication request/result values and explicit conversion + into the engine. Validate once between the complete preflight guards. +2. Create an annotated tag from captured note bytes, inspect type and peeled + commit, capture its object ID, and push only that object to the selected ref. +3. Recheck mutable local state and endpoint before the push, including the local + tag still identifying the captured object. A changed local tag prevents this + invocation from proceeding; cleanup remains deliberate recovery. +4. On successful push, return published state without another remote read-back. + On push error, retain the tag and return unknown until Stage 8 adds resolution. + +Acceptance: a local bare remote receives the expected tag object and commit; +its main and unrelated refs do not change. Follow-tags and mirror configuration +cannot expand publication. Note content, tag identity, and one-validation +behavior are checked through the public facade. Restrict stage trials to test +remotes while failure handling is incomplete. + +## Stage 8: Resolve rejected and ambiguous publication + +Status: Pending. Depends on Stage 7. + +1. Add the one-query reconciliation path and public error/result classification. + Preserve the original push cause when resolution does not confirm success. +2. Give post-push cancellation a separate bounded recovery context. Retain local + tag/object facts when the read fails or recovery is cancelled/times out. +3. Add real rejected-push tests using bare-remote hooks. Simulate lost + acknowledgments at the subprocess boundary after a real successful push. + Separate known absence, conflicting object, and unavailable-query cases. +4. Cover a remote branch change during validation and local tag mutation before + push. Keep concurrency promises at the observed guard boundaries. + +Acceptance: exact remote object match succeeds despite a reported push error; +missing, conflicting, and unavailable remote tags have distinct outcomes. Failed +pushes issue one reconciliation query, successful pushes issue none, and no +recovery force-update or automatic tag deletion occurs. + +## Stage 9: Expose publication and document ReleaseKit bootstrap + +Status: Pending. Depends on Stage 8. + +1. Wire the complete public publisher into `releasekit publish`, with selected + progress output, final result, exit classification, and signal cancellation. +2. Add ReleaseKit's source-only profile and one short maintainer entry script. + The profile selects the offline default suite; no inherited race, lint, + documentation, image, or platform gates are added. +3. Create `docs/release.md` now that publication works. Name authoritative + `origin` at the ReleaseKit forge URL and `main`; document preparation, + successful-push completion, exact-object recovery, local tag retention, and + deliberate cleanup of proven unpublished tags. +4. Document first-release execution from the captured candidate source using + the local Go toolchain, before a released ReleaseKit executable exists. + Include immutable release-note licensing footer construction and dependency + preparation separate from validation. +5. Update current development documentation and the policy-adoption roadmap's + bootstrap status to the implemented boundary. Preserve pending deliverables. + +Acceptance: an end-to-end CLI fixture publishes to a local remote through a +committed profile and returns the expected exit/output behavior. Runbook commands +are exercised using a temporary repository configured like ReleaseKit. This +stage prepares self-publication and does not publish a real ReleaseKit release. + +## Stage 10: Build selected targets with shared version injection + +Status: Pending. Depends on Stage 9. + +1. Implement public build values and internal build execution using the fixed + flags, version symbol, filename convention, and prepared Go environment. +2. Build explicit OS/architecture pairs and verify the application `version` + invocation using a host artifact or temporary native build. Preserve + caller-owned output files and report partial generated output on failure. +3. Return source identity, artifact paths, filenames, targets, hashes, and + compiler identity. Keep byte reproducibility and cross-platform execution + promises outside the result contract. +4. Add a temporary consumer module importing only `buildinfo` and exposing the + canonical version command. Its local replacement is fixture-only. + +Acceptance: the fixture compiles for the three initial targets and a native +execution reports the injected version. An ambient cross-target environment +does not select the smoke host. Empty targets perform no build or smoke test; +missing main packages, build failures, incorrect version output, unsafe binary +names, and cancellation fail with useful partial results. + +## Stage 11: Integrate candidate builds and artifact command + +Status: Pending. Depends on Stage 10. + +1. Add build profile fields, CLI `build`, and the private artifact receipt. + Bind receipts to source/build-input identity and generated hashes. Validate + data on reload; leave the receipt outside the public API and hosted asset list. +2. Integrate selected builds into `Check` and `Publish` after the single candidate + callback. Use disposable output outside the worktree for candidate builds. + Recheck mutable state after the entire validation/build sequence. +3. Add ignored `dist` output and source-only and binary-producing examples. + Document the API, profile target selection, version invocation, and build + output ownership. +4. Refresh facade and runtime dependency checks with the new values. + +Acceptance: source-only publication invokes no builder; selected build failure +prevents tag creation; selected builds inject the exact candidate version; +callback validation runs once. CLI artifacts and receipts agree on filenames, +hashes, commit, and resolved build settings. Generated validation output does +not dirty the candidate. This completes the first source-publication/build +capability slice. + +## Stage 12: Pilot publication in PromptKit + +Status: Pending. Depends on Stage 11 and a reachable immutable tool pin. + +1. Review PromptKit's current instructions, release policies, and entry scripts. + Add a source-only profile using its actual note display name, currently + `Promptkit`. Keep its application-specific consumer example checks and other + deliberately adopted candidate gates in its validation command. +2. Replace repeated release guards and tag publication with a short ReleaseKit + invocation. Move note identity validation to ReleaseKit; remove extra shared + note-section requirements only through the corresponding local policy change. +3. Update installation prerequisites, local release wiring tests, and maintainer + documentation. Retain no duplicate mandatory pre-check before publication. + +Acceptance: isolated repository fixtures exercise the new wrapper and profile +against a local remote, preserve source-only behavior, and run the selected +candidate checker once. PromptKit's own suite passes. Record deleted shared +mechanics separately from retained product checks. No Go runtime dependency or +hosted page is introduced for this library. + +## Stage 13: Pilot publication in Scriptorium + +Status: Pending. Depends on Stage 12. + +1. Adapt Scriptorium's publication entry point and release policy to the common + profile/command. Preserve its selected candidate test flags and genuinely + product-owned checks in a small local candidate command. +2. Retain its existing candidate build check temporarily: its application has + no canonical runtime version command yet. Document that shared builds are + deferred to Stage 19; the existing delivery pipeline continues meanwhile. +3. Transfer exact-object publication/failure tests into shared coverage and + retain a small wrapper/configuration test locally. Update its runbook. + +Acceptance: local-remote fixtures confirm common publication and bounded +recovery without changing the existing asynchronous deliverables. Scriptorium's +own suite passes. The two pilots substantiate the same public facade with +different product checks and endpoint configurations. + +## Stage 14: Find and create Gitea release pages + +Status: Pending. Depends on Stage 13. + +1. Implement the small internal REST transport using the served API operations, + explicit destination/token inputs, context cancellation, and structured + failure mapping. Use standard HTTP and JSON packages. +2. Verify the hosted tag targets the captured source commit. Find a release by + tag, create a missing stable release from the committed note, and verify + existing note body/tag and stable, non-draft state before accepting it. + Preserve the existing title. +3. Handle create conflicts and lost acknowledgments by one lookup and content + comparison. Never edit the existing page to make it match. +4. Add `docs/integrations/gitea.md` describing implemented API interactions, + credential supply, source binding, and failure semantics. + +Acceptance: `httptest.Server` covers found, missing, created, conflicting, +unauthorized, malformed, cancelled, and ambiguous-create results. A missing or +wrong hosted tag prevents page creation. Existing historical titles are retained +when the body, tag, and release state match. Fixture assertions cover actual +request paths, payloads, and authentication without using real credentials. + +## Stage 15: Inspect asset and manifest content + +Status: Pending. Depends on Stage 14. + +1. Generate the expected binary manifest from the declared build result. Add + attachment discovery and streaming hash comparison of existing assets. +2. Compare existing `SHA256SUMS` with the complete declared manifest. Identify + matching, missing, conflicting, and unverifiable content separately. +3. Inspect all declared conflicts before any upload and preserve unrelated + attachments. Keep download authorization confined to the configured origin. + +Acceptance: equal name/size with different bytes is conflict; matching content +is accepted; a full matching manifest can cover missing binaries; a subset or +different manifest is conflict; unreadable content is not treated as missing. +Source-only delivery generates no binary manifest. No write endpoint is used +by inspection tests. + +## Stage 16: Upload missing assets and report partial delivery + +Status: Pending. Depends on Stage 15. + +1. Add multipart uploads for missing binaries in filename order, followed by a + missing manifest. Record completed work and pending asset identities. +2. Resolve ambiguous uploads by one resource read and content comparison. + Handle concurrent matching uploads without creating replacement content; + detect duplicate/conflicting attachment names as conflicts. +3. Implement rerun behavior that accepts existing matching files, adds missing + ones, and stops on conflicts. Preserve every existing page and attachment. +4. Add tests where the first binary succeeds, a later write fails, and a second + invocation completes only the missing assets; also cover a lost manifest + acknowledgment and failure of the reconciliation read. + +Acceptance: partial and unknown outcomes retain exact completed/pending facts; +retries do not overwrite or delete content; no blind repeat POST is made after +an ambiguous acknowledgment. Published manifest constraints hold across reruns. + +## Stage 17: Expose tagged-source delivery through the facade and CLI + +Status: Pending. Depends on Stage 16. + +1. Implement `Deliver`, its facade-owned request/result values, and conversions. + Bind source note, resolved build settings, tag commit, and hosted repository + as specified. +2. Add delivery profile fields, environment-name token loading in the CLI, and + `deliver` with an optional verified assets directory. With no directory, + build tagged-source artifacts in temporary storage before uploading. +3. Accept detached tagged checkouts and historical tags without requiring old + remote main equality. Verify receipt identity/hashes before supplied assets + are used. Keep the library credential input explicit. +4. Add current consumer/CLI guidance for source status, delivery status, + retained assets, and retry commands. Extend public boundary checks. + +Acceptance: a facade/CLI end-to-end fixture builds tagged source and delivers to +a local server; a rerun is content-aware. Wrong commit, stale build settings or +receipt, tampered assets, and missing credentials prevent relevant writes. Candidate +tests and Git publication are not run by delivery. A delivery failure leaves +the existing source tag intact and reports only delivery failure. + +## Stage 18: Provide the common Woodpecker integration + +Status: Pending. Depends on Stage 17 and the delivery-capable tool pin. + +1. Add a maintained example tag pipeline using a pinned Go image, explicit + ReleaseKit source installation and cache preparation, artifact build, and + delivery with the configured secret. Explain how consumers pin their image + and tool source; create no new ReleaseKit image-distribution requirement. +2. Bind tag and commit variables to the checkout, fetch the selected tag when + necessary, share generated assets between steps, and keep output ignored. +3. Document preparation versus offline execution, asynchronous status, and + rerunning the same immutable tag. The workflow executes no publisher. +4. Exercise its shell command sequence locally against a fixture and local + server. Verify YAML with an available supported Woodpecker validator when + available; report that live runner behavior remains unverified otherwise. + +Acceptance: the maintained commands reach common build/delivery operations with +the selected version and commit, and secrets enter only the delivery step. +Static/manual YAML review and local command evidence are recorded accurately. +CI success is not a source release gate. The +[Woodpecker environment reference](https://woodpecker-ci.org/docs/usage/environment) +owns the runner-provided tag and commit variables. + +## Stage 19: Standardize Scriptorium runtime information and builds + +Status: Pending. Depends on Stage 18 and a released runtime dependency pin. + +1. Add the `buildinfo` dependency and canonical `scriptorium version` command + before configuration/provider initialization. Use graph discovery to locate + its CLI boundary; the earlier implementation has no release version command. +2. Set main package `./cmd/scriptorium`, binary name `scriptorium`, and its + existing Linux amd64/arm64 asset targets in the profile. +3. Replace the temporary local candidate build loop from Stage 13 with shared + candidate builds. Retain only product checks and a small version/wiring test. +4. Update current CLI contracts and compatibility impact documentation. + +Acceptance: shared builds report the injected version without credentials or +service access. The unchanged two-asset selection is used for candidate checks. +Runtime dependency checks show the application needs only `buildinfo` for this +responsibility; its ordinary suite passes. + +## Stage 20: Pilot common delivery in Scriptorium + +Status: Pending. Depends on Stage 19. + +1. Replace its duplicated Woodpecker build loop and release-plugin step with the + common example adapted to its pinned Go/tool versions and Gitea destination. +2. Keep the committed note as the hosted body and maintain the existing two + Linux assets and checksum deliverable. Add ignored local build output. +3. Remove its repeated asset builder code once local fixture evidence exercises + common build and retry delivery. Update its delivery runbook and tests. + +Acceptance: local fixture execution verifies first delivery, matching rerun, +partial delivery recovery, and conflicting bytes. The repository's source +publication path waits for no CI or attachments. Record live activation as a +separate maintainer action; preserve historical release workflows at old tags. + +## Stage 21: Migrate ConfigKit publication + +Status: Pending. Depends on Stage 20. + +1. Add a source-only profile and wrapper using the common publisher. Its current + sequential main/tag publication becomes main-first preparation and tag-only + publication. Update the adopted policy and maintainer runbook together. +2. Preserve its existing selected baseline-toolchain, test, vet, and build + checks in its candidate checker where its local policy justifies them. + These remain ConfigKit checks, not ReleaseKit-wide requirements. +3. Remove duplicated ref checks, annotated-tag creation, and push recovery. + Retain a small profile/wrapper test and run its suite. + +Acceptance: a local fixture refuses publication until authoritative main equals +the candidate, then publishes only the tag with one candidate-check invocation. +ConfigKit gains no runtime import, hosted page, or distributed binary. + +## Stage 22: Migrate Notarius publication + +Status: Pending. Depends on Stage 21. + +1. Add a source-only profile and common publication wrapper. Its existing + branch push and lightweight tag become prepared main plus annotated tag. + Update the adopted policy and procedure with that deliberate change. +2. Retain its existing explicit host version check temporarily in its local + checker. Remove repeated common release guards and shared publication tests. +3. Preserve a small wrapper/check invocation test and verify ordinary behavior. + +Acceptance: local remotes demonstrate the new publication approach and bounded +recovery. Source-only deliverables and the current version-check prerequisite +remain unchanged until the next stage. + +## Stage 23: Standardize Notarius runtime version access + +Status: Pending. Depends on Stage 22 and a released runtime dependency pin. + +1. Replace its private override/resolution mechanism with `buildinfo`. Preserve + its existing `--version` invocation and add the canonical `version` command + for common smoke checks, with `notarius ` output. +2. Document the common metadata/`dev` fallback and its deliberate effect on any + existing unversioned-build diagnostic behavior. Update owned tests/contracts + rather than silently preserving obsolete private override assumptions. +3. Replace the explicit host check's private linker flags with a small local + invocation of the shared Build API for the host target. This remains an + explicitly selected local check, not a distributed target declaration. + The API accepts explicit host Go targets as described above. + +Acceptance: both version invocations report the same injected value without +configuration or credentials, and the deliberate source-build behavior is +covered. The source-only profile still has no automatic binary targets or Gitea +page; the separately selected host check runs once with its candidate checker. + +## Stage 24: Migrate Audita publication + +Status: Pending. Depends on Stage 23. + +1. Replace atomic main/tag publication with main-first preparation and the + common annotated-tag publisher. Retain its `--check` user entry by mapping it + to common local `check`; map `--publish` to common publication. +2. Preserve current tests and its existing builder temporarily as selected + candidate checks. Update the local release policy and completion wording. +3. Transfer shared atomic/lightweight publication tests to the common contract, + retaining tests of the Audita command mapping and local checks. + +Acceptance: diagnostic mode performs no remote publication; publication refuses +an unprepared main and uses an annotated tag. The configured candidate sequence +runs once and existing asynchronous Linux deliverables remain selected. + +## Stage 25: Standardize Audita runtime information and builds + +Status: Pending. Depends on Stage 24 and a released runtime dependency pin. + +1. Add `audita version` using `buildinfo`, before application configuration and + provider initialization. The earlier CLI has no release-version accessor. +2. Declare `./cmd/audita`, binary name `audita`, and linux/amd64 plus linux/arm64. + Replace `scripts/build-release.sh` mechanics with shared candidate/build + operations and retain product-owned tests. +3. Add a small application version-command test and update current contracts. + +Acceptance: native smoke verifies the injected tag and needs no credentials; +both existing Linux assets retain their names/target selection. Audita's own +suite passes and duplicate candidate build work is removed. + +## Stage 26: Migrate Audita delivery + +Status: Pending. Depends on Stage 25. + +1. Adapt the common tag workflow to Audita's pinned image/tool, Gitea repository, + and secret name. Replace release-plugin skip-by-name behavior with shared + content verification and additive delivery. +2. Update delivery/retry documentation and remove unused build wrapper code. + +Acceptance: local first-run, rerun, conflicting-content, and partial-delivery +fixtures exercise the actual configured filenames and complete manifest. Source +completion and live activation remain separate from delivery verification. + +## Stage 27: Migrate Narratio publication + +Status: Pending. Depends on Stage 26. + +1. Retain main-first preparation and replace lightweight tags with common + annotated publication. Adapt its candidate checker and release entry point, + keeping its selected product checks and build mechanics temporarily. +2. Update policy/runbook and retire common version/note/ref helpers where their + behavior is now shared. Preserve remaining helper uses until Stage 28. + +Acceptance: local publication fixtures cover exact candidate/tag identity and +common recovery; the checker runs once. Narratio retains its three declared +asset targets and existing delivery pipeline until subsequent stages. + +## Stage 28: Standardize Narratio runtime information and builds + +Status: Pending. Depends on Stage 27 and a released runtime dependency pin. + +1. Replace its private buildinfo variable with the facade in its existing + `narratio version` command. Preserve output shape and update tests to use + supported behavior rather than mutating a private version variable. +2. Declare `./cmd/narratio`, binary name `narratio`, and linux/amd64, + linux/arm64, darwin/arm64. Use the prepared toolchain required by its module. +3. Replace `build-release-assets.sh` flags, host smoke, and target loop with + shared builds. Remove duplicate build invocation from its candidate checker. + Preserve product checks and remove helpers that now have no remaining use. + +Acceptance: the native version probe and all three cross-builds pass; foreign +platform compilation is reported without a native-execution claim. A nonempty +caller output directory can retain unrelated files, and no broad recursive +cleanup is inherited from the former builder. + +## Stage 29: Migrate Narratio delivery + +Status: Pending. Depends on Stage 28. + +1. Replace its release plugin and repeated tag-pipeline candidate validation + with the common prepared build/delivery workflow. Candidate validation belongs + to source publication; tagged delivery does not repeat it as a gate. +2. Keep its three binary assets, committed note, Gitea destination, and complete + checksum manifest. Adapt pinned images and tool versions to its Go baseline. +3. Update retry documentation and small repository-local integration checks. + +Acceptance: fixture workflow evidence verifies all three filenames, full +manifest, matching rerun, and partial/ambiguous delivery. Existing historical +tags/pages/assets are untouched and source publication waits for no delivery. + +## Stage 30: Audit the completed target state and documentation + +Status: Pending. Depends on Stage 29. + +1. Map each feature-roadmap responsibility to its implemented owner and tests. + Verify both public facades, source-only behavior, candidate checks, exact + publication, runtime injection, selected targets, hosted content verification, + partial recovery, and Woodpecker integration against current source. +2. Review the six migrations against both architecture success criteria: shared + infrastructure was removed and one coherent model was adopted. Count actual + removed scripts/tests, retained product checks, and new ReleaseKit code + separately; do not present the historical estimate as measured savings. +3. Check current documentation owners, links, examples, dependency attribution, + license texts, and maintainer runbooks. Record live integration verification + separately from local acceptance, including anything awaiting activation. +4. Update feature status and this plan from evidence. Retire completed execution + details during release preparation while preserving target intent, deferred + decisions, and useful navigation. + +Acceptance: each roadmap responsibility has direct source and behavioral +evidence, each migrated repository retains only justified product checks and +small wiring coverage, and no unimplemented or unverified responsibility is +marked complete. Run affected suites according to their policies; add no new +permanent release matrix or synchronization gate. + +## Coverage of the feature roadmap + +| Target responsibility | Implementing stages | +| --- | --- | +| Two public facades and small runtime dependencies | 1–3, extended in 10–11 and 17 | +| Stable version and committed note identity | 5 | +| Candidate capture, one validation, mutable guards | 5–8, 11 | +| Authoritative endpoint and remote ref state | 6–8 | +| Annotated object publication and failure recovery | 7–9 | +| ReleaseKit source-only bootstrap/runbook | 9 | +| Shared version injection and selected cross-builds | 2, 10–11 | +| Native smoke, empty target sets, consistent names | 10–11 | +| Hosted page/note creation and immutable content | 14–17 | +| Full checksums, comparison, partial/additive retries | 15–17 | +| Woodpecker tag integration and separate statuses | 18, 20, 26, 29 | +| PromptKit and Scriptorium pilots | 12–13, 19–20 | +| ConfigKit, Notarius, Audita, Narratio adoption | 21–29 | +| Actual infrastructure reduction and canonical docs | 30 | + +## Open Questions + +The core implementation choices above are complete for the recommended design. +These maintainer decisions affect adoption and live use; do not fill in unknown +release pins or silently broaden deliverables. + +### Which immutable ReleaseKit versions should consumers pin? + +Required before Stage 12, and refreshed before Stage 18 and runtime migrations. + +**Recommended:** publish a first source release such as `v0.1.0` after Stage 11, +then a delivery-capable source release after Stage 17. Verify version availability +at preparation time and use the actual published pins in profiles' accompanying +installation instructions, CI provisioning, and application module dependencies. +This gives the publication pilots an early usable tool and lets later delivery +and runtime adopters depend on documented versioned contracts. Publication is a +separate maintainer-authorized action under the release policy. + +**Viable alternative:** make immutable source-commit/pseudo-version pins available +for the pilots and publish the first numbered release after Stage 17. This +supports integration feedback before a numbered release, at the cost of more +operator bookkeeping and less friendly installation instructions. Neither +approach commits local checkout replacements or claims an unpublished version +exists. Record the selected pins here before dependent migration stages proceed. + +### Should source-only projects also receive hosted Gitea release pages? + +This decision is required only if adopting pages for PromptKit, ConfigKit, +Notarius, or ReleaseKit; their current plan stages preserve source-only delivery +without hosted-page requirements. + +**Recommended:** retain their existing delivery scope initially. Support explicit +zero-asset hosted delivery in ReleaseKit, while using the three existing binary +publishers to establish its behavior. This meets the requested standardization +without adding credentials, CI operations, or a new deliverable to other projects. + +**Viable alternative:** declare a hosted note page for every project, with zero +binary assets where appropriate. This gives uniform forge presentation and uses +the same delivery API, but needs deliberate local release-policy changes, +credentials, and responsibility for retries. If selected, add a small adoption +stage for each affected project before completing Stage 30. diff --git a/docs/roadmap/releasekit.md b/docs/roadmap/releasekit.md index 12423e6..eb7ce6b 100644 --- a/docs/roadmap/releasekit.md +++ b/docs/roadmap/releasekit.md @@ -4,6 +4,8 @@ This is a proposal based on a review of PromptKit, ConfigKit, Audita, Notarius, Narratio, and Scriptorium on 2026-09-29. Product capabilities remain proposed. The public facade architecture is accepted in [ADR 0001](../adr/0001-public-facade.md), with implementation pending. +The [implementation plan](implementation.md) owns ordered stages, execution +instructions, acceptance checks, and progress toward this target state. ## Opportunity @@ -175,17 +177,23 @@ ReleaseKit should report source publication and artifact delivery separately: | Woodpecker or Gitea delivery fails after tag publication | Keep the source release successful. Retry missing delivery from the immutable tag after checking existing content. | | Published source or content needs correction | Publish a new version; never replace content under the published version. | -## First implementation slice +## Consumer adoption target -Pilot the common publication path with PromptKit and Scriptorium, which already -require `main` to equal the candidate and publish annotated tags. Develop the -pilot through the root `releasekit` facade, with implementation behind its -internal boundary and tests exercising the supported consumer contract. Their -different validation and remote configuration will test the proposed boundary. -Migrate the other projects after the shared Git contract is exercised against -temporary remotes. Add common delivery only after publication behavior is -settled, using one of the existing binary-publishing projects as the first -delivery pilot. +All six projects should use the same selected publication, runtime version, +build, and delivery conventions where those responsibilities apply. Repository +entry points should contain only tool invocation and product-specific settings +or checks. Shared mechanics and their repeated tests belong in ReleaseKit. + +Application runtime code should obtain version information through `buildinfo`. +Source-only libraries need no runtime import or automatic binary deliverable. +Projects declaring binaries should use the common target selection and version +injection; projects declaring hosted delivery should use content-aware retries. +Existing published tags, notes, pages, and assets retain their historical contents. + +Consumer policy and procedure changes should describe the deliberately adopted +standard and its compatibility impact. Adoption should demonstrate reduced local +infrastructure and one coherent release model. The implementation plan owns +pilot selection, migration order, and acceptance evidence. ## Reviewed implementations