Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 17 additions & 5 deletions docs/extensions/diagnostics-linter-layer.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,19 @@ Current state:
provider; it remains the F4 linter microservices reference
implementation and its Python behavior is unchanged by this migration.
- Every other first-class and legacy/optional linter (22 in total) has its
own microservice directory with `manifest.py` and `package_contract.py`
only -- no `provider.py`, no execution, no output parsing.
own microservice directory with `manifest.py` and `package_contract.py`.
Implemented providers/parsers remain separate from provisioning; the
provisioning layer consumes only manifest/package-contract metadata and does
not run F4 diagnostics.
- `src/ecli/extensions/linters/__init__.py` aggregates every
microservice's `manifest.MANIFEST` into `LINTER_CATALOG` and exposes
`get_linter(name)`, `iter_linters()`, `linters_for_language(language)`.
- No new wiring into `DiagnosticsService` beyond Ruff, no output parsing
for non-Ruff tools, and no F4 UI change of any kind.
- `src/ecli/extensions/linters/core/provisioning_contract.py`,
`provisioning_registry.py`, and `provisioning.py` implement the
provider-neutral Full-install provisioning model, installer component data,
dry-run evidence, and evidence verification. This is packaging/release
contract logic only: no F4 UI change, no package-manager calls from F4, and
no provider/parser behavior change.

See `docs/extensions/diagnostics-model.md` for the normalized `Diagnostic`
contract every provider must produce, and
Expand Down Expand Up @@ -266,7 +272,7 @@ reserves the identifier.

## ECLI Full vs. minimal install

Two install shapes are anticipated (not implemented in this change):
Two install shapes are represented in the provisioning contract:

- **Minimal install** — the editor only. Ruff works out of the box because
it is bundled (`provider_kind="internal"`). Every other entry in the
Expand Down Expand Up @@ -303,6 +309,12 @@ allowed only with explicit source URL, version pinning, checksum verification
where available, clear provenance in `package_contract.py`, no silent
unverified execution, and deterministic install logs.

The repository exposes this contract through
`scripts/provision_f4_linters.py` and
`scripts/verify_f4_linter_provisioning.py`. `dry-run` planning is deterministic
and safe for CI; real missing-tool provisioning still belongs to
artifact-specific installer/package flows.

### The PyPI limitation

ECLI is published as a Python wheel on PyPI. Python wheels can only
Expand Down
4 changes: 4 additions & 0 deletions docs/release/README-release.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ required tools before provisioning, installation or bundling of missing required
linters/toolchains, executable checks, version probes, and provenance/checksum
evidence for bundled or GitHub/upstream downloaded binaries, JARs, and
tarballs. A missing required linter after ECLI Full install is a release blocker.
The provider-neutral entrypoints are `scripts/provision_f4_linters.py` and
`scripts/verify_f4_linter_provisioning.py`; they write and verify
`f4-linter-provisioning-<artifact-entry-id>.json` release evidence without
changing F4 runtime behavior.

Authoritative files:
- `artifact-contract.md`
Expand Down
13 changes: 13 additions & 0 deletions docs/release/artifact-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,17 @@ blocker and a packaging defect, not normal user workflow. Manual linter
installation documentation is only for developer checkouts, PyPI/source/minimal
installs, damaged-install repair, and advanced administration.

The provider-neutral release evidence entrypoints are
`scripts/provision_f4_linters.py` and
`scripts/verify_f4_linter_provisioning.py`. The provisioner builds the
artifact-specific component model and writes
`f4-linter-provisioning-<artifact-entry-id>.json` evidence. It is safe by
default: no network, no upstream downloads, no package-manager execution, and
no unverified binary execution. The verifier enforces the required Full tool
set for Full-capable entries, validates all 21 evidence files for release-level
checks, and ignores GitHub-generated source archives because they are outside
the canonical 21 artifact contract entries.

The PyPI wheel and source distribution entries are constrained by Python
packaging metadata: they cannot reliably provision Node, Rust, Go, Zig, Java,
or system binaries. Their release contract must document that limitation
Expand Down Expand Up @@ -408,6 +419,8 @@ scripts and release contract tests.
| `scripts/verify_artifact.py` | Structural SHA256 sidecar verifier; exit-code contract `0`-`5` preserved |
| `scripts/verify_release_assets.py` | Read-only exact 21 ECLI-owned GitHub Release asset verifier; ignores `.checksums/` only when it is a directory |
| `scripts/verify_runtime.py` | Cross-artifact launcher validation; exit codes (`0`/`2`/`3`/`4`/`5`/`6`) preserved |
| `scripts/provision_f4_linters.py` | Provider-neutral F4 linter provisioning planner/evidence writer; dry-run by default, no network/upstream downloads unless explicitly allowed |
| `scripts/verify_f4_linter_provisioning.py` | Read-only F4 linter provisioning evidence verifier for one artifact or all 21 canonical artifact entries |
| `scripts/build_pyinstaller_linux.py` | Linux PyInstaller build; prefers `packaging/pyinstaller/ecli.spec` |
| `scripts/build_and_package_deb.py` | `ecli_<version>_linux_<arch>.deb` via FPM; dependency set preserved |
| `scripts/build_and_package_rpm.py` | `ecli_<version>_<platform>_<arch>.rpm`; `RPM_PLATFORM_LABEL`/`RPM_DEPENDS` env preserved |
Expand Down
13 changes: 13 additions & 0 deletions docs/release/artifact-verification.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,19 @@ Use the canonical 21 ECLI-owned asset verifier before publication:
uv run python scripts/verify_release_assets.py
```

Use the F4 provisioning evidence verifier before declaring a Full artifact
release-ready:

```sh
uv run python scripts/verify_f4_linter_provisioning.py --all-artifacts --evidence-dir <evidence-dir>
```

Provisioning evidence is written by:

```sh
uv run python scripts/provision_f4_linters.py --artifact deb --target-dir <target-dir> --evidence-dir <evidence-dir> --mode dry-run --json
```

The verifier reads the version from `pyproject.toml`, validates
`releases/<version>/`, fails when the directory is missing, fails on missing or
extra top-level ECLI-owned files, and passes only when the exact 21 ECLI-owned
Expand Down
2 changes: 2 additions & 0 deletions docs/release/build-matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ detection, detection of already-installed required tools before install,
installation or bundling of missing required tools, executable checks, version
probes, and provenance/checksum evidence for bundled or upstream-downloaded
tools.
`scripts/provision_f4_linters.py` emits the per-entry evidence, and
`scripts/verify_f4_linter_provisioning.py` verifies one entry or all 21 entries.

- Linux: DEB/RPM/openSUSE RPM/Arch/Slackware/AppImage scripts and workflows

Expand Down
11 changes: 11 additions & 0 deletions docs/release/packaging-flows.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,15 @@ installation is a release blocker. Manual linter installation remains a
developer checkout, PyPI/source/minimal install, damaged-install repair, or
advanced administration path only.

The shared provider-neutral entrypoints are
`scripts/provision_f4_linters.py` and
`scripts/verify_f4_linter_provisioning.py`. Packaging flows may call the
planner in dry-run/verify-only mode to emit deterministic
`f4-linter-provisioning-<artifact-entry-id>.json` evidence. Concrete
package-manager or bundle installation remains the responsibility of each
artifact-specific flow and must stay non-interactive for CI and unattended
package-manager installs.

## Mandatory GitHub Release Assets

```text
Expand Down Expand Up @@ -135,6 +144,8 @@ list and migration rules live in `docs/release/artifact-contract.md` under
Canonical Python entrypoints include `scripts/sign_checksums.py`,
`scripts/check_log_invariant.py`, `scripts/verify_artifact.py`,
`scripts/verify_release_assets.py`, `scripts/verify_runtime.py`,
`scripts/provision_f4_linters.py`,
`scripts/verify_f4_linter_provisioning.py`,
`scripts/build_pyinstaller_linux.py`, `scripts/build_and_package_deb.py`,
`scripts/build_and_package_rpm.py`,
`scripts/build_and_package_opensuse_rpm.py`, `scripts/build_and_package_arch.py`,
Expand Down
4 changes: 4 additions & 0 deletions docs/release/release-checklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ See the LICENSE file in the project root for full license text.
detects already-installed required tools before installing missing tools,
verifies executable availability and version probes, and includes
deterministic provisioning evidence.
- [ ] `scripts/provision_f4_linters.py --all-artifacts --mode dry-run` writes
21 deterministic `f4-linter-provisioning-<artifact-entry-id>.json`
evidence files, and
`scripts/verify_f4_linter_provisioning.py --all-artifacts` verifies them.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
- [ ] F4 linter package-manager dependencies: package metadata asserts the
dependency relationship and post-install executable availability for each
required linter/toolchain dependency it delegates to the OS package
Expand Down
4 changes: 1 addition & 3 deletions scripts/build_and_package_arch.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,7 @@ def stage_pkgbuild(root: Path, build_dir: Path, version: str) -> Path:
source tree is never used as a makepkg working directory.
"""
source = (root / "packaging" / "arch" / "PKGBUILD").read_text(encoding="utf-8")
rewritten = re.sub(
r"(?m)^pkgver=.*$", f"pkgver={version}", source, count=1
)
rewritten = re.sub(r"(?m)^pkgver=.*$", f"pkgver={version}", source, count=1)
target = build_dir / "PKGBUILD"
target.write_text(rewritten, encoding="utf-8")
return target
Expand Down
Loading
Loading