Skip to content

Latest commit

 

History

History
745 lines (610 loc) · 36.9 KB

installation.md

File metadata and controls

745 lines (610 loc) · 36.9 KB

Installation

Code Polishy runs from one verified native release built from an exact reviewed source version. The source tag, portable archive, and Linux OCI image all bind to that same commit and internal release digest. This page describes source and portable installation, how a target selects the exact release it requires, and what makes that release acceptable to run. Target configuration is covered in Adoption.

Agent-first workflow

The normal user experience starts with the one bootstrap request in the root README. After adoption, the installed AGENTS.md owns recurring operating guidance.

The agent follows AI-Agent Setup and Adoption. It preserves an existing target lock. For a new adoption it honors a caller-specified exact tag or resolves the highest strict stable v<MAJOR>.<MINOR>.<PATCH> tag, then makes a temporary shallow clone of that tag. The clone must prove that the selected ref is annotated, points directly at HEAD, and matches VERSION. The default branch supplies current instructions; it is never an installable release.

Adoption pins the canonical publication-index URL and its separately published SHA-256 when it writes the repository lock. This records the exact native archive for every host without embedding a release in the target repository. An unpublished source checkout can build or recover an installation, but it cannot become repository lock authority.

A private repository URL or clean local checkout may be supplied explicitly for private development. The same exact-source rules apply, and the agent never substitutes another repository or revision. Published tags are immutable: never move or reuse one after consumers can select it.

Repository wrapper

Adoption installs a small POSIX wrapper and PowerShell wrapper in the target repository. A developer can prepare a fresh clone without finding an installer or changing PATH:

./code-polishyw setup
.\code-polishyw.ps1 setup

The wrapper reads .code-polishy.lock.json. If that exact release already exists in the default shared user prefix, setup verifies and reuses it without network access. A v2 lock otherwise supplies the exact HTTPS URL, SHA-256, and size for every supported host. Setup selects this host, downloads and checks that archive, and invokes the archive's engine to perform the bounded bundle installation. It does not need Git, a language toolchain, or a local build.

An explicit clean local checkout is also available as a recovery path for the exact release already named by a v2 lock:

./code-polishyw setup --source <local-code-polishy-checkout>

The source installer must still prove that its result satisfies the target lock. The wrapper never silently clones or falls back to source.

After setup, use ./code-polishyw <command> or .\code-polishyw.ps1 <command>. Ordinary dispatch verifies the exact installed release through the stable launcher and performs no clone, download, build, fallback selection, or PATH mutation. The wrappers contain only bootstrap and dispatch logic; the complete release remains in the shared user store.

Optional Rust tools

The default portable installation contains Rust policy and its small syntax parser, but omits the Rust SDK and Nextest binaries. A repository with governed Rust inputs still requires the corresponding checks. Missing tools produce a blocking setup instruction; optional installation cannot disable analysis.

Install the companion selected by the repository's existing release lock:

./code-polishyw install-rust

For an offline installation, use the matching published companion archive:

./code-polishyw install-rust --source /path/to/code-polishy-VERSION-rust-HOST.zip

Setup verifies every companion file against the authenticated core release's inventory. The companion cannot choose another version, alter policy, or change the repository lock. Ordinary checks never download tools or use ambient rustup installations. Modified, incomplete, or removed tools block Rust operations; rerunning install-rust repairs the optional installation. Repositories sharing an exact core installation also share its Rust tools.

Source builds prepare the complete tool inventory for both archives. The native source installer retains that developer installation; the portable release archive is the default distribution without Rust executables. Both archives retain their notice and dependency-inventory obligations.

Rust beta scope

The beta supports contained Cargo projects on the native host, using the pinned toolchain and a reviewed Cargo.lock. Ordinary checks cover selected formatting, compiler and Clippy diagnostics, written-source complexity and comment policy, supported module dependencies, and Cargo dependency policy. Native suites cover unit/integration tests through Nextest and separately declared doctests. Empty or wholly skipped execution cannot satisfy a required test suite.

Architecture analysis covers written modules, imports, aliases and re-exports, plus supported standard macro arguments. Custom macros, non-builtin attribute expansion, ambiguous or unresolved bindings and unsupported module layouts fail coverage explicitly. This beta does not provide general macro-expanded semantic analysis. Compiler checks use the native host and default feature configuration; test suites can declare their own supported package and feature selections. Project Nextest configuration and unsupported Cargo, rustfmt or Clippy settings produce findings rather than silently changing policy or execution.

Install the official companion before checking Rust code. Missing, damaged or wrong-version tools remain blocking errors; choosing beta support does not change required analysis. Host linkers and SDKs are still required as described below. See Cargo facts, Rust architecture and Rust test declarations for the exact supported boundaries and examples.

The companion contains the official checksum-pinned Rust and Nextest binaries. Their upstream notices and reviewed source license evidence are carried with the core. The release SBOM explicitly marks native transitive dependencies incomplete: upstream does not provide a complete binary dependency attestation, so the beta cannot promise exhaustive vulnerability coverage inside those tools. Target Cargo projects still receive the ordinary required dependency checks.

Native source installers

Source acquisition stays outside the installers. They never select a version, fetch, pull, switch revisions, invoke gh, or call a GitHub API. They require a clean Git checkout, read its exact commit, and build only what is already there. Maintainers can therefore use the same installers for a clean candidate commit before its version tag exists.

From a verified checkout on Linux or macOS:

./tools/install-policy-tools.sh
./scripts/install.sh

From a verified checkout on Windows x64, in PowerShell:

.\tools\install-policy-tools.ps1
.\scripts\install.ps1

./scripts/install.sh --require-repository <target> and .\scripts\install.ps1 -RequireRepository <target> add a pre-publication constraint for wrapper-driven installation. The completed staged release and all its bytes must satisfy that target's exact lock before installation begins.

Windows requires Git and PowerShell but does not need WSL or Git Bash. The PowerShell installer locally builds the same deterministic native ZIP format, verifies its checksum and complete release manifest, installs it atomically, and deletes its temporary archive.

When Windows does not grant symlink creation privilege, pnpm materializes its isolated dependency graph with absolute directory junctions. The tool installer therefore builds that graph only at its stable transaction-owned location and removes incomplete output on failure; moving a completed graph from a different staging path would leave its junction targets pointing at the wrong tree. The native release builder uses the same frozen lock and offline store to ask the pinned pnpm for its portable hoisted layout, converts its remaining links to regular entries, writes the bundle manifest, and proves the staged runner can report provenance before creating the ZIP. The portable layout is confined to the sealed release bundle and never resolves dependencies from a target.

The tool installer is the only networked installation stage. It admits only the checked-in pinned tool artifacts, verifies every archive before extraction, builds the pinned Go analyzers with the pinned Go toolchain, and installs the JavaScript graph from its frozen lock with lifecycle scripts disabled. The source installer then refuses to run until the sealed runtime, bundle, and every other pinned tool are present and verified. Its manifest records every staged byte, so the launcher verifies the carried tools with the rest of the release before it runs.

The release gate separately resolves every standalone tool pin against its fixed upstream publication metadata and enforces the shared 30-day minimum. Checksum verification proves which bytes were acquired; release-age admission proves the selected upstream release has matured or carries one exact, expiring assessment.

Portable archives

Each supported host publishes one deterministic ZIP, internal manifest, CycloneDX 1.6 SBOM, deterministic in-toto/SLSA provenance metadata, and machine-readable release descriptor. This local metadata binds inputs and outputs but does not authenticate a builder or publisher. The five hosts are darwin-arm64, darwin-x64, linux-arm64, linux-x64, and windows-x64. The descriptor binds every accompanying metadata file, the archive SHA-256, host-specific content digest, shared release digest, version, and source revision.

From a clean Linux or macOS release checkout with tools already installed:

./scripts/build-release.sh --output /absolute/path/to/publication

On Windows x64:

.\scripts\build-release.ps1 -Output C:\release\code-polishy.zip -PublicationDirectory C:\release\publication

Each command refuses an existing destination and publishes atomically. After all native builders finish, combine the five descriptors with repeated --artifact-descriptor options:

code-polishy release-manifest index \
  --artifact-descriptor /release/darwin-arm64/*.release.json \
  --artifact-descriptor /release/darwin-x64/*.release.json \
  --artifact-descriptor /release/linux-arm64/*.release.json \
  --artifact-descriptor /release/linux-x64/*.release.json \
  --artifact-descriptor /release/windows-x64/*.release.json \
  --output /release/code-polishy-release-index.json

The index is written only when every descriptor and sidecar validates, all five hosts appear exactly once, and every host names one version, source commit, and release digest. The command prints the SHA-256 of the exact canonical index; publish that digest beside its HTTPS URL for repository adoption and upgrade planning. Downloading and digest custody belong to the caller or CI. Given already acquired bytes and their trusted SHA-256, installation remains local:

code-polishy install-bundle \
  --source /absolute/path/code-polishy-<version>-<host>.zip \
  --sha256 <archive-sha256> \
  --prefix /opt/code-polishy

The installer accepts no URL, tag, redirect, or relative source. It verifies the archive digest, host, complete internal manifest, and launcher before atomically publishing the release and stable launcher.

Digest-pinned OCI execution

The version-tag release workflow builds the Linux image from its already verified publication archive instead of creating a second release tree. It reuses the archive's engine to prepare the OCI context, while Buildx emits SBOM and provenance attestations. The workflow resolves and pulls the registry's exact image digest, then exercises the installed launcher as the image's declared user before reporting success.

Tags aid discovery only. Every workflow and invocation uses the reported ghcr.io/riteofstring/code-polishy@sha256:... identity. The image runs as non-root user 65532, starts in /workspace, keeps the stable launcher on PATH, and verifies its internal release on every command.

A GitLab job may use the public canonical image directly. The package currently publishes Linux x86-64, and a public pull needs no GHCR credentials:

policy:
  image:
    name: ghcr.io/riteofstring/code-polishy@sha256:<index-digest>
    entrypoint: [""]
  script:
    - code-polishy merge-gate --base "$CI_MERGE_REQUEST_DIFF_BASE_SHA"

The equivalent GitHub Actions job sets container: ghcr.io/riteofstring/code-polishy@sha256:<index-digest> and runs the same command after checkout. A private or self-managed GitLab repository does not make this public image private; the runner needs only outbound GHCR access. Code Polishy needs no GitLab account, API token, monitoring provider, or machine-global runner installation. The version-tag release workflow publishes the native archives and this image together; see the Release Checklist.

The Unix prefix defaults to ~/.local/share/code-polishy; the Windows prefix defaults to %LOCALAPPDATA%\CodePolishy. Releases are installed under <prefix>/releases, and the stable launcher is installed under <prefix>/bin. The default Unix install also creates the guarded command link ~/.local/bin/code-polishy. It refuses to replace an unrelated path there.

The installers report command discovery and print a session-local PATH command when needed. Persistent PATH setup is explicit:

./scripts/install.sh --add-to-path
.\scripts\install.ps1 -AddToUserPath

On Unix, --add-to-path adds one owned entry to the supported shell startup file for future shells. --path-profile <file> selects a specific startup file when needed. On Windows, -AddToUserPath adds the launcher directory to the user PATH for future processes. Repeating either operation is idempotent.

A custom Unix --prefix alone keeps installation writes under that prefix. Add --command-dir <directory> to create a guarded command link elsewhere. code-polishy doctor reports command discovery for an installed release.

What a release contains

One release identity has one self-contained policy root per supported host:

  • the code-polishy binary built from the reviewed commit with the pinned Go toolchain;
  • the sealed Node runtime and JavaScript tool bundle;
  • Tree-sitter's pinned Rust syntax libraries and policy-owned formatter/Clippy settings; the optional companion supplies Rust 1.97.1, Cargo, rustdoc, rustfmt, Clippy, the host standard library and Nextest 0.9.138;
  • every other pinned tool the engine runs: the Go toolchain, ShellCheck, staticcheck, deadcode, govulncheck, OSV-Scanner, Ruff, PyPA packaging 26.3, Vulture 2.16, ty, and the carried CPython 3.12.13+20260728 runtime from python-build-standalone. A target installs no policy tooling, and none of these is ever taken from an ambient PATH, a host installation, or an environment override, so a check decides the same thing on every machine that has the matching host release;
  • the version-matched README.md, CHANGELOG.md, permanent docs/ tree, and documentation catalog;
  • the configuration schema, templates, canonical guidance, pinned tool versions, and native workflow contracts the engine reads at runtime;
  • the canonical repository wrapper templates installed during adoption;
  • the bundle's dependency and license inventory;
  • the launcher, which the installer also copies to <prefix>/bin/code-polishy; the default Unix installer links ~/.local/bin/code-polishy to that stable path; and
  • release-manifest.json, which records everything above.

From a managed repository, read its exact locked guides without locating the release directory:

code-polishy docs list
code-polishy docs find dependency review
code-polishy docs read installation

The files remain available below <prefix>/releases/<version>-<releaseDigest>/docs/. Documentation and catalog bytes are recorded in the release manifest like runtime inputs, so a changed, missing, or added file makes the installed release invalid. Agent guidance is delivered through generated root files and the versioned documentation CLI.

Language packs

Language packs are installed separately from engine releases and never change .code-polishy.lock.json. From a repository already using the intended locked engine release, verify and install a reviewed local source directory:

code-polishy pack verify --source ./code-polishy-example
code-polishy pack install --source ./code-polishy-example
code-polishy pack root

Installation prints the exact name, semantic version, and content digest to add under the target configuration's packs array. It performs no download and executes no pack code. See code-polishy docs read adding-a-language for the manifest, protocol, trust, and contributor contracts.

A release carries no source checkout, no history, and no build inputs. Installing does not modify the checkout it was built from.

The release manifest

The manifest answers two different questions with two different digests.

releaseDigest names which reviewed commit, which version, which capabilities, and the exact version of every executable the release carries. Nothing host specific contributes to it, so Linux, macOS, and Windows releases from the same commit carry the same value and a target lock can require an exact release without naming a platform. Their entry lists and contentDigest values differ because executable formats and other carried bytes are host specific.

contentDigest and the per-entry list name the exact installed bytes on this host. Every installed file contributes its SHA-256. On hosts that support release symlinks, every link contributes its exact target: the bundle is linked together by pnpm's isolated linker, so retargeting one link would otherwise swap the code a release runs without changing an installed file.

Each manifest version has an exact schema and identity calculation. Ordinary engine execution accepts only its own version. The stable launcher keeps explicit readers for historical installed manifest versions 2 through 6 so installing a newer release does not strand repositories still locked to an older one. It verifies each older release using that version's original fields and never invents evidence added by a later schema. Upgrade planning and application reuse those readers only to verify the outgoing release; the incoming release must use the current schema.

Manifest version 4 adds tools.python for the carried CPython 3.12.13+20260728 runtime and tools.vulture for Vulture 2.16. Those fields contribute only to version 4 identities; a version 3 release remains bound to the smaller tool inventory it originally recorded. The target configuration version is a separate contract.

Manifest version 5 adds tools.packaging for PyPA packaging 26.3. That field contributes only to version 5 identities; launchers continue validating older manifests against the exact smaller inventory of their own version.

Manifest version 6 adds capabilityCatalogSha256, the SHA-256 of the shipped docs/capabilities.json. This digest contributes to the host-independent release identity and must match the catalog's regular-file entry. Discovery validates the selected catalog against the repository's exact lock; another installed release or a modified catalog cannot supply its capabilities. Older releases with no authenticated catalog report discovery evidence unavailable.

Manifest version 7 adds tools.rust and tools.cargo-nextest. Each native release authenticates the pinned Rust SDK, host standard library, and Nextest runner in its optional companion; target repositories install them with install-rust. Rust parser libraries reside in the carried Python runtime with their exact dependency manifest, lock, and distribution metadata. Changing either executable pin changes the cross-host release identity.

code-polishy release-manifest verify --root <release-dir> recomputes the core entry evidence. Optional Rust tool integrity is required before Rust execution. A core release that was truncated, changed after installation, or copied from another host fails verification and is reinstalled rather than executed. The launcher makes the same native judgment before every run on Linux, macOS, and Windows.

Every policy-owned carried-Python command uses isolated mode and disables bytecode generation at one shared command boundary. Version probes, Vulture, quality checks, architecture checks, doctor, and gates therefore cannot create __pycache__ or .pyc entries inside the content-addressed release. Installed release acceptance runs these commands sequentially and verifies the complete manifest afterward, including rejection of any added entry. Writable caches, homes, temporary files, and target output remain outside the release tree.

Installing is all-or-nothing

Before it stages anything, the installer asks every tool the release will carry what version it is and requires the answer to be the version the checked-in pin beside it names — the Go toolchain, Node, pnpm, ShellCheck, staticcheck, deadcode, govulncheck, OSV-Scanner, Ruff, Rust/Cargo/rustdoc, Nextest, PyPA packaging, Vulture, ty, and carried CPython. The manifest records those identities, and a present file and a byte inventory cannot show that a local tool cache holds the version the manifest would claim. The two Go analyzers are read out of their binaries with the pinned toolchain rather than asked: govulncheck -version contacts the vulnerability database, and installation reaches no network. The engine and the launcher are built here from the reviewed commit rather than acquired, so what they are is the source revision the manifest already records.

The installer verifies the CPython archive before extraction and the exact packaging 26.3 and Vulture 2.16 wheels before unpacking their pure-Python packages into that carried runtime. It removes pip and ensurepip and does not use a target .venv or target Python installation for either package.

Rust target builds also need the host's native linker and SDK: a C toolchain on Linux, Xcode command-line tools on macOS, or the Visual Studio C++ build tools and Windows SDK for MSVC. Run Windows Rust checks from an x64 Visual Studio Developer PowerShell session so Microsoft's linker and SDK libraries are active. A regular shell can select an unrelated link.exe supplied by Unix tools. These host prerequisites are separate from the carried Rust SDK; Code Polishy does not install system development packages. See the Rust platform prerequisites.

The installer stages a complete release, verifies the staged tree against the manifest it just wrote, and only then moves it into place under one name. A previously installed release is untouched until a verified replacement is ready.

An installation that ends after staging has begun — a step that fails, or an interrupt — removes its own partial tree. A staging tree is never a release a target could run, because no lock can name one, but a release store holds only complete releases.

Before the manifest is written, the installer searches the staged tree for retired distribution mechanisms that must never reach a target: a check_policy.sh or .gitmodules entry, a checkout, a retired workflow wrapper, or policy-owned text that still tells a target to run a submodule command. A release is the whole Code Polishy interface a target gets, so one of those reaching a target through a template, canonical guidance, or workflow script is an installation failure rather than something to find later. The sealed runtime and bundle are not searched for Code Polishy's own retired commands; they are third-party bytes, and the bundle inventory and manifest govern them.

Release directories are named <version>-<releaseDigest>, so releases from different reviewed commits coexist and each target selects the exact one it requires. Reinstalling a commit whose release is already installed and verified keeps the installed bytes rather than replacing them.

Removing unused releases

Installed releases stay until they are removed. Each one carries its complete toolchain, so a prefix that has accumulated many versions can occupy tens of gigabytes. Remove every release that no known repository still locks with:

./code-polishyw cleanup --dry-run
./code-polishyw cleanup

Every command an installed release runs for a repository records that repository's path in <prefix>/repositories.json. The file records paths, not versions: cleanup rereads each recorded repository's current lock, so a pull, branch switch, or checkout that changes a lock outside Code Polishy cannot make it stale. Cleanup keeps the release each lock names, removes every other release directory, and forgets recorded repositories that no longer have a lock. It reports what it kept and for which repositories, what it removed, and the space freed. --dry-run reports the same plan without changing anything. Recording and cleanup take a lock file in the prefix, so concurrent commands cannot drop one another's entries and no repository can be recorded while cleanup decides what to keep. Cleanup holds that lock only until the releases it removes are moved aside, so other commands do not wait while it deletes them.

Cleanup removes nothing when the registry or a recorded repository's lock cannot be read, because it cannot then tell which releases are in use; repair the named file, or remove a registry that cannot be repaired. It leaves the stable launcher, language packs, and in-progress installations in place. A release is first moved out of <prefix>/releases and then deleted, so an interrupted removal never leaves a partial release under its name; the next cleanup finishes the deletion.

A repository that has never run a command through an installed release that records repositories, such as a fresh clone or one still locked to a release that predates cleanup, is unknown to cleanup, and its release may be removed. Cleanup reads only the lock each recorded checkout has now, so the release of another branch is not kept either. Its next wrapper call reports that the locked release is not installed, and ./code-polishyw setup reinstalls it. Cleanup can likewise remove a candidate that upgrade plan installed before upgrade apply replaced the lock; plan the upgrade again to reinstall it.

Removing repository outputs

Select generated repository outputs explicitly:

./code-polishyw cleanup --reports --artifacts --dry-run
./code-polishyw cleanup --reports --artifacts

Use either scope alone or combine them. These flags select repository outputs instead of installed releases. The dry run lists selected paths, logical byte sizes, and retained entries; actual cleanup reports the removed paths and total. Run repository cleanup after other Code Polishy commands finish.

--reports removes completed managed report trees, completed gate executions and their logs, and the test receipt cache under .code-polishy-reports. Deleting cached gate results and test receipts makes verification rerun as needed. Cleanup preserves behavior-review and architecture-review evidence, capability-upgrade records, incomplete gate runs, and files outside recognized generated trees. Keep personal notes and saved evidence outside generated report trees and the test-receipts cache. Use behavior-review cleanup when you intend to remove its retained review evidence.

--artifacts removes completed executions under .code-polishy-artifacts using the same ownership checks as automatic test artifact retention. Active leases, invalid ownership records, unowned files, and trees containing symbolic links or special entries are retained. Abandoned runs with active leases are also retained. Artifact-only cleanup preserves reports and test receipts.

Both scopes refuse linked output roots and preserve trees containing symbolic links or special entries. Missing output directories require no action.

The target lock

A published target repository names the release and complete native archive set it requires in .code-polishy.lock.json:

{
  "lockVersion": 2,
  "codePolishyVersion": "0.27.2",
  "releaseDigest": "…",
  "features": ["javascript-bundle"],
  "publication": {
    "indexUrl": "https://example.invalid/code-polishy-release-index.json",
    "indexSha256": "…",
    "archives": [
      {
        "host": "darwin-arm64",
        "url": "https://example.invalid/code-polishy-0.27.2-darwin-arm64.zip",
        "sha256": "…",
        "size": 123
      }
    ]
  }
}

The real archives value contains five structured entries rather than the single example entry above. It carries no credential, channel, or fallback version. The publication-index digest and copied archive digests are reviewed repository authority, so the same lock selects one release across all supported hosts. This authenticates acquired bytes relative to the lock; it is not authenticated builder provenance.

lock --index URL --sha256 DIGEST verifies that the index describes the executing installed release and writes this v2 lock without redownloading an archive. Every new lock requires publication metadata. Move between v2 releases with the two-phase upgrade:

code-polishy upgrade plan --index <https-index-url> --sha256 <index-sha256>
code-polishy upgrade apply --plan <reported-plan-path>

Planning verifies the selected index, installs the host candidate, authenticates the capability delta, and compares outgoing and incoming diagnostics without changing repository authority. Applying the plan revalidates its evidence and replaces managed guidance, wrappers, ignore rules, and the lock in one rollback-capable transaction with the lock last. New errors can be acknowledged without fixing them by adding --accept-new-findings. Neither phase edits application source, runs tests, or runs a merge gate.

When the outgoing release has a v2 lock but predates upgrade or cannot read the incoming manifest schema, bootstrap through the incoming release. Obtain the exact publication index and verify its separately published SHA-256. Download its current-host archive and verify the archive's SHA-256 against that index before extracting or executing it. For checksum verification, use sha256sum --check on Linux, shasum -a 256 --check on macOS, or Get-FileHash on Windows.

Use the verified archive's own engine for local installation, with the same prefix that contains the outgoing release:

<verified-extracted-release>/bin/code-polishy install-bundle \
  --source /absolute/path/code-polishy-<version>-<host>.zip \
  --sha256 <archive-sha256> --prefix <installation-prefix>
<installation-prefix>/releases/<version>-<release-digest>/bin/code-polishy \
  --repo-root <repository> upgrade plan \
  --index <https-index-url> --sha256 <index-sha256> --prefix <installation-prefix>
<installation-prefix>/releases/<version>-<release-digest>/bin/code-polishy \
  --repo-root <repository> upgrade apply --plan <reported-plan-path>

On Windows, use code-polishy.exe and PowerShell invocation syntax. Invoke both phases through the installed incoming engine, since the repository wrapper and stable launcher still select the outgoing lock. The outgoing lock and guidance govern until apply replaces the lock; the incoming release governs afterward.

Replacing a pre-v2 lock

Current releases do not parse or migrate v1 locks. For a repository still using one, an AI agent should preserve the working tree, install the exact published incoming release, and obtain its publication-index URL and SHA-256. It should then preserve the old lock through Git or a temporary file outside the repository, remove .code-polishy.lock.json, and invoke the incoming release directly:

<incoming-code-polishy> lock --index <https-index-url> --sha256 <index-sha256>
<incoming-code-polishy> agents sync

The agent then reviews the intervening changelog and target configuration, performs the checks selected for the upgrade, and commits the v2 lock with the managed adoption updates. This is a deliberate authority replacement, not an automated migration; Git retains the rollback path.

Selecting a release

<prefix>/bin/code-polishy is the launcher, and it is the only Code Polishy command a target runs. It reads the target's lock, resolves the one release that lock names, and confirms that release records the same version, digest, and required features and was built for this host.

It then verifies the release itself before running any of it. It recomputes the release the manifest describes from the commit, version, capabilities, and pins recorded in it, so a release cannot be installed or copied under another release's name; it reads every recorded file and confirms the recorded bytes, reads every recorded link and confirms the recorded target, and refuses anything installed under the release that the release does not record. Only then does it hand the release the target and the caller's arguments. The engine reads the schema, templates, pinned versions, workflow scripts, and sealed JavaScript bundle beside it, so checking the engine binary alone would not be checking what runs, and verifying at installation time cannot answer what a release is made of now.

There is no channel, version range, newest-wins rule, or fallback release. Ordinary launch never downloads. A target that names a release this host does not have is told to run its repository wrapper setup; it is never given a different release. --policy-root is refused, because the lock decides which release runs.

Running a release binary directly does not get around the lock: an installed release refuses to govern a repository whose lock names another release, or a repository with no lock at all. The exceptions are bounded bootstrap and upgrade commands plus read-only version, help, and packaged documentation.

An installed release is not a supported development environment, and the source runner in this repository's bin/ is not a supported target installation. It is not a release, so no target lock can name it.

Self-hosting

This repository checks in its own .code-polishy.lock.json, so the release it produces is exercised against a real target rather than only against fixtures: the launcher resolves that release, verifies every recorded byte, and governs this repository with the pinned tools the release carries.

A release digest names the commit it was built from, so this repository's lock names the release of an earlier reviewed commit and never of the commit that carries the lock: writing the lock changes the tree, and the tree it changes would build a different release. Refresh it the way any target does — install a release from the reviewed commit, run lock in this checkout from that release, and commit the lock as its own reviewed change — and keep that release installed for as long as the lock names it, because the launcher resolves that one and no other.

Development still runs the source runner. This repository's AGENTS.md uses the same canonical prefix every target receives, so the installed release governs the checkout without a guidance exception. agents sync replaces that prefix when a later locked release changes the canonical contract, preserves valid project principles, and repairs the root .gitignore rules that keep report and test artifacts workspace-local.

Exercising an installed release

Self-hosting covers one target shape: a Go repository that also owns the sealed bundle's source. ./scripts/test-installed-release.sh covers the others. It builds disposable repositories — Go with no JavaScript, a pnpm application, a pnpm workspace, TypeScript, React, Python and Rust — gives each the lock this checkout requires, and governs them with the installed launcher. The Go target also runs the complete non-documentation flow: pre-coding intent capture, review preparation, red/green proof, finalization, checkpoint, and merge. It verifies requested and preserved changes and confirms that an unintended change blocks.

Each target is first brought to a clean pass, then given one defect and required to produce the exact finding for it, because a pass that no defect can disturb is a check that did not run. The script installs nothing and reaches no network; it needs an installed release named by the selected lock. --prefix selects the release store, and --lock can select a temporary candidate lock without changing this repository's checked-in lock.

--fixture rust-adoption selects the dependency-free Cargo fixture. It exercises the carried formatter, compiler and parser, JSON/SARIF findings, structured native test outcomes, empty-test rejection, and recovery after correcting a failure.

What it proves is the release the selected lock names. By default that is this checkout's lock, which must be refreshed before validating a newly published release. A temporary lock can name a clean installed candidate before the public cutover. The script is not part of the ordinary test suite because a release store is outside the checkout and CI normally builds the engine from source.