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
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: 08df1715-e12d-4fb7-9f39-36a5177cb268
openapi_spec_hash: f056027f327e6dc07798c27da39f1916
openapi_transformed_spec_hash: ccbb3610f02d7b6473dfbb3ac3f7c0dd
generation_id: dfeb3d3b-06be-408a-88c2-f7e51d0931fb
openapi_spec_hash: 9c46c85a2ff35b78d9eeba9e1a285981
openapi_transformed_spec_hash: 5ff561cf4786af67b36a5c5b6fb00ce8
config_hash: 65d162cc4be98e6e2cf07658e30d1170
codegen_sha: 5749e9a81a33bc7cd25f7a6dda0a8186f4339e22
codegen_hash: 13a3dd32d06c51dda8572c82c3ec12c5ede2b83e8edaf720cf07048209132189
public_codegen_sha: 9edf54e2d708c12eb47df1ca228d28400e4dd335
codegen_sha: 9b3da60e57bf7da65ef2a94c76ef395262f7c9d0
codegen_hash: b555a476f1b902a2dab0f13b019d2c8e017079e2b544a33b3ce9cd3caac5d871
public_codegen_sha: 4109ce39f7e1dced610768c6f58b520d85f636c4
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
36 changes: 12 additions & 24 deletions api_reference/openapi.transformed.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36936,6 +36936,11 @@ webhooks:
components:
schemas:
UpdateProjectApiKeyBody:
anyOf:
- required:
- scopes
- required:
- expires_in_seconds
properties:
expires_in_seconds:
type: integer
Expand Down Expand Up @@ -87321,13 +87326,13 @@ components:
type: string
minLength: 0
description: The public ID of the environment.
container_size:
anyOf:
- $ref: '#/components/schemas/ContainerSizeResource'
- type: 'null'
description: The effective CPU and memory tier, or null when unknown or outside the public tiers.
x-stainless-skip:
- cli
files:
type: array
items:
$ref: '#/components/schemas/HostedEnvironmentFileResource'
minItems: 0
maxItems: 50
description: Files available in the environment, excluding their contents.
packages:
$ref: '#/components/schemas/EnvironmentPackagesResource'
description: Packages installed in the environment.
Expand Down Expand Up @@ -87359,13 +87364,6 @@ components:
minItems: 0
maxItems: 2000
description: Plugins installed in the environment, excluding their archive contents.
files:
type: array
items:
$ref: '#/components/schemas/HostedEnvironmentFileResource'
minItems: 0
maxItems: 50
description: Files available in the environment, excluding their contents.
required:
- type
- id
Expand Down Expand Up @@ -88074,11 +88072,6 @@ components:
minLength: 0
maxLength: 64
description: A reusable hosted template applied before inline session configuration. Omitted fields inherit the template; network overrides cannot broaden its policy.
container_size:
$ref: '#/components/schemas/ContainerSizeParam'
description: The hosted container size. Omission selects the medium tier.
x-stainless-skip:
- cli
required:
- type
additionalProperties: false
Expand Down Expand Up @@ -93483,11 +93476,6 @@ components:
minLength: 1
maxLength: 1
pattern: ^[0-9A-D*#]$
client_event_id:
description: The event_id of the client command, when supplied.
type: string
x-stainless-skip:
- cli
required:
- type
- event_id
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
14 changes: 7 additions & 7 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"
},
"generated/commands/adminorganizationadminapikey.go": {
"mode": "100644",
Expand Down Expand Up @@ -715,15 +715,15 @@
},
"scripts/castiron/custom_code_report.py": {
"mode": "100644",
"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 @@ -735,15 +735,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"
}
}
}
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