Skip to content
Closed
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
12 changes: 6 additions & 6 deletions .castiron.stats.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
schema_version: 1
generation_id: bb452d19-8d5f-4013-bfd3-3edecb25c37e
openapi_spec_hash: 72d34e76f62e434dc0b71e0b4ac0ce8a
openapi_transformed_spec_hash: bca4c6179df519f310841c4730b72c18
generation_id: b1641964-c2c6-4bee-bbf0-94ce27e69bbe
openapi_spec_hash: e856004bcb1faf4b5b91d43290188237
openapi_transformed_spec_hash: 7f2f62b14c75d5cd32be2baa653486ed
config_hash: 8a49d8603252fe24c2dc96f50eb0d05b
codegen_sha: 016ee2b2da744118cf209752f57c8616ad20b842
codegen_hash: 82cc4b7aca374db5d389b1ac06369a1ffa5946795a9ab86c87f57a5f5ec00390
public_codegen_sha: e23db4894b46c4da1d4d9625eb0876d0ab512e78
codegen_sha: 782e497532d06845ad69cd009bff71251296d2c7
codegen_hash: 2427f947d2dc5d6321110dc0749b2bf4888e2817b4be8818115de07d4097cffc
public_codegen_sha: 50008a1abc1c26e8e7fb404f4322430e099986d3
10 changes: 7 additions & 3 deletions .github/workflows/castiron-custom-code-comment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -168,9 +168,13 @@ jobs:
if (pr.state === 'open' && pr.head.sha === head &&
['main', 'next'].includes(pr.base.ref) && pr.base.repo.full_name === `${context.repo.owner}/${context.repo.repo}`) current.push(pr);
}
if (current.length !== 1) return;
branch = current[0].base.ref;
number = current[0].number;
// Match compute's preference for required main checks when
// fallback discovery finds an advisory next PR for the same head.
const main = current.filter(pr => pr.base.ref === 'main');
const selected = main.length ? main : current;
if (selected.length !== 1) return;
branch = selected[0].base.ref;
number = selected[0].number;
} else {
const match = run.head_branch.match(/^gh-readonly-queue\/(main|next)\//);
if (run.event !== 'merge_group' || !match) return;
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/castiron-custom-code.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ concurrency:
cancel-in-progress: false

env:
REPORTER_SHA256: 157ed30ac9d54d20fe5f7e625dc8ee5958beb91cc7e30aaa97cfba8fb3e06e21
REPORTER_SHA256: 4dc056b460d688cbe3035bd9649820f77e489424d0c9c48bd3f19ed4dfb7e9b4

jobs:
queue-signal:
Expand Down
5 changes: 5 additions & 0 deletions api_reference/openapi.transformed.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78486,6 +78486,11 @@ components:
- AE_STORAGE
- AE_STORAGE_PROCESSING
UpdateProjectApiKeyBody:
anyOf:
- required:
- scopes
- required:
- expires_in_seconds
properties:
expires_in_seconds:
type: integer
Expand Down
21 changes: 19 additions & 2 deletions scripts/castiron/custom_code_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -452,18 +452,31 @@ def resolve_checkpoint(
if not isinstance(decoded, dict):
raise ReportError("invalid codegen checkpoint metadata")
metadata = cast(dict[str, Any], decoded)
namespace = ""
if "schema_version" in metadata:
if (
set(metadata) != {"schema_version", "release_target", "record"}
or metadata["schema_version"] != "castiron-checkpoint/v2"
or metadata["release_target"] != "next"
or not isinstance(metadata["record"], dict)
):
raise ReportError("invalid versioned checkpoint envelope")
metadata = cast(dict[str, Any], metadata["record"])
namespace = "castiron/next/"
if {"release_target", "schema_version", "record"}.intersection(metadata):
raise ReportError("checkpoint target metadata must use the versioned envelope")
if metadata.get("generation_id") != stats["generation_id"]:
raise ReportError("codegen checkpoint generation ID does not match stats")
branch = metadata.get("source_branch")
if not isinstance(branch, str) or not branch:
raise ReportError("codegen checkpoint has no source branch")
ref = f"refs/heads/codegen/{branch}"
ref = f"refs/heads/codegen/{namespace}{branch}"
git(repo, "check-ref-format", ref)
tracking = f"refs/castiron/report/{hashlib.sha256(ref.encode()).hexdigest()}"
if fetch:
git(repo, "fetch", "--quiet", "--no-tags", "origin", f"+{ref}:{tracking}")
else:
tracking = f"refs/remotes/origin/codegen/{branch}"
tracking = f"refs/remotes/origin/codegen/{namespace}{branch}"
git(repo, "merge-base", "--is-ancestor", commit, tracking)
actual = hash_codegen_commit(repo, commit)
expected = stats.get("codegen_hash")
Expand Down Expand Up @@ -807,6 +820,10 @@ def current_pull(repository: str, run: dict[str, Any]) -> tuple[int, str] | None
and pull["base"]["ref"] in TARGET_BRANCHES
):
current.append((number, pull["base"]["ref"]))
# Fallback discovery can find both targets for one head. Main's required
# checks take precedence over next's advisory report; keep same-target
# ambiguity fail-closed. Explicit run associations still limit discovery.
current = [pull for pull in current if pull[1] == "main"] or current
if len(current) > 1:
raise ReportError("workflow run has multiple current pull requests")
return current[0] if current else None
Expand Down
2 changes: 1 addition & 1 deletion scripts/castiron/fixtures/github_publisher.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const writes = [];
const github = {
rest: {
actions: { getWorkflowRun: async () => ({ data: data.run }) },
pulls: { get: async () => ({ data: data.current_sequence?.shift() || data.current }), list: 'pulls' },
pulls: { get: async ({ pull_number }) => ({ data: data.current_sequence?.shift() || data.pulls_by_number?.[pull_number] || data.current }), list: 'pulls' },
git: {
getRef: async ({ ref }) => ({
data: {
Expand Down
32 changes: 16 additions & 16 deletions scripts/castiron/generated-files.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
"files": {
".github/workflows/castiron-custom-code-comment.yml": {
"mode": "100644",
"sha256": "02b1c497461fcaf604e1389e7dcab06cfcca01635a8cb2e92f8769245a2aaead"
"sha256": "f0a54911a96ae55a6a9c71ed7e6b259b0da84642dd1716b68e75a7123519728c"
},
".github/workflows/castiron-custom-code.yml": {
"mode": "100644",
"sha256": "ccd67760a8cfcc17ebdf5c8df7e330ee7be5199cb62232afdf9035fea68bbc2f"
"sha256": "867db0c0cb67312a7682236b2ff04f2ccf611ad9e307a2ee650b3a997191f289"
},
"api.md": {
"mode": "100644",
Expand All @@ -27,15 +27,15 @@
},
"scripts/castiron/custom_code_report.py": {
"mode": "100755",
"sha256": "157ed30ac9d54d20fe5f7e625dc8ee5958beb91cc7e30aaa97cfba8fb3e06e21"
"sha256": "4dc056b460d688cbe3035bd9649820f77e489424d0c9c48bd3f19ed4dfb7e9b4"
},
"scripts/castiron/custom_code_test_support.py": {
"mode": "100644",
"sha256": "27a1c5ee377fa601f896191e3914cb74e4bf65ab814819a408e884332b713bde"
},
"scripts/castiron/fixtures/github_publisher.cjs": {
"mode": "100644",
"sha256": "fc80b1aff054f8a2ea16fc6bd9cff5ed4e4a48eebc7f3af8427947796fb8d865"
"sha256": "4f5475e905818875e37d686bf03553b55219ac05e30957628d4aaa834fced202"
},
"scripts/castiron/ruff.toml": {
"mode": "100644",
Expand All @@ -47,15 +47,15 @@
},
"scripts/castiron/test_custom_code_github.py": {
"mode": "100644",
"sha256": "aa170458b1b58523df78073a96ba8c58b75fce03206ff7d62d99b29791c6ca39"
"sha256": "338d54bf6b53a44b555dc7baa778c76af499b06959caf0339142f1b4293edd92"
},
"scripts/castiron/test_custom_code_publication.py": {
"mode": "100644",
"sha256": "99b266761f427d77990e68ce2fdea4b5f1a5b175612b3a9f4355c094939130ca"
"sha256": "1d93757664a6ffd26f21df2a4c07ac635393f5da1ab0283df75bad66faa2ef2e"
},
"scripts/castiron/test_custom_code_report.py": {
"mode": "100644",
"sha256": "56a748c56ccf137aee0e5997dd29484157d5566bf844cc4969270dd26fa38cbd"
"sha256": "8cb4add28b7e6acb4f8b3844073d33e7a95d8a554c4caf47bd1e09da86a3bd04"
},
"src/openai/__init__.py": {
"mode": "100644",
Expand Down Expand Up @@ -411,7 +411,7 @@
},
"src/openai/resources/beta/responses/responses.py": {
"mode": "100644",
"sha256": "e631b84ca7b00615a9e2f232d45747ec39f284ffa1ceb71517032e969bb386bb"
"sha256": "c7e705185228ea5a1662dc0fe569e0c16bb7eadf38beff925f6067643d98feb6"
},
"src/openai/resources/beta/threads/__init__.py": {
"mode": "100644",
Expand Down Expand Up @@ -591,19 +591,19 @@
},
"src/openai/resources/live/forks.py": {
"mode": "100644",
"sha256": "f3395e5c09d1e690b3c4fd0e2adf04990362e217df3b1ab2a046657d6e1b54b2"
"sha256": "ff834c298a4566869e81e769be92bcbed216510557ff4eb344fd840bab2f2cd5"
},
"src/openai/resources/live/live.py": {
"mode": "100644",
"sha256": "472c140e576cb42c4965e1f623fc7b29e50b01dae6b474c2e9db2f0190289f40"
"sha256": "a0ae2ae3eaaeccf152edca7999656be8cf10e5da49fc6335d479d71ba259b30d"
},
"src/openai/resources/live/sessions.py": {
"mode": "100644",
"sha256": "9ba2d59d9c7498c96ac2e7e48660c91468e0eeb5df881e77f123ee37985c3dfd"
},
"src/openai/resources/live/sideband.py": {
"mode": "100644",
"sha256": "073e10b3edc936de7d35776069354c4272a9d7636070cb3ce4804dca978915f5"
"sha256": "37028100a5301b90b58cb6ed4ea2e20e0277fa476796d5d932cf848ad2a294c1"
},
"src/openai/resources/models.py": {
"mode": "100644",
Expand Down Expand Up @@ -631,7 +631,7 @@
},
"src/openai/resources/realtime/realtime.py": {
"mode": "100644",
"sha256": "a1af3c8791a4f29b66e3401348b6c6dea6dc658a8a6bb88843cdb30b4626119c"
"sha256": "29cf5fe34140d2f985151b264d3438897ed1579d7db1ef523a5831dd89ac7936"
},
"src/openai/resources/realtime/translations/__init__.py": {
"mode": "100644",
Expand All @@ -643,7 +643,7 @@
},
"src/openai/resources/realtime/translations/translations.py": {
"mode": "100644",
"sha256": "0da275b10d6f42a82cc83dde4ed659efc7b75ce1d8191365c22a3914fbb840e4"
"sha256": "2ed29f4fa975a92d0067c7c28e30fefad8070fc4bd6a7b35ff124b0bbaeb94b5"
},
"src/openai/resources/responses/__init__.py": {
"mode": "100644",
Expand All @@ -663,7 +663,7 @@
},
"src/openai/resources/responses/responses.py": {
"mode": "100644",
"sha256": "2239f2e9fe19aa019d2580a0f74a5d2efcedc2c284650ee1c2567af334a045e6"
"sha256": "77b3bd59e28c76c8c54d21d556718d30d6be437f1b19507e40e3708a24986e77"
},
"src/openai/resources/safety/__init__.py": {
"mode": "100644",
Expand Down Expand Up @@ -755,7 +755,7 @@
},
"src/openai/types/__init__.py": {
"mode": "100644",
"sha256": "5caaa29a79510a0a3e4a92f7b59d3ccb3f8f6651706f658738cb5e2a22b91481"
"sha256": "47c98e4ffd0eb3a28a9d60b78facf0bdd165d6d35b24ebb6bea5834a0165eeea"
},
"src/openai/types/admin/__init__.py": {
"mode": "100644",
Expand Down Expand Up @@ -7271,7 +7271,7 @@
},
"tests/api_resources/admin/organization/projects/test_api_keys.py": {
"mode": "100644",
"sha256": "1fa9769ccdc65d47c8c17c1e525a69e70ebb6d9248a017eb5fe4de7a337ec8ee"
"sha256": "49c21791da11c49c01c26b40da8e826b37a496d8e209c4b76911be04435d5048"
},
"tests/api_resources/admin/organization/projects/test_certificates.py": {
"mode": "100644",
Expand Down
29 changes: 29 additions & 0 deletions scripts/castiron/test_custom_code_github.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,35 @@ def __call__(self, method: str, path: str, payload: dict[str, Any] | None = None


class GitHubReportTests(GitTestCase):
def test_same_head_targets_preserve_required_main_discovery(self) -> None:
head = "b" * 40
run = source_run(head, "pull_request")
run["pull_requests"] = []
main: dict[str, Any] = {
"state": "open",
"head": {"sha": head},
"base": {"ref": "main", "repo": {"full_name": REPOSITORY}},
}
next_pull: dict[str, Any] = {**main, "base": {**main["base"], "ref": "next"}}
for numbers in ([3, 4], [4, 3]):
def api_response(_method: str, path: str, numbers: list[int] = numbers) -> Any:
if "/commits/" in path:
return [{"number": number} for number in numbers]
return {"3": main, "4": next_pull}[path.rsplit("/", 1)[1]]

with (
self.subTest(numbers=numbers),
mock.patch.object(report, "api", side_effect=api_response),
):
self.assertEqual(report.current_pull(REPOSITORY, run), (3, "main"))
run["pull_requests"] = [{"number": 4}]
self.assertEqual(report.current_pull(REPOSITORY, run), (4, "next"))
run["pull_requests"] = []
next_pull["base"]["ref"] = "main"
with self.assertRaisesRegex(report.ReportError, "multiple current"):
report.current_pull(REPOSITORY, run)
next_pull["base"]["ref"] = "next"

def test_trusted_report_recomputes_pr_output_in_a_bare_repository(self) -> None:
generated, _ = self.baseline()
content_hash = report.hash_codegen_commit(self.repo, generated)
Expand Down
27 changes: 26 additions & 1 deletion scripts/castiron/test_custom_code_publication.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@ def publish(
queue_absent: bool = False,
queue_pages: list[dict[str, Any]] | None = None,
retarget_during_publication: bool = False,
same_head_next_pr: bool = False,
) -> list[dict[str, Any]]:
base, head = "a" * 40, "b" * 40
run = source_run(head, event_name)
Expand Down Expand Up @@ -369,6 +370,12 @@ def publish(
"PUBLISH_ATTEMPT": "1",
},
}
if same_head_next_pr:
current = payload["current"]
payload["pulls_by_number"] = {
3: current,
4: {**current, "number": 4, "base": {**current["base"], "ref": "next"}},
}
if retarget_during_publication:
current = payload["current"]
payload["current_sequence"] = [
Expand Down Expand Up @@ -397,6 +404,22 @@ def test_next_cannot_overwrite_main_results(self) -> None:
result = self.publish(event_name=event, branch="next", previous_statuses=main)
self.assertEqual(result, [])

def test_same_head_next_pr_does_not_block_required_main_statuses(self) -> None:
for numbers in ([3, 4], [4, 3]):
with self.subTest(numbers=numbers):
results = self.publish(
run_overrides={"pull_requests": []},
fallback_pulls=[{"number": number} for number in numbers],
same_head_next_pr=True,
failed_budget=True,
)
self.assertEqual([r["state"] for r in results], ["success", "failure"])
self.assertTrue(all(r["sha"] == "b" * 40 for r in results))
self.assertEqual(
self.publish(run_overrides={"pull_requests": [{"number": 4}]}, same_head_next_pr=True),
[],
)

def test_next_queue_never_authorizes_against_a_stale_main_policy(self) -> None:
for changed in (False, True):
with self.subTest(policy_changed=changed):
Expand All @@ -413,7 +436,9 @@ def test_main_statuses_bind_target_policy_and_queue_membership(self) -> None:
for policy_changed, queue_absent in ((True, False), (False, True)):
with self.subTest(policy_changed=policy_changed, queue_absent=queue_absent):
result = self.publish(
event_name="merge_group", policy_changed=policy_changed, queue_absent=queue_absent
event_name="merge_group",
policy_changed=policy_changed,
queue_absent=queue_absent,
)
self.assertEqual([r["state"] for r in result], ["failure", "failure"])
self.assertEqual(self.publish(retarget_during_publication=True), [])
Expand Down
45 changes: 45 additions & 0 deletions scripts/castiron/test_custom_code_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,51 @@ def test_stats_symlink_is_rejected(self) -> None:
with self.assertRaisesRegex(report.ReportError, "regular Git file"):
report.read_stats(self.repo, self.commit())

def test_next_checkpoint_uses_its_namespaced_ref(self) -> None:
generated, base = self.baseline()
self.git("checkout", "--detach", generated)
metadata = {
"schema_version": "castiron-checkpoint/v2",
"release_target": "next",
"record": {"generation_id": GENERATION, "source_branch": "test"},
}
encoded = base64.b64encode(json.dumps(metadata).encode()).decode()
checkpoint = self.commit(f"codegen\n\nGeneration metadata: {encoded}")
ref = "refs/remotes/origin/codegen/castiron/next/test"
self.git("update-ref", ref, checkpoint)
stats = report.read_stats(self.repo, base) | {
"codegen_sha": checkpoint,
"codegen_hash": report.hash_codegen_commit(self.repo, checkpoint),
}
result = report.resolve_checkpoint(self.repo, stats, fetch=False, require_hash=True)
self.assertEqual(result["commit"], checkpoint)
self.git("update-ref", "refs/remotes/origin/codegen/test", checkpoint)
self.git("update-ref", "-d", ref)
with self.assertRaises(report.ReportError):
report.resolve_checkpoint(self.repo, stats, fetch=False, require_hash=True)

def test_invalid_checkpoint_envelopes_are_rejected(self) -> None:
_, base = self.baseline()
record = {"generation_id": GENERATION, "source_branch": "test"}
envelope = {
"schema_version": "castiron-checkpoint/v2",
"release_target": "next",
"record": record,
}
for metadata in (
envelope | {"schema_version": "unknown"},
envelope | {"release_target": "main"},
envelope | {"extra": True},
envelope | {"record": record | {"release_target": "next"}},
record | {"release_target": "next"},
):
with self.subTest(metadata=metadata):
encoded = base64.b64encode(json.dumps(metadata).encode()).decode()
checkpoint = self.commit(f"codegen\n\nGeneration metadata: {encoded}")
stats = report.read_stats(self.repo, base) | {"codegen_sha": checkpoint}
with self.assertRaisesRegex(report.ReportError, "envelope"):
report.resolve_checkpoint(self.repo, stats, fetch=False, require_hash=True)

def test_public_snapshot_rejects_private_governance_and_unpublished_paths(self) -> None:
entry = report.Entry(b"100644", "a" * 40)
blocked = [
Expand Down
Loading
Loading