diff --git a/AGENTS.md b/AGENTS.md index 6da5e37a..29416233 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -56,6 +56,28 @@ Kingfisher is a Rust secret scanner, live credential validator, revocation helpe - After markdown/doc changes, verify local documentation links when practical. - After `docs-site/` source changes, rebuild with `docs-site/.venv/bin/mkdocs build -f docs-site/mkdocs.yml` when practical so generated output stays in sync. +## Crate Versions And Publication + +- Before finishing changes to a publishable crate, check whether its packaged contents + differ from the same version on crates.io. Packaged tests, examples, README files, + dependency requirements, licenses, and generated data count, even without runtime/API changes. +- Crate versions are independent of the CLI tag and Python SDK version. Bump an + already-published crate when its package changes; do not bump every crate for a CLI + release or bump an unpublished prepared version unnecessarily. Choose patch/minor/major + versions according to the public API compatibility contract. +- Update dependent minimum requirements and `Cargo.lock` with each internal crate bump. + Recheck dependent packages too: changing their published manifests can require their + own version bumps. Regenerate rule-bundle provenance after manifest/lockfile changes; + never hand-edit its hashes or bypass the immutable-package comparison. +- Build fresh archives with `cargo package --locked` for `kingfisher-core`, + `kingfisher-rules`, `kingfisher-scanner`, and `kingfisher-bin`, then run the read-only + `python3 scripts/publish-crates.py --tag v` plan before declaring a release + ready. Keep package verification enabled; `--allow-dirty` is only for local pre-commit checks. +- For a metadata-only correction after a successful binary release, commit and merge + it into `main`, then run `make release crates` from a clean, current `main` checkout. + Preserve the existing public tag and artifacts. Runtime changes need a new binary + release, not a broader recovery exception. See [publishing guidance](docs/PUBLISHING.md). + ## Cross-Platform Tests - New and modified tests must work on Windows (x64 and arm64), macOS, and Linux. Review fixture setup and subprocess calls for platform assumptions even when developing on Unix. diff --git a/CHANGELOG.md b/CHANGELOG.md index b90e52a0..36f6c480 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,11 +49,14 @@ All notable changes to this project will be documented in this file. requests also refuse redirects; configure final canonical URLs instead. - Prepare Python SDK **1.6.0** with explicit analysis and source-attribute scanning, and rules/scanner crates **3.0.0** for public Rust schema changes. Existing - YAML/TOML rule files and Python APIs remain compatible; core stays at **1.0.4**. + YAML/TOML rule files and Python APIs remain compatible; core uses the compatible **1.0.5** patch. Rust callers should follow the [3.0 migration guide](docs/LIBRARY.md#migrating-to-rules-and-scanner-30). ### Improvements +- Correct crates.io release metadata for the packaged core regression tests and + verify the regenerated rules-manifest hash during crates-only recovery. Verified + metadata-only recovery does not require another Python SDK release. - Reduce malformed-container and recognized-placeholder false positives with conservative, bounded offline private-key checks shared by the CLI and SDKs. These checks do not establish cryptographic or live validity. See the diff --git a/Cargo.lock b/Cargo.lock index 38d34532..578bbc41 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7997,7 +7997,7 @@ dependencies = [ [[package]] name = "kingfisher-core" -version = "1.0.4" +version = "1.0.5" dependencies = [ "anyhow", "bstr", diff --git a/Cargo.toml b/Cargo.toml index 3ebbfedd..88fe72fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -117,7 +117,7 @@ gpui-kit = { version = "=0.6.6", optional = true } command-group = { version = "5.0.1", optional = true } image = { version = "0.25", default-features = false, features = ["png"], optional = true } # Library crates -kingfisher-core = { version = "1.0.4", path = "crates/kingfisher-core" } +kingfisher-core = { version = "1.0.5", path = "crates/kingfisher-core" } kingfisher-rules = { version = "3.0.0", path = "crates/kingfisher-rules" } kingfisher-scanner = { version = "3.0.0", path = "crates/kingfisher-scanner", features = ["validation", "archives", "context", "extraction", "git", "__cli-internals"] } diff --git a/crates/kingfisher-core/Cargo.toml b/crates/kingfisher-core/Cargo.toml index 9574fd2f..d50bee71 100644 --- a/crates/kingfisher-core/Cargo.toml +++ b/crates/kingfisher-core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "kingfisher-core" -version = "1.0.4" +version = "1.0.5" description = "Core types and traits for Kingfisher secret scanner" edition.workspace = true rust-version.workspace = true diff --git a/crates/kingfisher-core/README.md b/crates/kingfisher-core/README.md index 95e8bcfa..ce750955 100644 --- a/crates/kingfisher-core/README.md +++ b/crates/kingfisher-core/README.md @@ -6,7 +6,7 @@ semantic versioning; breaking changes require a new major version. ```toml [dependencies] -kingfisher-core = "1.0.4" +kingfisher-core = "1.0.5" ``` ```rust diff --git a/crates/kingfisher-rules/Cargo.toml b/crates/kingfisher-rules/Cargo.toml index cb78abe8..b71297b4 100644 --- a/crates/kingfisher-rules/Cargo.toml +++ b/crates/kingfisher-rules/Cargo.toml @@ -22,7 +22,7 @@ __scanner-internals = [] [dependencies] # Internal dependencies -kingfisher-core = { version = "1.0.4", path = "../kingfisher-core" } +kingfisher-core = { version = "1.0.5", path = "../kingfisher-core" } # Serialization serde.workspace = true diff --git a/crates/kingfisher-rules/README.md b/crates/kingfisher-rules/README.md index 38fce644..2163b8c0 100644 --- a/crates/kingfisher-rules/README.md +++ b/crates/kingfisher-rules/README.md @@ -101,7 +101,7 @@ These records document what was imported; they do not establish upstream authors Use rules/scanner `3.0.0` together. Existing YAML/TOML rule files remain compatible; public Rust schema fields and enum variants require source updates. See the [3.0 migration guide](../../docs/LIBRARY.md#migrating-to-rules-and-scanner-30). -Core stays at 1.0.4; Python SDK 1.6.0 preserves its existing Python API. +Core uses the compatible 1.0.5 patch; Python SDK 1.6.0 preserves its existing Python API. ## Migrating to 2.0 diff --git a/crates/kingfisher-rules/generated/provenance.json b/crates/kingfisher-rules/generated/provenance.json index eb355f48..166ea215 100644 --- a/crates/kingfisher-rules/generated/provenance.json +++ b/crates/kingfisher-rules/generated/provenance.json @@ -20,10 +20,10 @@ }, "bundle_format": "gzip / KFRULES v1 / converted YAML", "generator_input_sha256": { - "Cargo.lock": "366caf1b91c054c61214f99542ff47cb27dcac8698dfcbe12c83e14c851c9ff8", - "Cargo.toml": "2e2eb92b537116ca4ef4f31a432b9744aff138c05976e8cb59d00be57970a73e", + "Cargo.lock": "b3d5780ad992ae01aec14c8c6e6436d54b5fe83bb832ed064d537cb4981d5133", + "Cargo.toml": "49aee40b7285ac3e1be70f7a8bb403d13f4b066b3956aa3ed0b2c98f4188aa7e", "LICENSE": "43070e2d4e532684de521b885f385d0841030efa2b1a20bafb76133a5e1379c1", - "crates/kingfisher-rules/Cargo.toml": "c83c5aa9e2e2e9790f6eb032d0e97b481b9054b33e74b07cd59609ab1ac49b17", + "crates/kingfisher-rules/Cargo.toml": "a4312ea684367264638bc6c6aa5379697828b1e3e93b6ee024337b37608a9311", "crates/kingfisher-rules/build_support/betterleaks.rs": "831d97ea9edcfbb841b7ef5b962e48a6d3ed26ed4c2e85109ac2b49f9425ebfa", "crates/kingfisher-rules/build_support/builtin_docs.rs": "3edb2b444acfb2a7ade1ba7f6e1a3cbac14e6a743e01156293683fcdb4a77225", "crates/kingfisher-rules/build_support/imported_capabilities.rs": "2ddeaf7ffdcbedb55d1ccb13eb010ad04a5f451cecd91fd418033c1390242914", diff --git a/crates/kingfisher-scanner/Cargo.toml b/crates/kingfisher-scanner/Cargo.toml index 38eeeed9..7d7de185 100644 --- a/crates/kingfisher-scanner/Cargo.toml +++ b/crates/kingfisher-scanner/Cargo.toml @@ -109,7 +109,7 @@ validation-all = ["validation"] [dependencies] # Internal dependencies -kingfisher-core = { version = "1.0.4", path = "../kingfisher-core" } +kingfisher-core = { version = "1.0.5", path = "../kingfisher-core" } kingfisher-rules = { version = "3.0.0", path = "../kingfisher-rules", features = ["__scanner-internals"] } # Error handling diff --git a/crates/kingfisher-scanner/README.md b/crates/kingfisher-scanner/README.md index c44fe66d..0c40ec53 100644 --- a/crates/kingfisher-scanner/README.md +++ b/crates/kingfisher-scanner/README.md @@ -61,7 +61,7 @@ and [rule format guide](../../docs/RULES.md). Use rules/scanner `3.0.0` together. Existing YAML/TOML rule files remain compatible; public Rust schema fields and enum variants require source updates. See the [3.0 migration guide](../../docs/LIBRARY.md#migrating-to-rules-and-scanner-30). -Core stays at 1.0.4; Python SDK 1.6.0 preserves its existing Python API. +Core uses the compatible 1.0.5 patch; Python SDK 1.6.0 preserves its existing Python API. ## Migrating to 2.0 diff --git a/docs-site/docs/changelog.md b/docs-site/docs/changelog.md index b8fb70af..117fc2b1 100644 --- a/docs-site/docs/changelog.md +++ b/docs-site/docs/changelog.md @@ -7,9 +7,7 @@ description: "Kingfisher release history: new features, rules, bug fixes, and im All notable changes to this project will be documented in this file. -## [v2.15.0] (Unreleased) - -Changes since the published v2.14.0 release. +## [v2.15.0] ### Added @@ -56,11 +54,14 @@ Changes since the published v2.14.0 release. requests also refuse redirects; configure final canonical URLs instead. - Prepare Python SDK **1.6.0** with explicit analysis and source-attribute scanning, and rules/scanner crates **3.0.0** for public Rust schema changes. Existing - YAML/TOML rule files and Python APIs remain compatible; core stays at **1.0.4**. + YAML/TOML rule files and Python APIs remain compatible; core uses the compatible **1.0.5** patch. Rust callers should follow the [3.0 migration guide](reference/library.md#migrating-to-rules-and-scanner-30). ### Improvements +- Correct crates.io release metadata for the packaged core regression tests and + verify the regenerated rules-manifest hash during crates-only recovery. Verified + metadata-only recovery does not require another Python SDK release. - Reduce malformed-container and recognized-placeholder false positives with conservative, bounded offline private-key checks shared by the CLI and SDKs. These checks do not establish cryptographic or live validity. See the diff --git a/docs-site/docs/reference/library.md b/docs-site/docs/reference/library.md index 3be151b6..bca09875 100644 --- a/docs-site/docs/reference/library.md +++ b/docs-site/docs/reference/library.md @@ -7,7 +7,7 @@ description: "Embed secret detection, live validation, and explicit revocation i For in-process Python detection, validation and revocation, see the [Python SDK](../reference/python-bindings.md). -The prepared library releases are **kingfisher-core 1.0.4**, +The prepared library releases are **kingfisher-core 1.0.5**, **kingfisher-rules 3.0.0**, and **kingfisher-scanner 3.0.0**. They require Rust **1.99** or newer and are versioned independently of the `kingfisher-bin` CLI, currently **2.15.0**. See [publishing](https://github.com/mongodb/kingfisher/blob/main/docs/PUBLISHING.md). @@ -881,7 +881,7 @@ runner. The first published `1.0.0` established the stable contract. The rules and scanner -3.0.0 releases start their next stable major; core stays on the compatible 1.x API at 1.0.4. Within each +3.0.0 releases start their next stable major; core stays on the compatible 1.x API at 1.0.5. Within each stable major: - Existing reachable public Rust items, signatures, fields, trait implementations, @@ -912,7 +912,7 @@ behavior; review and behavioral tests remain required. ### Migrating to rules and scanner 3.0 Upgrade both crates to `"3.0.0"`; scanner 3.x exposes rules 3.x types. -Existing YAML/TOML files and Python APIs remain compatible. Core stays at 1.0.4; +Existing YAML/TOML files and Python APIs remain compatible. Core uses the compatible 1.0.5 patch; CLI 2.15.0 and Python SDK 1.6.0 are independently versioned. - `HttpRequest` literals need `fallback_urls: Vec::new()`; `url` remains a string. diff --git a/docs/LIBRARY.md b/docs/LIBRARY.md index c9a0d15b..1f6e4429 100644 --- a/docs/LIBRARY.md +++ b/docs/LIBRARY.md @@ -4,7 +4,7 @@ For in-process Python detection, validation and revocation, see the [Python SDK] [← Back to README](../README.md) -The prepared library releases are **kingfisher-core 1.0.4**, +The prepared library releases are **kingfisher-core 1.0.5**, **kingfisher-rules 3.0.0**, and **kingfisher-scanner 3.0.0**. They require Rust **1.99** or newer and are versioned independently of the `kingfisher-bin` CLI, currently **2.15.0**. See [publishing](PUBLISHING.md). @@ -878,7 +878,7 @@ runner. The first published `1.0.0` established the stable contract. The rules and scanner -3.0.0 releases start their next stable major; core stays on the compatible 1.x API at 1.0.4. Within each +3.0.0 releases start their next stable major; core stays on the compatible 1.x API at 1.0.5. Within each stable major: - Existing reachable public Rust items, signatures, fields, trait implementations, @@ -909,7 +909,7 @@ behavior; review and behavioral tests remain required. ### Migrating to rules and scanner 3.0 Upgrade both crates to `"3.0.0"`; scanner 3.x exposes rules 3.x types. -Existing YAML/TOML files and Python APIs remain compatible. Core stays at 1.0.4; +Existing YAML/TOML files and Python APIs remain compatible. Core uses the compatible 1.0.5 patch; CLI 2.15.0 and Python SDK 1.6.0 are independently versioned. - `HttpRequest` literals need `fallback_urls: Vec::new()`; `url` remains a string. diff --git a/docs/PUBLISHING.md b/docs/PUBLISHING.md index 1f2256e5..7681258e 100644 --- a/docs/PUBLISHING.md +++ b/docs/PUBLISHING.md @@ -20,7 +20,7 @@ The manifests currently prepare these releases; a Git tag does not override Carg | Package | Version | Published targets | | ------- | ------- | ----------------- | -| `kingfisher-core` | `1.0.4` | `kingfisher_core` library | +| `kingfisher-core` | `1.0.5` | `kingfisher_core` library | | `kingfisher-rules` | `3.0.0` | `kingfisher_rules` library | | `kingfisher-scanner` | `3.0.0` | `kingfisher_scanner` library | | `kingfisher-bin` | `2.15.0` | `kingfisher` executable and library | @@ -30,8 +30,9 @@ The Python SDK (`kingfisher-secret-scanner`) is independently versioned at The libraries follow semantic versioning and do not inherit the CLI version. Rules/scanner 3.0.0 add public Rust schema fields and Expr variants; upgrade both -crates together. Existing YAML/TOML files remain compatible. Core is unchanged at -1.0.4. Python SDK 1.6.0 adds compatible APIs for explicit analysis and source attributes. +crates together. Existing YAML/TOML files remain compatible. Core 1.0.5 packages +the added regression tests without changing its public API. Python SDK 1.6.0 adds +compatible APIs for explicit analysis and source attributes. See the [3.0 migration guide](LIBRARY.md#migrating-to-rules-and-scanner-30). `kingfisher-rule-bundle` is `0.1.0` with `publish = false`. The external `kingfisher-vectorscan` dependency is versioned separately. @@ -273,21 +274,27 @@ lockfile, provenance and documentation corrections into `main`, then dispatch: ```sh gh workflow run release.yml --repo mongodb/kingfisher --ref main \ - -f crates_only=true -f release_run_id=39339690046 \ + -f crates_only=true -f release_run_id=40610865292 \ -f expected_sha="$(git rev-parse HEAD)" ``` Use the original binary release's run ID. The recovery gate requires its successful build/test/release jobs, verifies the public tag, and rejects runtime source, dependency, feature or catalog changes. Only the core patch version and corresponding dependency, -lockfile and provenance metadata may change; the independent SDK version correction +lockfile and provenance metadata may change. Regenerated root manifest, lockfile and +rules-manifest hashes must match their committed files; all other provenance stays +unchanged. The independent SDK version correction from 1.4.0 to 1.5.0 and its matching lockfile entry are also allowed. Library tests and all four extracted-crate build checks still run before authentication and publication. The ordinary release path retains its exact tag/commit guards. A runtime change needs a new binary release. +The Python SDK preflight reuses the same strict metadata check, so a verified core +version/dependency-metadata correction does not require another SDK release. Actual +SDK/shared runtime or unrelated dependency changes still require a new SDK version. + The original failed run uses its original commit and remains historical; rerunning it cannot pick up a later metadata correction. The new recovery run publishes the missing -crate versions without retagging `v2.14.0` or republishing its assets. +crate versions without retagging `v2.15.0` or republishing its assets. ## Independent terminal commands @@ -321,7 +328,7 @@ publishing, first create fresh archives: ```sh cargo package --locked -p kingfisher-core -p kingfisher-rules -p kingfisher-scanner -p kingfisher-bin -python3 scripts/publish-crates.py --tag v2.14.0 +python3 scripts/publish-crates.py --tag v2.15.0 python3 -m unittest discover -s scripts/tests -v ``` diff --git a/scripts/check-crates-recovery.py b/scripts/check-crates-recovery.py index fe42068b..eab8e9d5 100644 --- a/scripts/check-crates-recovery.py +++ b/scripts/check-crates-recovery.py @@ -22,18 +22,20 @@ DEPENDENTS = ("Cargo.toml", "crates/kingfisher-rules/Cargo.toml", "crates/kingfisher-scanner/Cargo.toml") PROVENANCE = "crates/kingfisher-rules/generated/provenance.json" -PROVENANCE_INPUTS = ("Cargo.toml", "Cargo.lock") +# The core requirement bump also changes the rules manifest's recorded input hash. +# Manifest semantics are restricted above the provenance comparison in check_metadata. +PROVENANCE_INPUTS = ("Cargo.toml", "Cargo.lock", "crates/kingfisher-rules/Cargo.toml") PLATFORMS = ("Linux x64", "Linux arm64", "macOS x64", "macOS arm64", "Windows x64", "Windows arm64") REQUIRED_JOBS = {"Verify release ref and version", "Public GitHub Release", *PLATFORMS} | { f"tests / {platform}" for platform in PLATFORMS } MAINTENANCE_FILES = { - "README.md", "CHANGELOG.md", "docs/LIBRARY.md", "docs/PUBLISHING.md", + "AGENTS.md", "README.md", "CHANGELOG.md", "docs/LIBRARY.md", "docs/PUBLISHING.md", "crates/kingfisher-core/README.md", "crates/kingfisher-rules/README.md", "crates/kingfisher-scanner/README.md", "docs-site/docs/changelog.md", "docs-site/docs/reference/library.md", ".github/RELEASING.md", ".github/workflows/release.yml", "scripts/check-crates-recovery.py", - "scripts/tests/test_crates_recovery.py", "Makefile", + "scripts/tests/test_crates_recovery.py", "scripts/tests/test_publish_crates.py", "Makefile", "scripts/release-packages.py", "scripts/tests/test_release_packages.py", "scripts/check-sdk-release.py", "scripts/tests/test_sdk_preflight.py", "scripts/tests/test_python_sdk_release.py", "scripts/tests/test_release_makefile.py", @@ -120,7 +122,7 @@ def toml(commit, path): raise ValueError(f"Recovery provenance does not match {path}; regenerate the bundle") after["generator_input_sha256"][path] = before["generator_input_sha256"][path] if before != after: - raise ValueError("Recovery changes rule-bundle provenance beyond workspace manifest/lock hashes") + raise ValueError("Recovery changes rule-bundle provenance beyond approved metadata input hashes") def github_json(path, token): diff --git a/scripts/check-sdk-release.py b/scripts/check-sdk-release.py index 8bdded0e..b45db05a 100644 --- a/scripts/check-sdk-release.py +++ b/scripts/check-sdk-release.py @@ -9,6 +9,7 @@ means an unused version; an existing empty or yanked release is still reserved. """ import argparse +import importlib.util import json import os from pathlib import Path @@ -29,6 +30,11 @@ SHA = re.compile(r"[0-9a-f]{40}\Z") SHARED_CRATES = ("kingfisher-python", "kingfisher-core", "kingfisher-rules", "kingfisher-scanner") +_recovery_spec = importlib.util.spec_from_file_location( + "crates_recovery", ROOT / "scripts/check-crates-recovery.py") +crates_recovery = importlib.util.module_from_spec(_recovery_spec) +_recovery_spec.loader.exec_module(crates_recovery) + def sdk_version(root=ROOT): with (root / MANIFEST).open("rb") as stream: @@ -130,6 +136,16 @@ def runtime_path(path): def runtime_changed(root, base, head): paths = git(root, "diff", "--name-only", "-z", "--no-renames", base, head, "--").split("\0") + if "crates/kingfisher-core/Cargo.toml" in paths: + # Reuse the strict recovery gate, not a blanket exception for manifest or + # version edits. Source/dependency drift or unverified provenance falls + # back to the normal SDK runtime checks and still requires a new release. + try: + crates_recovery.check_metadata(root, base, head) + except (ValueError, KeyError, TypeError, OSError, subprocess.CalledProcessError): + pass + else: + return False if any(runtime_path(path) for path in paths): return True if "Cargo.lock" in paths: diff --git a/scripts/tests/test_crates_recovery.py b/scripts/tests/test_crates_recovery.py index 74b6a12f..b96c0d2a 100644 --- a/scripts/tests/test_crates_recovery.py +++ b/scripts/tests/test_crates_recovery.py @@ -81,10 +81,13 @@ def write_metadata(self, version): self.regenerate_provenance() def regenerate_provenance(self): - provenance = {"generator_input_sha256": { - path: hashlib.sha256((self.root / path).read_bytes()).hexdigest() - for path in recovery.PROVENANCE_INPUTS}, - "artifact_sha256": {"builtin-rules.gz": "unchanged"}, "rules": {"unchanged": True}} + # Match the real generator inputs independently of the recovery allowlist. + inputs = {path: hashlib.sha256((self.root / path).read_bytes()).hexdigest() + for path in ("Cargo.toml", "Cargo.lock", "crates/kingfisher-rules/Cargo.toml")} + inputs["tools/rule-bundle/src/main.rs"] = "unchanged" + provenance = {"generator_input_sha256": inputs, + "artifact_sha256": {"builtin-rules.gz": "unchanged"}, + "rules": {"unchanged": True}} self.write(recovery.PROVENANCE, json.dumps(provenance)) def api(self, path, token): @@ -112,6 +115,14 @@ def test_metadata_only_recovery_accepts_failed_downstream_run(self): self.assertEqual(self.validate(), "v2.14.0") self.assertEqual(self.git("rev-parse", "v2.14.0"), self.base) + def test_accepts_regenerated_rules_manifest_hash_for_core_requirement_bump(self): + before = json.loads(self.git("show", f"{self.base}:{recovery.PROVENANCE}")) + after = json.loads((self.root / recovery.PROVENANCE).read_text(encoding="utf-8")) + path = "crates/kingfisher-rules/Cargo.toml" + self.assertNotEqual(before["generator_input_sha256"][path], + after["generator_input_sha256"][path]) + recovery.check_metadata(self.root, self.base) + def sdk_version_change(self, version="1.5.0", lock_version=None): self.write(recovery.SDK, (self.root / recovery.SDK).read_text(encoding="utf-8").replace("1.4.0", version)) lock = (self.root / "Cargo.lock").read_text(encoding="utf-8") @@ -154,8 +165,9 @@ def test_rejects_other_sdk_manifest_and_lock_fields(self): recovery.check_metadata(self.root, self.base) def test_release_maintenance_allowlist_is_explicit(self): - for path in ("Makefile", "scripts/release-packages.py", "scripts/check-sdk-release.py", + for path in ("AGENTS.md", "Makefile", "scripts/release-packages.py", "scripts/check-sdk-release.py", "scripts/tests/test_release_packages.py", "scripts/tests/test_release_makefile.py", + "scripts/tests/test_publish_crates.py", "scripts/tests/test_sdk_preflight.py", "scripts/tests/test_python_sdk_release.py", ".github/workflows/python-sdk.yml", "docs/PYPI.md", "python/README.md", "docs-site/docs/reference/python-bindings.md"): @@ -245,13 +257,14 @@ def test_rejects_lockfile_dependency_or_checksum_drift(self): def test_rejects_stale_or_unrelated_provenance_changes(self): original = json.loads((self.root / recovery.PROVENANCE).read_text(encoding="utf-8")) - for field in ("input", "artifact"): - with self.subTest(field=field): + for section, path in (("generator_input_sha256", "Cargo.toml"), + ("generator_input_sha256", "Cargo.lock"), + ("generator_input_sha256", "crates/kingfisher-rules/Cargo.toml"), + ("generator_input_sha256", "tools/rule-bundle/src/main.rs"), + ("artifact_sha256", "builtin-rules.gz")): + with self.subTest(section=section, path=path): changed = copy.deepcopy(original) - if field == "input": - changed["generator_input_sha256"]["Cargo.toml"] = "stale" - else: - changed["artifact_sha256"]["builtin-rules.gz"] = "changed" + changed[section][path] = "changed" self.write(recovery.PROVENANCE, json.dumps(changed)) self.commit() with self.assertRaisesRegex(ValueError, "provenance"): diff --git a/scripts/tests/test_publish_crates.py b/scripts/tests/test_publish_crates.py index d9391d01..d2cc45a2 100644 --- a/scripts/tests/test_publish_crates.py +++ b/scripts/tests/test_publish_crates.py @@ -109,6 +109,14 @@ def test_changed_source_requires_version_bump(self): with self.assertRaisesRegex(ValueError, "Bump its version"): publisher.verify_existing(local, "kingfisher-core", "1.0.0", {"checksum": hashlib.sha256(remote).hexdigest()}) + def test_added_packaged_regression_tests_require_version_bump(self): + remote = crate() + local = crate(**{"src/blob/identity_tests.rs": b"#[test] fn identity_is_stable() {}"}) + with patch.object(publisher, "fetch", return_value=remote): + with self.assertRaisesRegex(ValueError, r"src/blob/identity_tests\.rs.*Bump its version"): + publisher.verify_existing(local, "kingfisher-core", "1.0.0", + {"checksum": hashlib.sha256(remote).hexdigest()}) + def test_provenance_exception_is_limited_to_workspace_manifest_hashes(self): def rules(root_hash, source_hash, bundle): provenance = {"generator_input_sha256": {"Cargo.toml": root_hash, "Cargo.lock": root_hash, diff --git a/scripts/tests/test_sdk_preflight.py b/scripts/tests/test_sdk_preflight.py index eb2cdbd6..a780f397 100644 --- a/scripts/tests/test_sdk_preflight.py +++ b/scripts/tests/test_sdk_preflight.py @@ -1,4 +1,5 @@ """Read-only SDK preflight and early release DAG regression tests.""" +import hashlib import importlib.util import io import json @@ -308,6 +309,58 @@ def lock(cli="1.0.0", core="1.0.0"): 'dependencies = ["kingfisher-core"]\n[[package]]\nname = "kingfisher-core"\n' f'version = "{core}"\n[[package]]\nname = "cli-only"\nversion = "{cli}"\n') + def core_metadata_recovery(self): + commits = [] + for version in ("1.0.4", "1.0.5"): + self.write("crates/kingfisher-core/Cargo.toml", + f'[package]\nname = "kingfisher-core"\nversion = "{version}"\n') + for name, path, package_version in ( + ("kingfisher-bin", "Cargo.toml", "2.14.0"), + ("kingfisher-rules", "crates/kingfisher-rules/Cargo.toml", "2.0.0"), + ("kingfisher-scanner", "crates/kingfisher-scanner/Cargo.toml", "2.0.0")): + self.write(path, f'[package]\nname = "{name}"\nversion = "{package_version}"\n' + '[dependencies]\n' + f'kingfisher-core = {{ version = "{version}", path = "core" }}\n') + self.write("Cargo.lock", self.lock(core=version)) + inputs = {path: hashlib.sha256((self.root / path).read_bytes()).hexdigest() + for path in ("Cargo.toml", "Cargo.lock", "crates/kingfisher-rules/Cargo.toml")} + self.write("crates/kingfisher-rules/generated/provenance.json", json.dumps({ + "generator_input_sha256": inputs, "artifact_sha256": {"bundle": "unchanged"}, + })) + commits.append(self.commit()) + return commits + + def test_verified_core_metadata_recovery_does_not_require_sdk_release(self): + base, head = self.core_metadata_recovery() + self.assertFalse(preflight.runtime_changed(self.root, base, head)) + for kind, event in (("push", {"before": base}), + ("pull_request", {"pull_request": {"base": {"sha": base}}})): + with self.subTest(event=kind), \ + patch.object(preflight, "publication_source", return_value=(kind == "push", False)), \ + patch.object(preflight, "version_exists", return_value=True): + plan = preflight.workflow_plan(self.root, environment(kind, GITHUB_SHA=head), event, VERSION) + self.assertEqual(plan, {"build": kind == "pull_request", "publish": False, "version": VERSION}) + + def test_core_metadata_recovery_does_not_hide_runtime_changes(self): + base, _ = self.core_metadata_recovery() + self.write("crates/kingfisher-core/src/lib.rs", "// changed runtime implementation\n") + self.assertTrue(preflight.runtime_changed(self.root, base, self.commit())) + + def test_core_metadata_recovery_does_not_hide_dependency_changes(self): + base, _ = self.core_metadata_recovery() + path = "crates/kingfisher-core/Cargo.toml" + manifest = (self.root / path).read_text(encoding="utf-8") + self.write(path, manifest + '\n[dependencies]\nnew-dependency = "1"\n') + self.assertTrue(preflight.runtime_changed(self.root, base, self.commit())) + + def test_core_metadata_recovery_requires_matching_provenance(self): + base, _ = self.core_metadata_recovery() + path = "crates/kingfisher-rules/generated/provenance.json" + provenance = json.loads((self.root / path).read_text(encoding="utf-8")) + provenance["generator_input_sha256"]["crates/kingfisher-rules/Cargo.toml"] = "stale" + self.write(path, json.dumps(provenance)) + self.assertTrue(preflight.runtime_changed(self.root, base, self.commit())) + def test_meaningful_python_native_shared_and_rule_inputs(self): for path in ("python/kingfisher_sdk/inputs.py", "python/src/module.py", "crates/kingfisher-python/src/lib.rs", "crates/kingfisher-core/src/lib.rs",