From 1b609b22dd6139dfe02ff650e5c887889cb667ff Mon Sep 17 00:00:00 2001 From: JasonStys Date: Tue, 10 Nov 2026 12:00:05 -0800 Subject: [PATCH] fix: require compiler acceptance before preparing releases --- docs/code-index.md | 435 +++++++++++++++++++------------------ docs/releases/v0.4.3.md | 26 ++- scripts/release_gate.py | 2 +- tests/test_release_gate.py | 54 ++++- 4 files changed, 288 insertions(+), 229 deletions(-) diff --git a/docs/code-index.md b/docs/code-index.md index 788cf23..a500623 100644 --- a/docs/code-index.md +++ b/docs/code-index.md @@ -4273,226 +4273,233 @@ Generated declaration locations. Python uses AST; JS/TS uses declaration-line ma | variable | `prefix` | 94 | | function | `test_real_source_response_contract_passes(monkeypatch)` | 110 | | parameter | `monkeypatch` | 110 | -| function | `test_zero_unknown_or_noninteger_codeql_rule_counts_never_authorize_release(monkeypatch, count)` | 117 | -| parameter | `count` | 117 | +| function | `test_compiler_acceptance_is_an_independent_exact_main_release_prerequisite(monkeypatch, defect)` | 117 | +| parameter | `defect` | 117 | | parameter | `monkeypatch` | 117 | | variable | `responses` | 119 | -| variable | `row` | 120 | -| function | `test_source_failures_cannot_authorize_release(monkeypatch, defect)` | 149 | -| parameter | `defect` | 149 | -| parameter | `monkeypatch` | 149 | -| variable | `responses` | 151 | -| variable | `key` | 152 | -| variable | `row` | 153 | -| variable | `field` | 155 | -| variable | `value` | 155 | -| variable | `analyses` | 177 | -| function | `test_unknown_or_full_scan_pages_fail_closed(monkeypatch)` | 193 | -| parameter | `monkeypatch` | 193 | -| parameter | `endpoint` | 195 | -| function | `test_prepared_numeric_id_cannot_be_public_or_swapped(monkeypatch, change)` | 210 | -| parameter | `change` | 210 | -| parameter | `monkeypatch` | 210 | -| parameter | `tag` | 213 | -| function | `test_actual_workflow_policy_rejects_security_mutants(before, after)` | 234 | -| parameter | `after` | 234 | -| parameter | `before` | 234 | -| variable | `original` | 236 | -| variable | `changed` | 238 | -| function | `test_real_final_job_permission_mutations_cannot_authorize_draft(scope, before, after)` | 250 | -| parameter | `after` | 250 | -| parameter | `before` | 250 | -| parameter | `scope` | 250 | -| variable | `original` | 252 | -| variable | `prefix` | 254 | -| variable | `separator` | 254 | -| variable | `verifier` | 254 | -| variable | `changed` | 256 | -| function | `test_commented_duplicate_or_unparsed_final_permissions_fail_closed(extra)` | 266 | -| parameter | `extra` | 266 | -| variable | `original` | 268 | -| variable | `prefix` | 269 | -| variable | `separator` | 269 | -| variable | `verifier` | 269 | -| variable | `changed` | 271 | -| function | `test_legitimate_final_scope_comments_are_parsed_not_ignored()` | 276 | -| variable | `original` | 278 | +| variable | `endpoint` | 120 | +| variable | `row` | 121 | +| function | `test_zero_unknown_or_noninteger_codeql_rule_counts_never_authorize_release(monkeypatch, count)` | 142 | +| parameter | `count` | 142 | +| parameter | `monkeypatch` | 142 | +| variable | `responses` | 144 | +| variable | `row` | 145 | +| function | `test_source_failures_cannot_authorize_release(monkeypatch, defect)` | 174 | +| parameter | `defect` | 174 | +| parameter | `monkeypatch` | 174 | +| variable | `responses` | 176 | +| variable | `key` | 177 | +| variable | `row` | 178 | +| variable | `field` | 180 | +| variable | `value` | 180 | +| variable | `analyses` | 202 | +| function | `test_unknown_or_full_scan_pages_fail_closed(monkeypatch)` | 218 | +| parameter | `monkeypatch` | 218 | +| parameter | `endpoint` | 220 | +| function | `test_prepared_numeric_id_cannot_be_public_or_swapped(monkeypatch, change)` | 235 | +| parameter | `change` | 235 | +| parameter | `monkeypatch` | 235 | +| parameter | `tag` | 238 | +| function | `test_actual_workflow_policy_rejects_security_mutants(before, after)` | 259 | +| parameter | `after` | 259 | +| parameter | `before` | 259 | +| variable | `original` | 261 | +| variable | `changed` | 263 | +| function | `test_real_final_job_permission_mutations_cannot_authorize_draft(scope, before, after)` | 275 | +| parameter | `after` | 275 | +| parameter | `before` | 275 | +| parameter | `scope` | 275 | +| variable | `original` | 277 | | variable | `prefix` | 279 | | variable | `separator` | 279 | | variable | `verifier` | 279 | | variable | `changed` | 281 | -| function | `test_duplicate_whole_final_permission_blocks_fail_closed(position, field)` | 290 | -| parameter | `field` | 290 | -| parameter | `position` | 290 | -| variable | `original` | 292 | -| variable | `prefix` | 293 | -| variable | `separator` | 293 | -| variable | `verifier` | 293 | -| variable | `extra` | 295 | -| variable | `changed` | 297 | -| variable | `changed` | 299 | -| function | `test_package_schema_is_strict(value)` | 305 | -| parameter | `value` | 305 | -| variable | `bad` | 307 | -| function | `test_metadata_duplicates_nonfinite_and_nonobjects_are_errors(source)` | 313 | -| parameter | `source` | 313 | -| function | `portable(file, value, worker, system, symlink)` | 319 | -| parameter | `file` | 319 | -| parameter | `symlink` | 319 | -| parameter | `system` | 319 | -| parameter | `value` | 319 | -| parameter | `worker` | 319 | -| variable | `prefix` | 321 | -| variable | `members` | 322 | -| variable | `archive` | 327 | -| variable | `content` | 328 | -| variable | `name` | 328 | -| variable | `archive` | 331 | -| variable | `content` | 332 | -| variable | `name` | 332 | -| variable | `entry` | 333 | -| function | `test_actual_platform_archive_metadata(tmp_path, system, suffix)` | 343 | -| parameter | `suffix` | 343 | -| parameter | `system` | 343 | -| parameter | `tmp_path` | 343 | -| variable | `file` | 345 | -| function | `test_archive_metadata_link_is_never_followed(tmp_path)` | 350 | -| parameter | `tmp_path` | 350 | -| variable | `file` | 352 | -| function | `staged(tmp_path, monkeypatch)` | 358 | -| parameter | `metadata_changes` | 358 | -| parameter | `monkeypatch` | 358 | -| parameter | `tmp_path` | 358 | -| variable | `directory` | 362 | -| variable | `installer` | 364 | -| variable | `archive` | 366 | -| variable | `file` | 368 | -| variable | `digest` | 369 | -| variable | `record` | 384 | -| parameter | `tag` | 385 | -| parameter | `endpoint` | 389 | -| function | `test_invalid_local_or_remote_package_cannot_reach_upload(tmp_path, monkeypatch, defect)` | 399 | -| parameter | `defect` | 399 | -| parameter | `monkeypatch` | 399 | -| parameter | `tmp_path` | 399 | -| variable | `changes` | 401 | -| variable | `directory` | 408 | -| variable | `record` | 408 | -| parameter | `args` | 424 | -| function | `test_exact_staged_files_upload_to_prepared_id_without_overwrite(tmp_path, monkeypatch)` | 430 | -| parameter | `monkeypatch` | 430 | -| parameter | `tmp_path` | 430 | -| variable | `_` | 432 | -| variable | `record` | 432 | -| variable | `writes` | 433 | -| function | `send(identifier, file, name, digest)` | 435 | -| parameter | `digest` | 435 | -| parameter | `file` | 435 | -| parameter | `identifier` | 435 | -| parameter | `name` | 435 | -| variable | `row` | 438 | -| variable | `row` | 440 | -| function | `test_fixed_https_transport_id_digest_and_redirect_refusal(tmp_path, monkeypatch, status, digest_ok)` | 465 | -| parameter | `digest_ok` | 465 | -| parameter | `monkeypatch` | 465 | -| parameter | `status` | 465 | -| parameter | `tmp_path` | 465 | -| variable | `file` | 467 | -| variable | `digest` | 469 | -| variable | `events` | 470 | -| class | `Connection` | 472 | -| function | `__init__(self, host, timeout)` | 475 | -| parameter | `host` | 475 | -| parameter | `self` | 475 | -| parameter | `timeout` | 475 | -| function | `putrequest(self, method, url)` | 478 | -| parameter | `method` | 478 | -| parameter | `self` | 478 | -| parameter | `url` | 478 | -| function | `putheader(self, key, value)` | 481 | -| parameter | `key` | 481 | -| parameter | `self` | 481 | -| parameter | `value` | 481 | -| function | `endheaders(self)` | 485 | -| parameter | `self` | 485 | -| function | `send(self, block)` | 488 | -| parameter | `block` | 488 | -| parameter | `self` | 488 | -| function | `getresponse(self)` | 491 | -| parameter | `self` | 491 | -| parameter | `limit` | 497 | -| parameter | `self` | 497 | -| function | `close(self)` | 507 | -| parameter | `self` | 507 | -| variable | `key` | 519 | -| variable | `value` | 519 | -| function | `test_cli_command_allowlist_precedes_execution(monkeypatch)` | 522 | -| parameter | `monkeypatch` | 522 | -| parameter | `args` | 525 | -| parameter | `kwargs` | 525 | -| variable | `arguments` | 527 | -| function | `test_tag_absence_distinguishes_real404_from_permission_errors(monkeypatch)` | 537 | -| parameter | `monkeypatch` | 537 | -| parameter | `name` | 539 | -| variable | `accepted` | 540 | -| variable | `code` | 540 | -| variable | `error` | 540 | -| variable | `output` | 540 | -| parameter | `args` | 549 | -| parameter | `code` | 549 | -| parameter | `error` | 549 | -| parameter | `kwargs` | 549 | -| parameter | `output` | 549 | -| function | `prepared_context(tmp_path, monkeypatch)` | 560 | -| parameter | `monkeypatch` | 560 | -| parameter | `tmp_path` | 560 | -| variable | `workflow` | 562 | -| variable | `key` | 569 | -| variable | `value` | 569 | -| function | `test_actual_prepare_orders_all_checks_before_the_only_write(tmp_path, monkeypatch, defect)` | 584 | -| parameter | `defect` | 584 | -| parameter | `monkeypatch` | 584 | -| parameter | `tmp_path` | 584 | -| variable | `responses` | 587 | -| variable | `listing` | 588 | -| variable | `record` | 600 | -| variable | `reads` | 603 | -| variable | `writes` | 603 | -| function | `transport(endpoint, body)` | 605 | -| parameter | `body` | 605 | -| parameter | `endpoint` | 605 | -| parameter | `tag` | 625 | -| function | `test_untrusted_context_never_reaches_github(tmp_path, monkeypatch, field, value)` | 646 | -| parameter | `field` | 646 | -| parameter | `monkeypatch` | 646 | -| parameter | `tmp_path` | 646 | -| parameter | `value` | 646 | -| parameter | `args` | 650 | -| parameter | `kwargs` | 650 | -| function | `test_final_verification_rechecks_security_but_keeps_release_draft(tmp_path, monkeypatch, capsys)` | 655 | -| parameter | `capsys` | 655 | -| parameter | `monkeypatch` | 655 | -| parameter | `tmp_path` | 655 | -| variable | `record` | 660 | -| variable | `system` | 661 | -| variable | `name` | 662 | -| variable | `responses` | 672 | -| parameter | `tag` | 675 | -| variable | `result` | 677 | -| function | `test_dirty_source_and_publish_operation_are_refused_before_network(tmp_path, monkeypatch)` | 683 | -| parameter | `monkeypatch` | 683 | -| parameter | `tmp_path` | 683 | -| parameter | `args` | 687 | -| parameter | `kwargs` | 687 | -| variable | `error` | 691 | -| function | `test_actual_checked_in_product_version_and_worker_metadata()` | 696 | -| function | `test_api_structured_body_and_fixed_namespace(monkeypatch)` | 701 | -| parameter | `monkeypatch` | 701 | -| variable | `calls` | 703 | -| function | `execute(arguments, data)` | 705 | -| parameter | `arguments` | 705 | -| parameter | `data` | 705 | -| variable | `body` | 711 | +| function | `test_commented_duplicate_or_unparsed_final_permissions_fail_closed(extra)` | 291 | +| parameter | `extra` | 291 | +| variable | `original` | 293 | +| variable | `prefix` | 294 | +| variable | `separator` | 294 | +| variable | `verifier` | 294 | +| variable | `changed` | 296 | +| function | `test_legitimate_final_scope_comments_are_parsed_not_ignored()` | 301 | +| variable | `original` | 303 | +| variable | `prefix` | 304 | +| variable | `separator` | 304 | +| variable | `verifier` | 304 | +| variable | `changed` | 306 | +| function | `test_duplicate_whole_final_permission_blocks_fail_closed(position, field)` | 315 | +| parameter | `field` | 315 | +| parameter | `position` | 315 | +| variable | `original` | 317 | +| variable | `prefix` | 318 | +| variable | `separator` | 318 | +| variable | `verifier` | 318 | +| variable | `extra` | 320 | +| variable | `changed` | 322 | +| variable | `changed` | 324 | +| function | `test_package_schema_is_strict(value)` | 330 | +| parameter | `value` | 330 | +| variable | `bad` | 332 | +| function | `test_metadata_duplicates_nonfinite_and_nonobjects_are_errors(source)` | 338 | +| parameter | `source` | 338 | +| function | `portable(file, value, worker, system, symlink)` | 344 | +| parameter | `file` | 344 | +| parameter | `symlink` | 344 | +| parameter | `system` | 344 | +| parameter | `value` | 344 | +| parameter | `worker` | 344 | +| variable | `prefix` | 346 | +| variable | `members` | 347 | +| variable | `archive` | 352 | +| variable | `content` | 353 | +| variable | `name` | 353 | +| variable | `archive` | 356 | +| variable | `content` | 357 | +| variable | `name` | 357 | +| variable | `entry` | 358 | +| function | `test_actual_platform_archive_metadata(tmp_path, system, suffix)` | 368 | +| parameter | `suffix` | 368 | +| parameter | `system` | 368 | +| parameter | `tmp_path` | 368 | +| variable | `file` | 370 | +| function | `test_archive_metadata_link_is_never_followed(tmp_path)` | 375 | +| parameter | `tmp_path` | 375 | +| variable | `file` | 377 | +| function | `staged(tmp_path, monkeypatch)` | 383 | +| parameter | `metadata_changes` | 383 | +| parameter | `monkeypatch` | 383 | +| parameter | `tmp_path` | 383 | +| variable | `directory` | 387 | +| variable | `installer` | 389 | +| variable | `archive` | 391 | +| variable | `file` | 393 | +| variable | `digest` | 394 | +| variable | `record` | 409 | +| parameter | `tag` | 410 | +| parameter | `endpoint` | 414 | +| function | `test_invalid_local_or_remote_package_cannot_reach_upload(tmp_path, monkeypatch, defect)` | 424 | +| parameter | `defect` | 424 | +| parameter | `monkeypatch` | 424 | +| parameter | `tmp_path` | 424 | +| variable | `changes` | 426 | +| variable | `directory` | 433 | +| variable | `record` | 433 | +| parameter | `args` | 449 | +| function | `test_exact_staged_files_upload_to_prepared_id_without_overwrite(tmp_path, monkeypatch)` | 455 | +| parameter | `monkeypatch` | 455 | +| parameter | `tmp_path` | 455 | +| variable | `_` | 457 | +| variable | `record` | 457 | +| variable | `writes` | 458 | +| function | `send(identifier, file, name, digest)` | 460 | +| parameter | `digest` | 460 | +| parameter | `file` | 460 | +| parameter | `identifier` | 460 | +| parameter | `name` | 460 | +| variable | `row` | 463 | +| variable | `row` | 465 | +| function | `test_fixed_https_transport_id_digest_and_redirect_refusal(tmp_path, monkeypatch, status, digest_ok)` | 490 | +| parameter | `digest_ok` | 490 | +| parameter | `monkeypatch` | 490 | +| parameter | `status` | 490 | +| parameter | `tmp_path` | 490 | +| variable | `file` | 492 | +| variable | `digest` | 494 | +| variable | `events` | 495 | +| class | `Connection` | 497 | +| function | `__init__(self, host, timeout)` | 500 | +| parameter | `host` | 500 | +| parameter | `self` | 500 | +| parameter | `timeout` | 500 | +| function | `putrequest(self, method, url)` | 503 | +| parameter | `method` | 503 | +| parameter | `self` | 503 | +| parameter | `url` | 503 | +| function | `putheader(self, key, value)` | 506 | +| parameter | `key` | 506 | +| parameter | `self` | 506 | +| parameter | `value` | 506 | +| function | `endheaders(self)` | 510 | +| parameter | `self` | 510 | +| function | `send(self, block)` | 513 | +| parameter | `block` | 513 | +| parameter | `self` | 513 | +| function | `getresponse(self)` | 516 | +| parameter | `self` | 516 | +| parameter | `limit` | 522 | +| parameter | `self` | 522 | +| function | `close(self)` | 532 | +| parameter | `self` | 532 | +| variable | `key` | 544 | +| variable | `value` | 544 | +| function | `test_cli_command_allowlist_precedes_execution(monkeypatch)` | 547 | +| parameter | `monkeypatch` | 547 | +| parameter | `args` | 550 | +| parameter | `kwargs` | 550 | +| variable | `arguments` | 552 | +| function | `test_tag_absence_distinguishes_real404_from_permission_errors(monkeypatch)` | 562 | +| parameter | `monkeypatch` | 562 | +| parameter | `name` | 564 | +| variable | `accepted` | 565 | +| variable | `code` | 565 | +| variable | `error` | 565 | +| variable | `output` | 565 | +| parameter | `args` | 574 | +| parameter | `code` | 574 | +| parameter | `error` | 574 | +| parameter | `kwargs` | 574 | +| parameter | `output` | 574 | +| function | `prepared_context(tmp_path, monkeypatch)` | 585 | +| parameter | `monkeypatch` | 585 | +| parameter | `tmp_path` | 585 | +| variable | `workflow` | 587 | +| variable | `key` | 594 | +| variable | `value` | 594 | +| function | `test_actual_prepare_orders_all_checks_before_the_only_write(tmp_path, monkeypatch, defect)` | 609 | +| parameter | `defect` | 609 | +| parameter | `monkeypatch` | 609 | +| parameter | `tmp_path` | 609 | +| variable | `responses` | 612 | +| variable | `listing` | 613 | +| variable | `record` | 629 | +| variable | `reads` | 632 | +| variable | `writes` | 632 | +| function | `transport(endpoint, body)` | 634 | +| parameter | `body` | 634 | +| parameter | `endpoint` | 634 | +| parameter | `tag` | 655 | +| function | `test_untrusted_context_never_reaches_github(tmp_path, monkeypatch, field, value)` | 676 | +| parameter | `field` | 676 | +| parameter | `monkeypatch` | 676 | +| parameter | `tmp_path` | 676 | +| parameter | `value` | 676 | +| parameter | `args` | 680 | +| parameter | `kwargs` | 680 | +| function | `test_final_verification_rechecks_security_but_keeps_release_draft(tmp_path, monkeypatch, capsys, compiler_passed)` | 686 | +| parameter | `capsys` | 686 | +| parameter | `compiler_passed` | 686 | +| parameter | `monkeypatch` | 686 | +| parameter | `tmp_path` | 686 | +| variable | `record` | 691 | +| variable | `system` | 692 | +| variable | `name` | 693 | +| variable | `responses` | 703 | +| parameter | `tag` | 710 | +| variable | `result` | 717 | +| function | `test_dirty_source_and_publish_operation_are_refused_before_network(tmp_path, monkeypatch)` | 723 | +| parameter | `monkeypatch` | 723 | +| parameter | `tmp_path` | 723 | +| parameter | `args` | 727 | +| parameter | `kwargs` | 727 | +| variable | `error` | 731 | +| function | `test_actual_checked_in_product_version_and_worker_metadata()` | 736 | +| function | `test_api_structured_body_and_fixed_namespace(monkeypatch)` | 741 | +| parameter | `monkeypatch` | 741 | +| variable | `calls` | 743 | +| function | `execute(arguments, data)` | 745 | +| parameter | `arguments` | 745 | +| parameter | `data` | 745 | +| variable | `body` | 751 | ## tests/test_released_service_probe.py diff --git a/docs/releases/v0.4.3.md b/docs/releases/v0.4.3.md index 3ca9704..0727045 100644 --- a/docs/releases/v0.4.3.md +++ b/docs/releases/v0.4.3.md @@ -3,7 +3,9 @@ By Jason Stys. This source update corrects C# instrumented coverage. Hosted compiler, security and native-package checks passed for the reviewed source; see the [dated validation report](../reports/csharp-coverage-2026-11-10.md) for exact -sources, runs and measurement limits. No 0.4.3 download has been published. +sources, runs and measurement limits. Installers and portable archives are +assembled by the guarded release workflow, tested on each native operating +system and accompanied by SHA-256 checksums and embedded source metadata. The new worker retains a C# target's actual exit status separately from Coverlet's collector status. Normal compilation and approved-case results @@ -18,10 +20,20 @@ its source digest during actual packaged-application acceptance. Metadata schema commit identifies the complete source tree. The published 0.4.2 downloads, their source, captured compiler observations and -known partial Coverlet coverage remain historical evidence. This change does -not replace those assets, install an update on the owner's PC, repair Docker, -alter existing accounts, or permit target execution outside an isolated worker. +known partial Coverlet coverage remain historical evidence. The 0.4.3 update +keeps the same application identity and account/vault format; no account or +history migration is introduced. Back up private data before upgrading and quit +the app cleanly so an unlocked workspace can be sealed. Existing credentials +remain valid. Private data stays outside the installation and repository. -Any eventual fresh downloads remain subject to checksums, source-bound release -gates and actual platform acceptance. Launching an extracted package is distinct -from testing an operating-system installation or upgrade transaction. +The release gate now requires successful compiler acceptance, validation, native +packages and all three full-main CodeQL analyses for the exact source. Independent +final download/installer acceptance precedes publication. Packages are unsigned; +no signing or notarization is claimed. + +This update does not install, start, repair or rebuild Docker. Compilation still +requires a working isolated engine and a separately prepared matching +`code-flow-worker:0.4.3` image. Without it, editing and review remain available and +execution is explicitly blocked. Do not run source directly on the host as a +fallback. Launching an extracted package is distinct from testing an +operating-system installation or upgrade transaction. diff --git a/scripts/release_gate.py b/scripts/release_gate.py index b53aed3..adffe91 100644 --- a/scripts/release_gate.py +++ b/scripts/release_gate.py @@ -35,7 +35,7 @@ ROOT = Path(__file__).resolve().parents[1] REPOSITORY = "JasonStys/code-flow-studio" BASE = f"repos/{REPOSITORY}" -WORKFLOWS = ("validate.yml", "packages.yml", "codeql.yml") +WORKFLOWS = ("validate.yml", "packages.yml", "codeql.yml", "compiler-execution.yml") CATEGORIES = {"/language:python", "/language:javascript-typescript", "/language:actions"} MAX_ASSET = 1024**3 diff --git a/tests/test_release_gate.py b/tests/test_release_gate.py index 3ee17fe..ac2f015 100644 --- a/tests/test_release_gate.py +++ b/tests/test_release_gate.py @@ -1,10 +1,10 @@ # # Generated definition map; @L is the exact declaration line. # Function/class docs explain behavior; variables/parameters are scoped inputs, outputs or state. -# classes: Connection@L472 -# functions: source_responses()@L41; strict_reader(responses)@L81; read(endpoint)@L84; release_record()@L92; test_real_source_response_contract_passes(monkeypatch)@L110; test_zero_unknown_or_noninteger_codeql_rule_counts_never_authorize_release(monkeypatch, count)@L117; test_source_failures_cannot_authorize_release(monkeypatch, defect)@L149; test_unknown_or_full_scan_pages_fail_closed(monkeypatch)@L193; test_prepared_numeric_id_cannot_be_public_or_swapped(monkeypatch, change)@L210; test_actual_workflow_policy_rejects_security_mutants(before, after)@L234; test_real_final_job_permission_mutations_cannot_authorize_draft(scope, before, after)@L250; test_commented_duplicate_or_unparsed_final_permissions_fail_closed(extra)@L266; test_legitimate_final_scope_comments_are_parsed_not_ignored()@L276; test_duplicate_whole_final_permission_blocks_fail_closed(position, field)@L290; test_package_schema_is_strict(value)@L305; test_metadata_duplicates_nonfinite_and_nonobjects_are_errors(source)@L313; portable(file, value, worker, system, symlink)@L319; test_actual_platform_archive_metadata(tmp_path, system, suffix)@L343; test_archive_metadata_link_is_never_followed(tmp_path)@L350; staged(tmp_path, monkeypatch)@L358; test_invalid_local_or_remote_package_cannot_reach_upload(tmp_path, monkeypatch, defect)@L399; test_exact_staged_files_upload_to_prepared_id_without_overwrite(tmp_path, monkeypatch)@L430; send(identifier, file, name, digest)@L435; test_fixed_https_transport_id_digest_and_redirect_refusal(tmp_path, monkeypatch, status, digest_ok)@L465; __init__(self, host, timeout)@L475; putrequest(self, method, url)@L478; putheader(self, key, value)@L481; endheaders(self)@L485; send(self, block)@L488; getresponse(self)@L491; close(self)@L507; test_cli_command_allowlist_precedes_execution(monkeypatch)@L522; test_tag_absence_distinguishes_real404_from_permission_errors(monkeypatch)@L537; prepared_context(tmp_path, monkeypatch)@L560; test_actual_prepare_orders_all_checks_before_the_only_write(tmp_path, monkeypatch, defect)@L584; transport(endpoint, body)@L605; test_untrusted_context_never_reaches_github(tmp_path, monkeypatch, field, value)@L646; test_final_verification_rechecks_security_but_keeps_release_draft(tmp_path, monkeypatch, capsys)@L655; test_dirty_source_and_publish_operation_are_refused_before_network(tmp_path, monkeypatch)@L683; test_actual_checked_in_product_version_and_worker_metadata()@L696; test_api_structured_body_and_fixed_namespace(monkeypatch)@L701; execute(arguments, data)@L705 -# variables: SOURCE@L30; VALUE@L31; result@L43; workflow@L44; category@L73; index@L73; prefix@L94; responses@L119; row@L120; responses@L151; key@L152; row@L153; field@L155; value@L155; analyses@L177; original@L236; changed@L238; original@L252; prefix@L254; separator@L254; verifier@L254; changed@L256; original@L268; prefix@L269; separator@L269; verifier@L269; changed@L271; original@L278; prefix@L279; separator@L279; verifier@L279; changed@L281; original@L292; prefix@L293; separator@L293; verifier@L293; extra@L295; changed@L297; changed@L299; bad@L307; prefix@L321; members@L322; archive@L327; content@L328; name@L328; archive@L331; content@L332; name@L332; entry@L333; file@L345; file@L352; directory@L362; installer@L364; archive@L366; file@L368; digest@L369; record@L384; changes@L401; directory@L408; record@L408; _@L432; record@L432; writes@L433; row@L438; row@L440; file@L467; digest@L469; events@L470; key@L519; value@L519; arguments@L527; accepted@L540; code@L540; error@L540; output@L540; workflow@L562; key@L569; value@L569; responses@L587; listing@L588; record@L600; reads@L603; writes@L603; record@L660; system@L661; name@L662; responses@L672; result@L677; error@L691; calls@L703; body@L711 -# parameters: responses@L81; endpoint@L84; kwargs@L84; changes@L92; monkeypatch@L110; count@L117; monkeypatch@L117; defect@L149; monkeypatch@L149; monkeypatch@L193; endpoint@L195; change@L210; monkeypatch@L210; tag@L213; after@L234; before@L234; after@L250; before@L250; scope@L250; extra@L266; field@L290; position@L290; value@L305; source@L313; file@L319; symlink@L319; system@L319; value@L319; worker@L319; suffix@L343; system@L343; tmp_path@L343; tmp_path@L350; metadata_changes@L358; monkeypatch@L358; tmp_path@L358; tag@L385; endpoint@L389; defect@L399; monkeypatch@L399; tmp_path@L399; args@L424; monkeypatch@L430; tmp_path@L430; digest@L435; file@L435; identifier@L435; name@L435; digest_ok@L465; monkeypatch@L465; status@L465; tmp_path@L465; host@L475; self@L475; timeout@L475; method@L478; self@L478; url@L478; key@L481; self@L481; value@L481; self@L485; block@L488; self@L488; self@L491; limit@L497; self@L497; self@L507; monkeypatch@L522; args@L525; kwargs@L525; monkeypatch@L537; name@L539; args@L549; code@L549; error@L549; kwargs@L549; output@L549; monkeypatch@L560; tmp_path@L560; defect@L584; monkeypatch@L584; tmp_path@L584; body@L605; endpoint@L605; tag@L625; field@L646; monkeypatch@L646; tmp_path@L646; value@L646; args@L650; kwargs@L650; capsys@L655; monkeypatch@L655; tmp_path@L655; tag@L675; monkeypatch@L683; tmp_path@L683; args@L687; kwargs@L687; monkeypatch@L701; arguments@L705; data@L705 +# classes: Connection@L497 +# functions: source_responses()@L41; strict_reader(responses)@L81; read(endpoint)@L84; release_record()@L92; test_real_source_response_contract_passes(monkeypatch)@L110; test_compiler_acceptance_is_an_independent_exact_main_release_prerequisite(monkeypatch, defect)@L117; test_zero_unknown_or_noninteger_codeql_rule_counts_never_authorize_release(monkeypatch, count)@L142; test_source_failures_cannot_authorize_release(monkeypatch, defect)@L174; test_unknown_or_full_scan_pages_fail_closed(monkeypatch)@L218; test_prepared_numeric_id_cannot_be_public_or_swapped(monkeypatch, change)@L235; test_actual_workflow_policy_rejects_security_mutants(before, after)@L259; test_real_final_job_permission_mutations_cannot_authorize_draft(scope, before, after)@L275; test_commented_duplicate_or_unparsed_final_permissions_fail_closed(extra)@L291; test_legitimate_final_scope_comments_are_parsed_not_ignored()@L301; test_duplicate_whole_final_permission_blocks_fail_closed(position, field)@L315; test_package_schema_is_strict(value)@L330; test_metadata_duplicates_nonfinite_and_nonobjects_are_errors(source)@L338; portable(file, value, worker, system, symlink)@L344; test_actual_platform_archive_metadata(tmp_path, system, suffix)@L368; test_archive_metadata_link_is_never_followed(tmp_path)@L375; staged(tmp_path, monkeypatch)@L383; test_invalid_local_or_remote_package_cannot_reach_upload(tmp_path, monkeypatch, defect)@L424; test_exact_staged_files_upload_to_prepared_id_without_overwrite(tmp_path, monkeypatch)@L455; send(identifier, file, name, digest)@L460; test_fixed_https_transport_id_digest_and_redirect_refusal(tmp_path, monkeypatch, status, digest_ok)@L490; __init__(self, host, timeout)@L500; putrequest(self, method, url)@L503; putheader(self, key, value)@L506; endheaders(self)@L510; send(self, block)@L513; getresponse(self)@L516; close(self)@L532; test_cli_command_allowlist_precedes_execution(monkeypatch)@L547; test_tag_absence_distinguishes_real404_from_permission_errors(monkeypatch)@L562; prepared_context(tmp_path, monkeypatch)@L585; test_actual_prepare_orders_all_checks_before_the_only_write(tmp_path, monkeypatch, defect)@L609; transport(endpoint, body)@L634; test_untrusted_context_never_reaches_github(tmp_path, monkeypatch, field, value)@L676; test_final_verification_rechecks_security_but_keeps_release_draft(tmp_path, monkeypatch, capsys, compiler_passed)@L686; test_dirty_source_and_publish_operation_are_refused_before_network(tmp_path, monkeypatch)@L723; test_actual_checked_in_product_version_and_worker_metadata()@L736; test_api_structured_body_and_fixed_namespace(monkeypatch)@L741; execute(arguments, data)@L745 +# variables: SOURCE@L30; VALUE@L31; result@L43; workflow@L44; category@L73; index@L73; prefix@L94; responses@L119; endpoint@L120; row@L121; responses@L144; row@L145; responses@L176; key@L177; row@L178; field@L180; value@L180; analyses@L202; original@L261; changed@L263; original@L277; prefix@L279; separator@L279; verifier@L279; changed@L281; original@L293; prefix@L294; separator@L294; verifier@L294; changed@L296; original@L303; prefix@L304; separator@L304; verifier@L304; changed@L306; original@L317; prefix@L318; separator@L318; verifier@L318; extra@L320; changed@L322; changed@L324; bad@L332; prefix@L346; members@L347; archive@L352; content@L353; name@L353; archive@L356; content@L357; name@L357; entry@L358; file@L370; file@L377; directory@L387; installer@L389; archive@L391; file@L393; digest@L394; record@L409; changes@L426; directory@L433; record@L433; _@L457; record@L457; writes@L458; row@L463; row@L465; file@L492; digest@L494; events@L495; key@L544; value@L544; arguments@L552; accepted@L565; code@L565; error@L565; output@L565; workflow@L587; key@L594; value@L594; responses@L612; listing@L613; record@L629; reads@L632; writes@L632; record@L691; system@L692; name@L693; responses@L703; result@L717; error@L731; calls@L743; body@L751 +# parameters: responses@L81; endpoint@L84; kwargs@L84; changes@L92; monkeypatch@L110; defect@L117; monkeypatch@L117; count@L142; monkeypatch@L142; defect@L174; monkeypatch@L174; monkeypatch@L218; endpoint@L220; change@L235; monkeypatch@L235; tag@L238; after@L259; before@L259; after@L275; before@L275; scope@L275; extra@L291; field@L315; position@L315; value@L330; source@L338; file@L344; symlink@L344; system@L344; value@L344; worker@L344; suffix@L368; system@L368; tmp_path@L368; tmp_path@L375; metadata_changes@L383; monkeypatch@L383; tmp_path@L383; tag@L410; endpoint@L414; defect@L424; monkeypatch@L424; tmp_path@L424; args@L449; monkeypatch@L455; tmp_path@L455; digest@L460; file@L460; identifier@L460; name@L460; digest_ok@L490; monkeypatch@L490; status@L490; tmp_path@L490; host@L500; self@L500; timeout@L500; method@L503; self@L503; url@L503; key@L506; self@L506; value@L506; self@L510; block@L513; self@L513; self@L516; limit@L522; self@L522; self@L532; monkeypatch@L547; args@L550; kwargs@L550; monkeypatch@L562; name@L564; args@L574; code@L574; error@L574; kwargs@L574; output@L574; monkeypatch@L585; tmp_path@L585; defect@L609; monkeypatch@L609; tmp_path@L609; body@L634; endpoint@L634; tag@L655; field@L676; monkeypatch@L676; tmp_path@L676; value@L676; args@L680; kwargs@L680; capsys@L686; compiler_passed@L686; monkeypatch@L686; tmp_path@L686; tag@L710; monkeypatch@L723; tmp_path@L723; args@L727; kwargs@L727; monkeypatch@L741; arguments@L745; data@L745 # """Jason Stys: independent real-shape main/security/draft fixtures and actual archive/transport oracles. @@ -41,7 +41,7 @@ def source_responses(): """Independent documented GitHub shapes: exactly one main push and three processed categories.""" result = {gate.BASE + "/git/ref/heads/main": {"object": {"sha": SOURCE}}} - for workflow in ("validate.yml", "packages.yml", "codeql.yml"): + for workflow in ("validate.yml", "packages.yml", "codeql.yml", "compiler-execution.yml"): result[f"{gate.BASE}/actions/workflows/{workflow}/runs?head_sha={SOURCE}&per_page=100"] = { "total_count": 1, "workflow_runs": [ @@ -113,6 +113,31 @@ def test_real_source_response_contract_passes(monkeypatch): gate.require_source(VALUE) +@pytest.mark.parametrize("defect", ["missing", "failed", "running", "wrong-source", "pr-only", "wrong-path", "wrong-repository"]) +def test_compiler_acceptance_is_an_independent_exact_main_release_prerequisite(monkeypatch, defect): + """Green unit/package/CodeQL lanes cannot substitute for real compiler acceptance.""" + responses = source_responses() + endpoint = f"{gate.BASE}/actions/workflows/compiler-execution.yml/runs?head_sha={SOURCE}&per_page=100" + row = responses[endpoint]["workflow_runs"][0] + if defect == "missing": + responses[endpoint] = {"total_count": 0, "workflow_runs": []} + elif defect == "failed": + row["conclusion"] = "failure" + elif defect == "running": + row["status"] = "in_progress" + elif defect == "wrong-source": + row["head_sha"] = "b" * 40 + elif defect == "pr-only": + row["event"] = "pull_request" + elif defect == "wrong-path": + row["path"] = ".github/workflows/validate.yml" + else: + row["repository"]["full_name"] = "other/project" + monkeypatch.setattr(gate, "api", strict_reader(responses)) + with pytest.raises(ValueError): + gate.require_source(VALUE) + + @pytest.mark.parametrize("count", [None, 0, True, -1, "50"]) def test_zero_unknown_or_noninteger_codeql_rule_counts_never_authorize_release(monkeypatch, count): """An error-free category without actual executed rules is not a processed security prerequisite.""" @@ -579,7 +604,7 @@ def prepared_context(tmp_path, monkeypatch): @pytest.mark.parametrize( - "defect", ["none", "incomplete-main", "open-alert", "existing-draft", "existing-public"] + "defect", ["none", "incomplete-main", "failed-compiler", "open-alert", "existing-draft", "existing-public"] ) def test_actual_prepare_orders_all_checks_before_the_only_write(tmp_path, monkeypatch, defect): """Execute the actual CLI through complete endpoint fixtures; failed prerequisites never create or reuse a release.""" @@ -592,6 +617,10 @@ def test_actual_prepare_orders_all_checks_before_the_only_write(tmp_path, monkey responses[f"{gate.BASE}/actions/workflows/validate.yml/runs?head_sha={SOURCE}&per_page=100"][ "workflow_runs" ][0]["status"] = "in_progress" + elif defect == "failed-compiler": + responses[f"{gate.BASE}/actions/workflows/compiler-execution.yml/runs?head_sha={SOURCE}&per_page=100"][ + "workflow_runs" + ][0]["conclusion"] = "failure" elif defect == "open-alert": responses[ gate.BASE + "/code-scanning/alerts?ref=refs%2Fheads%2Fmain&state=open&per_page=100&page=1" @@ -615,6 +644,7 @@ def transport(endpoint, *, body=None): "body": "Owned synthetic release notes", } assert listing in reads and len(reads) >= 6 + assert f"{gate.BASE}/actions/workflows/compiler-execution.yml/runs?head_sha={SOURCE}&per_page=100" in reads writes.append(body) return release_record() assert endpoint in responses, endpoint @@ -652,7 +682,8 @@ def test_untrusted_context_never_reaches_github(tmp_path, monkeypatch, field, va gate.main() -def test_final_verification_rechecks_security_but_keeps_release_draft(tmp_path, monkeypatch, capsys): +@pytest.mark.parametrize("compiler_passed", [True, False]) +def test_final_verification_rechecks_security_but_keeps_release_draft(tmp_path, monkeypatch, capsys, compiler_passed): """Execute the final CLI path against full real-shape reads; it has no promotion transport or write.""" prepared_context(tmp_path, monkeypatch) monkeypatch.setattr(sys, "argv", ["release_gate", "verify"]) @@ -670,9 +701,18 @@ def test_final_verification_rechecks_security_but_keeps_release_draft(tmp_path, } ) responses = source_responses() + if not compiler_passed: + responses[f"{gate.BASE}/actions/workflows/compiler-execution.yml/runs?head_sha={SOURCE}&per_page=100"][ + "workflow_runs" + ][0]["conclusion"] = "failure" responses[gate.BASE + "/releases/123"] = record monkeypatch.setattr(gate, "api", strict_reader(responses)) monkeypatch.setattr(gate, "no_tag", lambda tag: None) + if not compiler_passed: + with pytest.raises(ValueError): + gate.main() + assert record["draft"] is True and capsys.readouterr().out == "" + return gate.main() result = json.loads(capsys.readouterr().out) assert result["draft"] is True and result["assets"] == 12 and result["source"] == SOURCE