diff --git a/.castiron.stats.yml b/.castiron.stats.yml index aa113a34c..b9a5be7b1 100644 --- a/.castiron.stats.yml +++ b/.castiron.stats.yml @@ -1,8 +1,8 @@ schema_version: 1 -generation_id: f664572a-7dc0-499b-9a19-46357300068f +generation_id: 7b8e4f9d-66bd-465d-97d2-cbe0f41aadaf openapi_spec_hash: 28504532afa0c3f477f005e641ec7d6d openapi_transformed_spec_hash: 13422b63a47a5d25533cf55573de7632 config_hash: 4446d9cc8e7a00d29ce72282aa5616f9 -codegen_sha: 46b4c0fbaf91c9d178f4b29d2fed8a03794b5662 -codegen_hash: 753430c43af080e5995298094195c43f14ec9db4b146a14c7e33bf2c2d12df8b -public_codegen_sha: 89db964dcda3fa55c381ce03108f5d46b61279eb +codegen_sha: b231d2133bce32566450e5544a3205825be270b7 +codegen_hash: bafe248e46487a4eb8ea6868d7fa57e6453e489bf7522869c0ad8eb8125e247d +public_codegen_sha: bb32fa596d2a5e59ed4b7a908aebcdf347cced9d diff --git a/.github/workflows/castiron-custom-code-comment.yml b/.github/workflows/castiron-custom-code-comment.yml index ace9ba11a..4daf717a0 100644 --- a/.github/workflows/castiron-custom-code-comment.yml +++ b/.github/workflows/castiron-custom-code-comment.yml @@ -79,7 +79,7 @@ jobs: if (!proposedChecker || !/^[0-9a-f]{40}$/.test(previousChecker || '') || !/^[0-9a-f]{40}$/.test(mainSha || '')) return true; const [previous, proposed, previousBase, proposedBase] = await Promise.all([ - targetPolicy.next_policy ? readPolicy(previousChecker) : targetPolicy, + readPolicy(previousChecker), readPolicy(proposedChecker), mergeBase(previousChecker, proposedChecker), mergeBase(proposedChecker, mainSha), ]); diff --git a/scripts/castiron/CUSTOM_CODE.md b/scripts/castiron/CUSTOM_CODE.md index 5c8a53ae3..945491bb4 100644 --- a/scripts/castiron/CUSTOM_CODE.md +++ b/scripts/castiron/CUSTOM_CODE.md @@ -210,7 +210,8 @@ base, never the candidate. Pin-only updates and initial pin adoption use ordinar CODEOWNERS and required reviews when the pin does not roll back to an older main checker, the effective pinned budget stays the same or decreases, and its schema, strictness and ratchet mode stay unchanged. Initial adoption compares checker age -with authenticated bootstrap main; later updates compare with the current protected +and effective policy with authenticated bootstrap main, not the unpinned target's +local policy fields; later updates compare with the current protected target pin and effective policy, even for stale PRs. A checker rollback, an effective pinned-budget increase or mode transition, a local numeric budget increase, or Go ratchet activation still requires an exact-head human approving review from a diff --git a/scripts/castiron/custom_code_policy.py b/scripts/castiron/custom_code_policy.py index 65a63d5e7..a8410cb94 100755 --- a/scripts/castiron/custom_code_policy.py +++ b/scripts/castiron/custom_code_policy.py @@ -278,8 +278,9 @@ def select_policy( repo, next_policy_sha(repo, repository, candidate, authenticated_main) ) # Authored changes come from the merge base, but their safety must be - # measured against the protected target that will actually be replaced. - previous = read_policy(repo, policy) if "next_policy" in base_policy else base_policy + # measured against the active policy: the protected target's pin, or + # authenticated main during bootstrap. + previous = read_policy(repo, policy) # Bootstrap executes authenticated main; initial adoption must not silently # replace it with older checker code, even when policy fields match. previous_checker = ( diff --git a/scripts/castiron/generated-files.json b/scripts/castiron/generated-files.json index d3d3cfe4a..d67d4bfd2 100644 --- a/scripts/castiron/generated-files.json +++ b/scripts/castiron/generated-files.json @@ -3,7 +3,7 @@ "files": { ".github/workflows/castiron-custom-code-comment.yml": { "mode": "100644", - "sha256": "afebb2d21c7b322905e67452004da087d5bd3f60fc3c0cc11d9bccad9900103d" + "sha256": "a896f4bac69c6f8debe7b4bf540129466be04cb62b75c60f5f80a37f5739b0a5" }, ".github/workflows/castiron-custom-code.yml": { "mode": "100644", @@ -15911,7 +15911,7 @@ }, "scripts/castiron/CUSTOM_CODE.md": { "mode": "100644", - "sha256": "c0315b363849bcebb2819d07ed84615db47f03132ad3f59f1fcdfcf9ffad2ece" + "sha256": "e7ac634501e8f21144cc5678539282b50b64830a0455b5b840f8ca4010033d3b" }, "scripts/castiron/README.md": { "mode": "100644", @@ -15923,7 +15923,7 @@ }, "scripts/castiron/custom_code_policy.py": { "mode": "100755", - "sha256": "b1425b2c9c5fcf47897c266776fbd1ce28f98df1cfd8af492335589a707f840a" + "sha256": "c0c4d5191b90880c881675816ec18ea0bd4b078838ef663513159b2d157edb47" }, "scripts/castiron/custom_code_report.py": { "mode": "100755", @@ -15951,11 +15951,11 @@ }, "scripts/castiron/test_custom_code_policy.py": { "mode": "100644", - "sha256": "5de73d64a2f89a4b7bd805f59f0c6a6d29452866ef050c1191e04a8c48e6db25" + "sha256": "22405957bac59e79943a3ea5c72a5657cea5ae3b3b7a5d9ed80966edf2e94b23" }, "scripts/castiron/test_custom_code_publication.py": { "mode": "100644", - "sha256": "4e9077ffc214685e49d4156163eaf904e650bc9ed82ecbdb03a19fc22bd4fbbf" + "sha256": "8548acaf8958bf745759ba5c7a08791cb72793aa0a780d027a2a686e0464ac8e" }, "scripts/castiron/test_custom_code_report.py": { "mode": "100644", diff --git a/scripts/castiron/test_custom_code_policy.py b/scripts/castiron/test_custom_code_policy.py index 57aae389c..909d29f60 100644 --- a/scripts/castiron/test_custom_code_policy.py +++ b/scripts/castiron/test_custom_code_policy.py @@ -475,12 +475,13 @@ def test_pin_effective_policy_review_for_pr_and_queue(self) -> None: self.head = self.commit("request pin without changing local budget") for heads in (None, [self.head]): with self.subTest(proposed=proposed, bootstrap=bootstrap, heads=heads): + needs_proof = requires_human and not bootstrap with mock.patch.object( selector, "require_human_policy_review", side_effect=ValueError("human review missing"), ) as review: - if requires_human: + if needs_proof: with self.assertRaisesRegex(ValueError, "human"): self.select(base=base, main=newer, pull_heads=heads) review.assert_called_once_with(REPO, self.head) @@ -488,7 +489,7 @@ def test_pin_effective_policy_review_for_pr_and_queue(self) -> None: result = self.select(base=base, main=newer, pull_heads=heads) review.assert_not_called() self.assertEqual(result["policy_reviews"], []) - if requires_human: + if needs_proof: with mock.patch.object( selector.report, "api", @@ -606,6 +607,40 @@ def test_initial_older_pin_keeps_human_proof_for_pr_and_queue(self) -> None: [{"number": 3, "head_sha": self.head}] if pin_sha == self.main else [], ) + def test_bootstrap_uses_active_main_policy_for_pr_and_queue(self) -> None: + self.git("checkout", "-q", "--detach", self.main) + self.write_policy( + {"schema_version": 2, "max_custom_patch_lines": 0, "generated_files": "strict"} + ) + current = self.commit("main activates strict zero policy") + self.git("update-ref", "refs/remotes/origin/main", current) + for proposed in (self.main, current): + self.git("checkout", "-q", "--detach", self.main) + self.write_policy( + {**self.policy, "next_policy": {"repository": REPO, "main_sha": proposed}} + ) + self.head = self.commit("initial pin with unchanged legacy local ceiling") + for heads in (None, [self.head]): + with self.subTest(proposed=proposed, heads=heads): + with mock.patch.object(selector.report, "api", side_effect=self.review_api([])): + if proposed == self.main: + with self.assertRaisesRegex(ValueError, "human"): + self.select(base=self.main, main=current, pull_heads=heads) + else: + result = self.select(base=self.main, main=current, pull_heads=heads) + self.assertEqual(result["policy_reviews"], []) + self.assertEqual(result["policy_sha"], current) + with mock.patch.object( + selector.report, "api", side_effect=self.review_api([self.approved()]) + ): + result = self.select(base=self.main, main=current, pull_heads=heads) + self.assertTrue(result["bootstrap"]) + self.assertEqual(result["policy_sha"], current) + self.assertEqual( + result["policy_reviews"], + [{"number": 3, "head_sha": self.head}] if proposed == self.main else [], + ) + def test_incapable_policy_is_not_executed(self) -> None: self.git("checkout", "-q", "--detach", self.main) (self.repo / "scripts/castiron/custom_code_budget.py").write_text( diff --git a/scripts/castiron/test_custom_code_publication.py b/scripts/castiron/test_custom_code_publication.py index 190a9db69..1c225b6d2 100644 --- a/scripts/castiron/test_custom_code_publication.py +++ b/scripts/castiron/test_custom_code_publication.py @@ -1486,13 +1486,22 @@ def test_initial_pin_at_current_main_keeps_statuses(self) -> None: payload = self.payload() checker = "f" * 40 payload["main_sha"] = checker - payload["policies_by_sha"][checker] = payload["policies_by_sha"]["b" * 40] + payload["policies_by_sha"]["b" * 40] = { + "schema_version": 1, + "max_custom_patch_lines": 4000, + } + payload["policies_by_sha"][checker] = { + "schema_version": 2, + "max_custom_patch_lines": 0, + "generated_files": "strict", + } payload["policies_by_sha"]["a" * 40] = { **payload["policies_by_sha"]["b" * 40], "next_policy": {"repository": "openai/example", "main_sha": checker}, } self.assertEqual(self.invoke(payload), []) advanced_main = "e" * 40 + payload["policies_by_sha"][advanced_main] = payload["policies_by_sha"][checker] payload["main_sha_sequence"] = [checker, advanced_main] payload["main_sha"] = advanced_main payload["comparisons"] = {