diff --git a/.github/workflows/release-native.yml b/.github/workflows/release-native.yml index e918705..976a599 100644 --- a/.github/workflows/release-native.yml +++ b/.github/workflows/release-native.yml @@ -107,7 +107,10 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 10 permissions: - contents: read + # GitHub exposes draft metadata only to push-capable credentials. + # https://docs.github.com/en/rest/releases/releases#list-releases + # The guarded verify command performs reads only and never promotes a draft. + contents: write actions: read security-events: read steps: diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index f54ee11..d7553ff 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -93,7 +93,7 @@ jobs: with: python-version: '3.12' - run: pip install -e '.[dev]' - - run: docker build -t code-flow-worker:0.4.1 runner + - run: docker build -t code-flow-worker:0.4.2 runner - run: python -m scripts.worker_checks - run: python -m scripts.private_worker_checks - run: python -m scripts.push_analysis diff --git a/README.md b/README.md index 2a8ab30..1676a5a 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ An independently written successor to a group AI-in-SDLC capstone: a local-first development workbench for isolated polyglot checks, configurable AI proposals, and replayable revision evidence. -**Version 0.4.1: bounded worker reports and verified source review.** Create an account, unlock your encrypted source/history workspace, and use the same interface in the desktop app or your browser. Filter diagnostics by file or unused-code category, select an exact line, inspect its read-only evidence snapshot, and open the editable draft only when its SHA-256 still matches. No public backend, shared login, analytics, ads or cloud AI calls are enabled by default. By [Jason Stys](https://github.com/JasonStys); [contact via LinkedIn](https://www.linkedin.com/in/jason-stys-31b30b351/). +**Version 0.4.2: bounded reports, safe shutdown and corrected draft verification.** Create an account, unlock your encrypted source/history workspace, and use the same interface in the desktop app or your browser. Filter diagnostics by file or unused-code category, select an exact line, inspect its read-only evidence snapshot, and open the editable draft only when its SHA-256 still matches. No public backend, shared login, analytics, ads or cloud AI calls are enabled by default. By [Jason Stys](https://github.com/JasonStys); [contact via LinkedIn](https://www.linkedin.com/in/jason-stys-31b30b351/). Download OS-specific installers/portable packages from [Releases](https://github.com/JasonStys/code-flow-studio/releases). Read [private-account operation](docs/private-accounts.md), [current validation](docs/validation-polyglot.md), [historical Python-location validation](docs/validation-locations.md) and [security boundaries](docs/security.md). Packages are unsigned. This is a local development tool, not a public hostile-code SaaS sandbox or an autonomous correctness oracle. @@ -38,10 +38,15 @@ The [source inspection workflow](docs/source-navigation.md) explains stale/legac ## Run and install -Version 0.4.1 includes bounded worker-report reads and verified source review. Its [release notes](docs/releases/v0.4.1.md) +Version 0.4.2 retains bounded worker-report reads, verified source review and safe +local shutdown, and corrects the final draft-verification job's required metadata +visibility. Its [release notes](docs/releases/v0.4.2.md) and [full-main finding/validation report](docs/reports/codeql-main-fixes-2026-11-09.md) describe the bounded worker reader and the fresh native/download checks required -before release promotion. Check Releases for the currently published downloads; a source version alone does not replace one. +before release promotion. The 0.4.1 draft passed native builds but its final +metadata check lacked GitHub's required draft visibility; that draft and all its +assets are preserved. See the [observed CI fault and narrow correction](docs/reports/draft-release-visibility-2026-11-10.md). +Check Releases for the currently published downloads; a source version alone does not replace one. For end users, use the checksummed OS-specific packages in Releases. The UI runtime is bundled. Open the app's **Code Flow → Open local website** menu for browser use; no login token appears in the URL. Compiler jobs require an isolated local Unix/named-pipe Docker engine and the trusted worker image. Missing isolation is blocked, never passed; there is no host-execution fallback. Save/edit/static review/history/help remain usable without Docker. diff --git a/docs/README.md b/docs/README.md index fa63a3d..e414332 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,7 +2,9 @@ - [Verified source inspection and draft navigation](source-navigation.md), with [observed practical-workflow validation](reports/practical-workflows-2026-11-09.md). - [Worker sanitizer compatibility and separate analyzer audit](worker-toolchain.md). -- [0.4.1 hotfix preparation](releases/v0.4.1.md): descriptor-bound worker JSON reads; full-main CodeQL fixes and pending native acceptance. +- [0.4.2 delivery correction](releases/v0.4.2.md): required draft visibility for the read-only final verifier; coherent new product/worker metadata and preserved prior assets. +- [Draft-verification CI report](reports/draft-release-visibility-2026-11-10.md): actual failed final job, official visibility rule, narrow permission/policy regression and separate pending delivery gates. +- [0.4.1 historical hotfix](releases/v0.4.1.md): descriptor-bound worker reads and safe shutdown; native builds passed but final draft metadata visibility failed. - [0.4.1 finding and release-guard validation](reports/codeql-main-fixes-2026-11-09.md): exact original alerts, independent negative cases, local compiled package provenance and required hosted acceptance. - [0.4.0 release notes](releases/v0.4.0.md): exact-source publication gate, unchanged data formats and matching worker-image update. @@ -26,4 +28,3 @@ - [Research and provenance](research.md): source review and primary technical references. - [Remaining scope](remaining-scope.md): implemented contracts, missing integrations and acceptance milestones. - [Evidence files](evidence/worker-evidence.json): genuine isolated worker reports; training and inference reports are in the same directory and linked from the validation report. - diff --git a/docs/code-index.md b/docs/code-index.md index 16c2bd9..b0fb7b6 100644 --- a/docs/code-index.md +++ b/docs/code-index.md @@ -891,117 +891,127 @@ Generated declaration locations. Python uses AST; JS/TS uses declaration-line ma | function | `workflow_policy(text)` | 185 | | parameter | `text` | 185 | | variable | `operation` | 196 | -| function | `context()` | 217 | -| variable | `value` | 219 | -| function | `complete_list(endpoint)` | 233 | -| parameter | `endpoint` | 233 | -| variable | `rows` | 235 | -| variable | `page` | 236 | -| variable | `current` | 237 | -| function | `require_source(value)` | 245 | -| parameter | `value` | 245 | -| variable | `source` | 247 | -| variable | `workflow` | 249 | -| variable | `envelope` | 250 | -| variable | `rows` | 251 | -| variable | `row` | 256 | -| variable | `row` | 266 | -| variable | `analyses` | 269 | -| variable | `current` | 270 | -| variable | `row` | 270 | -| variable | `row` | 272 | -| variable | `row` | 275 | -| function | `draft(identifier, value)` | 297 | -| parameter | `identifier` | 297 | -| parameter | `value` | 297 | -| variable | `record` | 299 | -| function | `names(version, system)` | 311 | -| parameter | `system` | 311 | -| parameter | `version` | 311 | -| variable | `values` | 313 | -| variable | `name` | 319 | -| variable | `payload` | 319 | -| function | `attached(record, version, complete)` | 322 | -| parameter | `complete` | 322 | -| parameter | `record` | 322 | -| parameter | `version` | 322 | -| variable | `expected` | 324 | -| variable | `name` | 325 | -| variable | `system` | 325 | -| variable | `result` | 327 | -| variable | `identifiers` | 328 | -| variable | `row` | 329 | -| variable | `name` | 330 | -| function | `archive_metadata(file, system)` | 352 | -| parameter | `file` | 352 | -| parameter | `system` | 352 | -| variable | `prefix` | 358 | -| variable | `expected` | 359 | -| variable | `found` | 360 | -| variable | `archive` | 362 | -| variable | `member` | 365 | -| variable | `member` | 368 | -| variable | `archive` | 379 | -| variable | `expanded` | 380 | -| variable | `index` | 381 | -| variable | `member` | 381 | -| variable | `expanded` | 383 | -| variable | `name` | 385 | -| variable | `stream` | 391 | -| variable | `value` | 394 | -| function | `upload_bytes(identifier, file, name, expected_digest)` | 399 | -| parameter | `expected_digest` | 399 | -| parameter | `file` | 399 | -| parameter | `identifier` | 399 | -| parameter | `name` | 399 | -| variable | `token` | 401 | -| variable | `connection` | 403 | -| variable | `started` | 404 | -| variable | `stream` | 406 | -| variable | `size` | 407 | -| variable | `key` | 412 | -| variable | `value` | 412 | -| variable | `remaining` | 422 | -| variable | `uploaded_digest` | 423 | -| variable | `block` | 426 | -| variable | `remaining` | 430 | -| variable | `response` | 435 | -| variable | `content` | 437 | -| variable | `result` | 439 | -| function | `upload(value, identifier)` | 451 | -| parameter | `identifier` | 451 | -| parameter | `value` | 451 | -| variable | `system` | 453 | -| variable | `name` | 454 | -| variable | `paths` | 454 | -| variable | `receipt` | 455 | -| variable | `archive` | 469 | -| variable | `archive_value` | 470 | -| variable | `worker` | 470 | -| variable | `payload` | 475 | -| variable | `sidecar` | 475 | -| variable | `stream` | 484 | -| variable | `digest` | 485 | -| variable | `existing` | 490 | -| variable | `file` | 492 | -| variable | `file` | 495 | -| variable | `current` | 496 | -| variable | `name` | 497 | -| variable | `stream` | 499 | -| variable | `digest` | 500 | -| variable | `observed` | 502 | -| function | `main()` | 509 | -| variable | `parser` | 511 | -| variable | `operation` | 513 | -| variable | `target` | 515 | -| variable | `value` | 519 | -| variable | `tag` | 522 | -| variable | `created` | 527 | -| variable | `record` | 538 | -| variable | `output` | 540 | -| variable | `raw` | 543 | -| variable | `identifier` | 545 | -| variable | `record` | 550 | +| variable | `verifier` | 209 | +| variable | `fields` | 210 | +| variable | `line` | 211 | +| variable | `field` | 214 | +| variable | `permissions` | 221 | +| variable | `scopes` | 222 | +| variable | `line` | 224 | +| variable | `scope` | 227 | +| variable | `_` | 232 | +| variable | `name` | 232 | +| function | `context()` | 244 | +| variable | `value` | 246 | +| function | `complete_list(endpoint)` | 260 | +| parameter | `endpoint` | 260 | +| variable | `rows` | 262 | +| variable | `page` | 263 | +| variable | `current` | 264 | +| function | `require_source(value)` | 272 | +| parameter | `value` | 272 | +| variable | `source` | 274 | +| variable | `workflow` | 276 | +| variable | `envelope` | 277 | +| variable | `rows` | 278 | +| variable | `row` | 283 | +| variable | `row` | 293 | +| variable | `analyses` | 296 | +| variable | `current` | 297 | +| variable | `row` | 297 | +| variable | `row` | 299 | +| variable | `row` | 302 | +| function | `draft(identifier, value)` | 324 | +| parameter | `identifier` | 324 | +| parameter | `value` | 324 | +| variable | `record` | 326 | +| function | `names(version, system)` | 338 | +| parameter | `system` | 338 | +| parameter | `version` | 338 | +| variable | `values` | 340 | +| variable | `name` | 346 | +| variable | `payload` | 346 | +| function | `attached(record, version, complete)` | 349 | +| parameter | `complete` | 349 | +| parameter | `record` | 349 | +| parameter | `version` | 349 | +| variable | `expected` | 351 | +| variable | `name` | 352 | +| variable | `system` | 352 | +| variable | `result` | 354 | +| variable | `identifiers` | 355 | +| variable | `row` | 356 | +| variable | `name` | 357 | +| function | `archive_metadata(file, system)` | 379 | +| parameter | `file` | 379 | +| parameter | `system` | 379 | +| variable | `prefix` | 385 | +| variable | `expected` | 386 | +| variable | `found` | 387 | +| variable | `archive` | 389 | +| variable | `member` | 392 | +| variable | `member` | 395 | +| variable | `archive` | 406 | +| variable | `expanded` | 407 | +| variable | `index` | 408 | +| variable | `member` | 408 | +| variable | `expanded` | 410 | +| variable | `name` | 412 | +| variable | `stream` | 418 | +| variable | `value` | 421 | +| function | `upload_bytes(identifier, file, name, expected_digest)` | 426 | +| parameter | `expected_digest` | 426 | +| parameter | `file` | 426 | +| parameter | `identifier` | 426 | +| parameter | `name` | 426 | +| variable | `token` | 428 | +| variable | `connection` | 430 | +| variable | `started` | 431 | +| variable | `stream` | 433 | +| variable | `size` | 434 | +| variable | `key` | 439 | +| variable | `value` | 439 | +| variable | `remaining` | 449 | +| variable | `uploaded_digest` | 450 | +| variable | `block` | 453 | +| variable | `remaining` | 457 | +| variable | `response` | 462 | +| variable | `content` | 464 | +| variable | `result` | 466 | +| function | `upload(value, identifier)` | 478 | +| parameter | `identifier` | 478 | +| parameter | `value` | 478 | +| variable | `system` | 480 | +| variable | `name` | 481 | +| variable | `paths` | 481 | +| variable | `receipt` | 482 | +| variable | `archive` | 496 | +| variable | `archive_value` | 497 | +| variable | `worker` | 497 | +| variable | `payload` | 502 | +| variable | `sidecar` | 502 | +| variable | `stream` | 511 | +| variable | `digest` | 512 | +| variable | `existing` | 517 | +| variable | `file` | 519 | +| variable | `file` | 522 | +| variable | `current` | 523 | +| variable | `name` | 524 | +| variable | `stream` | 526 | +| variable | `digest` | 527 | +| variable | `observed` | 529 | +| function | `main()` | 536 | +| variable | `parser` | 538 | +| variable | `operation` | 540 | +| variable | `target` | 542 | +| variable | `value` | 546 | +| variable | `tag` | 549 | +| variable | `created` | 554 | +| variable | `record` | 565 | +| variable | `output` | 567 | +| variable | `raw` | 570 | +| variable | `identifier` | 572 | +| variable | `record` | 577 | ## scripts/training_checks.py @@ -3422,168 +3432,200 @@ Generated declaration locations. Python uses AST; JS/TS uses declaration-line ma | parameter | `before` | 234 | | variable | `original` | 236 | | variable | `changed` | 238 | -| function | `test_package_schema_is_strict(value)` | 245 | -| parameter | `value` | 245 | -| variable | `bad` | 247 | -| function | `test_metadata_duplicates_nonfinite_and_nonobjects_are_errors(source)` | 253 | -| parameter | `source` | 253 | -| function | `portable(file, value, worker, system, symlink)` | 259 | -| parameter | `file` | 259 | -| parameter | `symlink` | 259 | -| parameter | `system` | 259 | -| parameter | `value` | 259 | -| parameter | `worker` | 259 | -| variable | `prefix` | 261 | -| variable | `members` | 262 | -| variable | `archive` | 267 | -| variable | `content` | 268 | -| variable | `name` | 268 | -| variable | `archive` | 271 | -| variable | `content` | 272 | -| variable | `name` | 272 | -| variable | `entry` | 273 | -| function | `test_actual_platform_archive_metadata(tmp_path, system, suffix)` | 283 | -| parameter | `suffix` | 283 | -| parameter | `system` | 283 | -| parameter | `tmp_path` | 283 | -| variable | `file` | 285 | -| function | `test_archive_metadata_link_is_never_followed(tmp_path)` | 290 | -| parameter | `tmp_path` | 290 | -| variable | `file` | 292 | -| function | `staged(tmp_path, monkeypatch)` | 298 | -| parameter | `metadata_changes` | 298 | -| parameter | `monkeypatch` | 298 | -| parameter | `tmp_path` | 298 | -| variable | `directory` | 302 | -| variable | `installer` | 304 | -| variable | `archive` | 306 | -| variable | `file` | 308 | -| variable | `digest` | 309 | -| variable | `record` | 324 | -| parameter | `tag` | 325 | -| parameter | `endpoint` | 329 | -| function | `test_invalid_local_or_remote_package_cannot_reach_upload(tmp_path, monkeypatch, defect)` | 339 | -| parameter | `defect` | 339 | -| parameter | `monkeypatch` | 339 | -| parameter | `tmp_path` | 339 | -| variable | `changes` | 341 | -| variable | `directory` | 348 | -| variable | `record` | 348 | -| parameter | `args` | 364 | -| function | `test_exact_staged_files_upload_to_prepared_id_without_overwrite(tmp_path, monkeypatch)` | 370 | -| parameter | `monkeypatch` | 370 | -| parameter | `tmp_path` | 370 | -| variable | `_` | 372 | -| variable | `record` | 372 | -| variable | `writes` | 373 | -| function | `send(identifier, file, name, digest)` | 375 | -| parameter | `digest` | 375 | -| parameter | `file` | 375 | -| parameter | `identifier` | 375 | -| parameter | `name` | 375 | -| variable | `row` | 378 | -| variable | `row` | 380 | -| function | `test_fixed_https_transport_id_digest_and_redirect_refusal(tmp_path, monkeypatch, status, digest_ok)` | 405 | -| parameter | `digest_ok` | 405 | -| parameter | `monkeypatch` | 405 | -| parameter | `status` | 405 | -| parameter | `tmp_path` | 405 | -| variable | `file` | 407 | -| variable | `digest` | 409 | -| variable | `events` | 410 | -| class | `Connection` | 412 | -| function | `__init__(self, host, timeout)` | 415 | -| parameter | `host` | 415 | -| parameter | `self` | 415 | -| parameter | `timeout` | 415 | -| function | `putrequest(self, method, url)` | 418 | -| parameter | `method` | 418 | -| parameter | `self` | 418 | -| parameter | `url` | 418 | -| function | `putheader(self, key, value)` | 421 | -| parameter | `key` | 421 | -| parameter | `self` | 421 | -| parameter | `value` | 421 | -| function | `endheaders(self)` | 425 | -| parameter | `self` | 425 | -| function | `send(self, block)` | 428 | -| parameter | `block` | 428 | -| parameter | `self` | 428 | -| function | `getresponse(self)` | 431 | -| parameter | `self` | 431 | -| parameter | `limit` | 437 | -| parameter | `self` | 437 | -| function | `close(self)` | 447 | -| parameter | `self` | 447 | -| variable | `key` | 459 | -| variable | `value` | 459 | -| function | `test_cli_command_allowlist_precedes_execution(monkeypatch)` | 462 | -| parameter | `monkeypatch` | 462 | -| parameter | `args` | 465 | -| parameter | `kwargs` | 465 | -| variable | `arguments` | 467 | -| function | `test_tag_absence_distinguishes_real404_from_permission_errors(monkeypatch)` | 477 | -| parameter | `monkeypatch` | 477 | -| parameter | `name` | 479 | -| variable | `accepted` | 480 | -| variable | `code` | 480 | -| variable | `error` | 480 | -| variable | `output` | 480 | -| parameter | `args` | 489 | -| parameter | `code` | 489 | -| parameter | `error` | 489 | -| parameter | `kwargs` | 489 | -| parameter | `output` | 489 | -| function | `prepared_context(tmp_path, monkeypatch)` | 500 | -| parameter | `monkeypatch` | 500 | -| parameter | `tmp_path` | 500 | -| variable | `workflow` | 502 | -| variable | `key` | 509 | -| variable | `value` | 509 | -| function | `test_actual_prepare_orders_all_checks_before_the_only_write(tmp_path, monkeypatch, defect)` | 524 | -| parameter | `defect` | 524 | -| parameter | `monkeypatch` | 524 | -| parameter | `tmp_path` | 524 | -| variable | `responses` | 527 | -| variable | `listing` | 528 | -| variable | `record` | 540 | -| variable | `reads` | 543 | -| variable | `writes` | 543 | -| function | `transport(endpoint, body)` | 545 | -| parameter | `body` | 545 | -| parameter | `endpoint` | 545 | -| parameter | `tag` | 565 | -| function | `test_untrusted_context_never_reaches_github(tmp_path, monkeypatch, field, value)` | 586 | -| parameter | `field` | 586 | -| parameter | `monkeypatch` | 586 | -| parameter | `tmp_path` | 586 | -| parameter | `value` | 586 | -| parameter | `args` | 590 | -| parameter | `kwargs` | 590 | -| function | `test_final_verification_rechecks_security_but_keeps_release_draft(tmp_path, monkeypatch, capsys)` | 595 | -| parameter | `capsys` | 595 | -| parameter | `monkeypatch` | 595 | -| parameter | `tmp_path` | 595 | +| 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 | `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 | `system` | 601 | -| variable | `name` | 602 | -| variable | `responses` | 612 | -| parameter | `tag` | 615 | -| variable | `result` | 617 | -| function | `test_dirty_source_and_publish_operation_are_refused_before_network(tmp_path, monkeypatch)` | 623 | -| parameter | `monkeypatch` | 623 | -| parameter | `tmp_path` | 623 | -| parameter | `args` | 627 | -| parameter | `kwargs` | 627 | -| variable | `error` | 631 | -| function | `test_actual_checked_in_product_version_and_worker_metadata()` | 636 | -| function | `test_api_structured_body_and_fixed_namespace(monkeypatch)` | 641 | -| parameter | `monkeypatch` | 641 | -| variable | `calls` | 643 | -| function | `execute(arguments, data)` | 645 | -| parameter | `arguments` | 645 | -| parameter | `data` | 645 | -| variable | `body` | 651 | +| 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 | ## tests/test_sanitizer_probe.py diff --git a/docs/codeql.md b/docs/codeql.md index 215670f..03eb0c4 100644 --- a/docs/codeql.md +++ b/docs/codeql.md @@ -50,7 +50,10 @@ that baseline. We add no internal override, exclusion or fabricated empty result The [full-main fix report](reports/codeql-main-fixes-2026-11-09.md) records the two actual pre-existing findings, descriptor-bound worker reads, independent negative -regressions and the separate 0.4.1 delivery acceptance still required. +regressions and separate delivery acceptance. The preserved 0.4.1 draft's final +metadata visibility failure and fresh 0.4.2 candidate are recorded in the +[CI visibility report](reports/draft-release-visibility-2026-11-10.md); they do not +change the original finding/scan history. Open **Security → Code scanning** or the CodeQL check on the pull request. Confirm the workflow source, language/category, completed extraction and successful result processing. diff --git a/docs/handbook.md b/docs/handbook.md index 82be5e6..27cf5be 100644 --- a/docs/handbook.md +++ b/docs/handbook.md @@ -2,6 +2,13 @@ By Jason Stys. +Current source is the 0.4.2 delivery candidate. Read its [release notes](releases/v0.4.2.md) +and the [observed draft-verification CI correction](reports/draft-release-visibility-2026-11-10.md) +before interpreting release availability. The unchanged Word/Excel editions below +remain explicit historical snapshots of accepted runtime source +`7d2158a57bb49e8f837fffbfe23b301902465412`; they are not regenerated or relabeled as +0.4.2 acceptance. The 0.4.1 draft and its assets remain preserved and unpublished. + Filter diagnostics, unused-code candidates and missed coverage; inspect hash-verified source and navigate safely to unchanged drafts. Fixed rapid typing, worker compatibility and native launch preparation. Turns report findings into a reproducible developer triage workflow while preserving source identity and isolated execution. diff --git a/docs/located-diagnostics.md b/docs/located-diagnostics.md index 4970043..0d5de9b 100644 --- a/docs/located-diagnostics.md +++ b/docs/located-diagnostics.md @@ -7,7 +7,7 @@ Version 0.3 extends the original Python contract with [native coverage and unuse ## Use it 1. Upload the flat source files or edit them in Workspace. Select **Static review · no execution**. The Evidence view lists original filename, one-based line/column, check code and explanation. This works without Docker or a configured AI provider. -2. Approve independent stdin/stdout cases, save the revision and run checks with the updated isolated worker. Evidence adds compiler/runtime references, native unused candidates and **Uncovered source locations** with a language-specific metric. Rebuild the worker after updating source: `docker build -t code-flow-worker:0.4.1 runner`. This command is for an already working, authorized Docker engine; the application never repairs Docker or falls back to executing targets on the host. +2. Approve independent stdin/stdout cases, save the revision and run checks with the updated isolated worker. Evidence adds compiler/runtime references, native unused candidates and **Uncovered source locations** with a language-specific metric. Rebuild the worker after updating source: `docker build -t code-flow-worker:0.4.2 runner`. This command is for an already working, authorized Docker engine; the application never repairs Docker or falls back to executing targets on the host. 3. Download JSON, HTML or ZIP. HTML now includes a readable location table and per-file missing-line lists before raw evidence. JSON and ZIP retain the same structured fields and source/test snapshots. Check a file's `source_sha256` against that snapshot before applying a finding to another version. ## What is checked diff --git a/docs/polyglot-locations.md b/docs/polyglot-locations.md index fec51f2..a750d11 100644 --- a/docs/polyglot-locations.md +++ b/docs/polyglot-locations.md @@ -7,7 +7,7 @@ By Jason Stys. Version 0.3 adds native evidence to every existing execution adap Rebuild the reviewed worker on an **already working, authorized** local Docker engine: ```sh -docker build -t code-flow-worker:0.4.1 runner +docker build -t code-flow-worker:0.4.2 runner ``` The new image tag prevents silently reusing the old Python-only coverage worker. No Docker repair, privileged container, host execution fallback, additional network or host filesystem access is introduced. Installed UI packages include the worker sources and locked tool manifest, not an installed Docker engine or container image. diff --git a/docs/private-accounts.md b/docs/private-accounts.md index 2d4683a..98d854c 100644 --- a/docs/private-accounts.md +++ b/docs/private-accounts.md @@ -42,7 +42,7 @@ Provider keys are input once to the local service, saved only in its encrypted a Review destinations, then opt in to sending project text. Requirements/cases/harness/source/prompt may be sent; binary fixtures are not sent. Cloud providers may store that text or charge fees under their policies. No external calls happen without an explicitly configured provider and consent. Protocol tests use synthetic mocks, not live keys or model-quality claims. Proposals are unverified, must be reviewed, and cannot silently replace approved oracles or claim test evidence. -Compiler jobs require a local Unix/named-pipe engine and the reviewed `code-flow-worker:0.4.1` image. Remote Docker contexts are blocked in account mode; targets get no network, broad host mounts, account database, provider settings or Docker socket. No automatic Docker installation/repair and no direct-host execution fallback exist. Training remains a separate optional, rights-checked ML workflow, not bundled in the end-user runtime. Version 0.3 adds [native coverage/unused-code evidence](polyglot-locations.md); static Python review remains available without Docker. +Compiler jobs require a local Unix/named-pipe engine and the reviewed `code-flow-worker:0.4.2` image. Remote Docker contexts are blocked in account mode; targets get no network, broad host mounts, account database, provider settings or Docker socket. No automatic Docker installation/repair and no direct-host execution fallback exist. Training remains a separate optional, rights-checked ML workflow, not bundled in the end-user runtime. Version 0.3 adds [native coverage/unused-code evidence](polyglot-locations.md); static Python review remains available without Docker. Private readiness is refreshed in the background every thirty seconds, without blocking source saving. The UI polls that cached badge every five seconds; it never renews the human-idle clock. Each actual execution rechecks and pins the engine, so a stale ready badge cannot authorize a host fallback. diff --git a/docs/releases/v0.4.1.md b/docs/releases/v0.4.1.md index 0ee3352..19e0847 100644 --- a/docs/releases/v0.4.1.md +++ b/docs/releases/v0.4.1.md @@ -4,6 +4,12 @@ By Jason Stys. This source prepares a hotfix; a new downloadable release require successful exact-source validation, full-main CodeQL review and three-OS native package acceptance. Published 0.4.0 assets and tags remain unchanged. +Historical delivery outcome: all three native builds passed, but the final +read-only draft metadata check failed because its credential lacked draft +visibility. Draft 440443517 and every 0.4.1 asset remain unchanged and unpublished. +The [fresh 0.4.2 candidate](v0.4.2.md) corrects only that CI permission/policy fault +plus coherent version metadata; it does not overwrite or retarget this draft. + The native account service gives HTTP work five seconds to drain, then cancels and joins its own remaining request tasks before normal vault sealing. Real offloaded disk operations and authorized local account/provider transitions finish diff --git a/docs/releases/v0.4.2.md b/docs/releases/v0.4.2.md new file mode 100644 index 0000000..a308049 --- /dev/null +++ b/docs/releases/v0.4.2.md @@ -0,0 +1,33 @@ +# Code Flow Studio 0.4.2 — draft verification visibility + +By Jason Stys. This is a fresh delivery candidate, not a claim that downloads have +been published. The 0.4.1 draft and all its existing assets are preserved. Published +0.4.0 releases/tags/assets remain unchanged. + +All 0.4.1 native platform builds passed actual compiled application acceptance. +Its final verification job could not read the prepared draft with a read-only +contents credential. GitHub exposes draft metadata to push-capable credentials. +Only that main-only manual job now has `contents: write`, with unchanged read-only +actions/security scopes. Its command still performs metadata reads only and keeps +the release a draft; there is no automatic publication or asset overwrite. + +The policy validator now requires the exact final-job permission set. Real +workflow mutation regressions reject insufficient draft visibility and expanded +unrelated scopes; the actual final-verifier test still permits reads only. +Current exact-main successful validation/native/CodeQL checks, positive processed +rule counts, zero open main alerts, prepared numeric-ID/source binding, serial +uploads, compiled acceptance, checksums and draft preservation remain mandatory. +See the [observed CI fault and evidence](../reports/draft-release-visibility-2026-11-10.md). + +Application algorithms, account/vault/consent schemas and UI workflows retain the +accepted safe-shutdown and bounded worker-report implementation. Product/runtime/ +UI/npm/Python/installer and worker metadata are coherently 0.4.2. Compiler jobs +require the matching reviewed worker: `docker build -t code-flow-worker:0.4.2 runner`. +This version change neither repairs Docker nor executes target programs on the host. +No account-data migration is added. + +Fresh exact-source hosted gates, three-OS native acceptance and independent +downloaded Windows installer acceptance must pass before owner publication. +Packages remain unsigned; OS security protections and the trusted local-service/ +device boundary still apply. Existing Word/Excel editions retain their explicit +7d historical runtime baseline; they are not relabeled as current release proof. diff --git a/docs/reports/draft-release-visibility-2026-11-10.md b/docs/reports/draft-release-visibility-2026-11-10.md new file mode 100644 index 0000000..b3d9ac3 --- /dev/null +++ b/docs/reports/draft-release-visibility-2026-11-10.md @@ -0,0 +1,77 @@ +# Draft release visibility correction — 2026-11-10 + +By Jason Stys. + +## Actual observed failure + +Release run [42830936957](https://github.com/JasonStys/code-flow-studio/actions/runs/42830936957) +used immutable source `5d6e594605b04f37429d5e1cd96ad4e98fe43c3b`. +Windows, Linux and macOS builds all passed actual compiled native acceptance and +uploaded their accepted payloads/checksums. The final +[verify-draft job 129887436962](https://github.com/JasonStys/code-flow-studio/actions/runs/42830936957/job/129887436962) +failed while reading prepared numeric draft ID 440443517, before completing final +draft/asset verification. Its metadata command returned nonzero and failed closed; +the bounded diagnostic does not establish a specific HTTP status. + +An independent authenticated owner read found the draft present, unpublished, +non-prerelease, bound to that exact source, with all twelve assets uploaded and +provider SHA-256 digests. The successful prepare/build jobs had contents-write +credentials; the failed final job had only contents-read visibility. This is a +release-workflow permission fault, not a demonstrated application/native failure. + +## Provider requirement and narrow correction + +[GitHub's official release API documentation](https://docs.github.com/en/rest/releases/releases#list-releases) +states that draft release listings require push access. The final main-only manual +verification job now receives `contents: write` solely for draft metadata visibility. +Its `actions: read` and `security-events: read` scopes remain unchanged. The actual +`verify` command still makes reads only, validates the prepared numeric ID/source, +rechecks current-main validation and processed CodeQL/open-alert evidence, and +preserves the draft. It cannot publish or overwrite an asset. + +The actual workflow policy now requires that exact final-job permission set. +Mutation tests alter only the real final job's scopes to reject contents-read/none +visibility and expanded unrelated write scopes. Commented mappings, duplicate keys, +unparsed expressions and duplicate whole permission blocks are rejected; legitimate +inline explanations retain their parsed scope. This is a deliberately narrow +reviewed workflow-format contract, not a general YAML parser. The existing integrated final CLI +test still supplies only allowed metadata reads and proves the release remains a +draft. Immutable main checkouts, no privileged PR trigger, serial uploads and all +existing source/security/native/payload gates are retained. + +## Fresh version and preserved history + +Version 0.4.2 is a fresh coherent product/runtime/UI/Python/npm/worker/installer +identity. Application algorithms, account/vault/consent schemas and UI workflows +retain the previously accepted implementation; only metadata/display/required +matching worker-image identity changes accompany the CI correction. No data +migration, Docker repair or host-execution fallback is added. + +The failed 0.4.1 draft and its assets/tags are not deleted, overwritten or retargeted. +The 0.4.0 published release also remains unchanged. Historical source/native/CodeQL +reports keep their original source and execution scope. Approved Word/Excel bytes +remain snapshots of runtime baseline `7d2158a57bb49e8f837fffbfe23b301902465412`. + +## Validation boundary + +Local validation passed 286 tests and 28 subtests in 48.77 seconds, measuring +1,539/1,808 controller/data statements (85.12%, unchanged 80% floor). This includes +the actual workflow mutants and coherent product/worker metadata checks. Ruff, +87 exact source maps, strict TypeScript/Vite build and unchanged publication-byte/ +source/link validation passed. All four npm metadata files were independently +compared with the prior source: only product/root versions changed, with no +dependency-graph update. Existing cache/chunk warnings remain visible. + +A freshly frozen and compiled local Windows 0.4.2 application passed its real +account/editor/privacy/lock journey in 6.3 seconds, including matching product/ +health version, fixed worker bytes, actual HTTP drain/sealing and disappearance +of both owned service/bootloader PIDs. This was an unsigned dirty development +build, not a published download or clean-machine installer claim. Existing +0.4.1 draft assets and all approved Word/Excel bytes remain unchanged. + +Passing local +fixtures cannot prove GitHub token visibility; the fresh authorized 0.4.2 release +workflow must demonstrate that provider boundary. Full current-main CodeQL, +three-OS native acceptance and independent downloaded installer checks remain +separate required gates before owner publication. No automatic rerun or bypass +turns the failed historical run into a pass. diff --git a/docs/setup.md b/docs/setup.md index 36ae922..77aeee1 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -8,7 +8,7 @@ Windows: launch the single `Code Flow Studio Setup … .exe` artifact. It instal Linux uses an AppImage; macOS uses a DMG. Native packages are architecture/OS specific. Mobile devices use the responsive browser interface; native Android/iOS packaging is not implemented. -Execution prerequisites are separate from UI installation: Docker must be running, and the trusted `code-flow-worker:0.4.1` image must exist. Build the image from the reviewed `runner` directory. This can download compilers and libraries during trusted image installation; target jobs have no network. If unavailable, the UI remains usable but execution is explicitly blocked. The new tag is required for [polyglot coverage and unused checks](polyglot-locations.md). +Execution prerequisites are separate from UI installation: Docker must be running, and the trusted `code-flow-worker:0.4.2` image must exist. Build the image from the reviewed `runner` directory. This can download compilers and libraries during trusted image installation; target jobs have no network. If unavailable, the UI remains usable but execution is explicitly blocked. The new tag is required for [polyglot coverage and unused checks](polyglot-locations.md). ## Source developers @@ -20,7 +20,7 @@ python -m venv .venv pip install -e '.[dev]' npm ci npm run build -docker build -t code-flow-worker:0.4.1 runner +docker build -t code-flow-worker:0.4.2 runner python -m studio.cli serve --port 8012 ``` diff --git a/package-lock.json b/package-lock.json index 95ad9fd..e5159bb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "code-flow-studio", - "version": "0.4.1", + "version": "0.4.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "code-flow-studio", - "version": "0.4.1", + "version": "0.4.2", "license": "MIT", "dependencies": { "@monaco-editor/react": "4.7.0", diff --git a/package.json b/package.json index f2677af..88b9350 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "code-flow-studio", - "version": "0.4.1", + "version": "0.4.2", "description": "Isolated polyglot testing and evidence-first AI development studio", "author": "Jason Stys", "license": "MIT", diff --git a/pyproject.toml b/pyproject.toml index ffc53cb..1fad231 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "code-flow-studio" -version = "0.4.1" +version = "0.4.2" description = "Evidence-first, isolated polyglot testing and AI development workbench" requires-python = ">=3.12" dependencies = ["fastapi==0.142.1", "uvicorn==0.38.0", "httpx==0.28.1", "pydantic==2.13.2", "cryptography==50.0.2", "psutil==7.2.2", "certifi==2026.10.19"] diff --git a/runner/tools/package-lock.json b/runner/tools/package-lock.json index f2388f1..fa9933b 100644 --- a/runner/tools/package-lock.json +++ b/runner/tools/package-lock.json @@ -1,12 +1,12 @@ { "name": "code-flow-worker-analyzers", - "version": "0.4.1", + "version": "0.4.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "code-flow-worker-analyzers", - "version": "0.4.1", + "version": "0.4.2", "license": "MIT", "dependencies": { "@typescript-eslint/eslint-plugin": "8.72.0", diff --git a/runner/tools/package.json b/runner/tools/package.json index a869fc9..6c7fd72 100644 --- a/runner/tools/package.json +++ b/runner/tools/package.json @@ -1,6 +1,6 @@ { "name": "code-flow-worker-analyzers", - "version": "0.4.1", + "version": "0.4.2", "private": true, "description": "Fixed, offline worker analyzers; never load target configuration or plugins", "license": "MIT", diff --git a/scripts/release_gate.py b/scripts/release_gate.py index 5e73c97..b53aed3 100644 --- a/scripts/release_gate.py +++ b/scripts/release_gate.py @@ -1,9 +1,9 @@ # # Generated definition map; @L is the exact declaration line. # Function/class docs explain behavior; variables/parameters are scoped inputs, outputs or state. -# functions: strict_object(source)@L43; pairs(items)@L46; invalid_constant(value)@L52; valid_metadata(value)@L61; command(arguments, data, missing)@L88; api(endpoint, body)@L121; no_tag(tag)@L130; product_version()@L142; metadata()@L171; workflow_policy(text)@L185; context()@L217; complete_list(endpoint)@L233; require_source(value)@L245; draft(identifier, value)@L297; names(version, system)@L311; attached(record, version, complete)@L322; archive_metadata(file, system)@L352; upload_bytes(identifier, file, name, expected_digest)@L399; upload(value, identifier)@L451; main()@L509 -# variables: ROOT@L35; REPOSITORY@L36; BASE@L37; WORKFLOWS@L38; CATEGORIES@L39; MAX_ASSET@L40; result@L48; value@L56; executable@L101; result@L104; arguments@L124; arguments@L126; version@L144; name@L150; value@L156; assignments@L160; runtime@L161; node@L163; t@L165; source@L173; operation@L196; value@L219; rows@L235; page@L236; current@L237; source@L247; workflow@L249; envelope@L250; rows@L251; row@L256; row@L266; analyses@L269; current@L270; row@L270; row@L272; row@L275; record@L299; values@L313; name@L319; payload@L319; expected@L324; name@L325; system@L325; result@L327; identifiers@L328; row@L329; name@L330; prefix@L358; expected@L359; found@L360; archive@L362; member@L365; member@L368; archive@L379; expanded@L380; index@L381; member@L381; expanded@L383; name@L385; stream@L391; value@L394; token@L401; connection@L403; started@L404; stream@L406; size@L407; key@L412; value@L412; remaining@L422; uploaded_digest@L423; block@L426; remaining@L430; response@L435; content@L437; result@L439; system@L453; name@L454; paths@L454; receipt@L455; archive@L469; archive_value@L470; worker@L470; payload@L475; sidecar@L475; stream@L484; digest@L485; existing@L490; file@L492; file@L495; current@L496; name@L497; stream@L499; digest@L500; observed@L502; parser@L511; operation@L513; target@L515; value@L519; tag@L522; created@L527; record@L538; output@L540; raw@L543; identifier@L545; record@L550 -# parameters: source@L43; items@L46; value@L52; value@L61; arguments@L88; data@L88; missing@L88; body@L121; endpoint@L121; tag@L130; text@L185; endpoint@L233; value@L245; identifier@L297; value@L297; system@L311; version@L311; complete@L322; record@L322; version@L322; file@L352; system@L352; expected_digest@L399; file@L399; identifier@L399; name@L399; identifier@L451; value@L451 +# functions: strict_object(source)@L43; pairs(items)@L46; invalid_constant(value)@L52; valid_metadata(value)@L61; command(arguments, data, missing)@L88; api(endpoint, body)@L121; no_tag(tag)@L130; product_version()@L142; metadata()@L171; workflow_policy(text)@L185; context()@L244; complete_list(endpoint)@L260; require_source(value)@L272; draft(identifier, value)@L324; names(version, system)@L338; attached(record, version, complete)@L349; archive_metadata(file, system)@L379; upload_bytes(identifier, file, name, expected_digest)@L426; upload(value, identifier)@L478; main()@L536 +# variables: ROOT@L35; REPOSITORY@L36; BASE@L37; WORKFLOWS@L38; CATEGORIES@L39; MAX_ASSET@L40; result@L48; value@L56; executable@L101; result@L104; arguments@L124; arguments@L126; version@L144; name@L150; value@L156; assignments@L160; runtime@L161; node@L163; t@L165; source@L173; operation@L196; verifier@L209; fields@L210; line@L211; field@L214; permissions@L221; scopes@L222; line@L224; scope@L227; _@L232; name@L232; value@L246; rows@L262; page@L263; current@L264; source@L274; workflow@L276; envelope@L277; rows@L278; row@L283; row@L293; analyses@L296; current@L297; row@L297; row@L299; row@L302; record@L326; values@L340; name@L346; payload@L346; expected@L351; name@L352; system@L352; result@L354; identifiers@L355; row@L356; name@L357; prefix@L385; expected@L386; found@L387; archive@L389; member@L392; member@L395; archive@L406; expanded@L407; index@L408; member@L408; expanded@L410; name@L412; stream@L418; value@L421; token@L428; connection@L430; started@L431; stream@L433; size@L434; key@L439; value@L439; remaining@L449; uploaded_digest@L450; block@L453; remaining@L457; response@L462; content@L464; result@L466; system@L480; name@L481; paths@L481; receipt@L482; archive@L496; archive_value@L497; worker@L497; payload@L502; sidecar@L502; stream@L511; digest@L512; existing@L517; file@L519; file@L522; current@L523; name@L524; stream@L526; digest@L527; observed@L529; parser@L538; operation@L540; target@L542; value@L546; tag@L549; created@L554; record@L565; output@L567; raw@L570; identifier@L572; record@L577 +# parameters: source@L43; items@L46; value@L52; value@L61; arguments@L88; data@L88; missing@L88; body@L121; endpoint@L121; tag@L130; text@L185; endpoint@L260; value@L272; identifier@L324; value@L324; system@L338; version@L338; complete@L349; record@L349; version@L349; file@L379; system@L379; expected_digest@L426; file@L426; identifier@L426; name@L426; identifier@L478; value@L478 # """Jason Stys: exact-main CodeQL-gated, numeric-ID-bound draft preparation and uploads. @@ -206,6 +206,33 @@ def workflow_policy(text: str) -> None: "Build source/prepared release binding missing", ) require("max-parallel: 1" in text, "Release build/uploads must be serialized") + verifier = text.split("\n verify-draft:", 1)[1] + fields = [] + for line in verifier.splitlines(): + if not re.match(r"^ [^ \t#]", line): + continue + field = re.match(r"^ ([a-z][a-z_-]*):(?:[ \t]|$)", line) + require(field is not None, "Unparsed final verification job field") + fields.append(field[1]) + require( + fields == ["if", "needs", "runs-on", "timeout-minutes", "permissions", "steps"], + "Duplicate or unknown final verification job field", + ) + permissions = re.search(r"(?ms)^ permissions:\n(.*?)(?=^ [a-z][a-z_-]*:|\Z)", verifier) + scopes = [] + require(permissions is not None, "Explicit draft verification permissions required") + for line in permissions[1].splitlines(): + if not line.strip() or line.lstrip().startswith("#"): + continue + scope = re.fullmatch(r" ([a-z-]+): (read|write|none)(?:[ \t]+#[^\r\n]*)?[ \t]*", line) + require(scope is not None, "Unparsed draft verification permission line") + scopes.append(scope.groups()) + require( + len(scopes) == 3 + and len({name for name, _ in scopes}) == len(scopes) + and dict(scopes) == {"contents": "write", "actions": "read", "security-events": "read"}, + "Draft verification needs push-capable contents visibility and only read actions/security scopes", + ) require( "STUDIO_INSTALLED_APP" in text and "npm run test:desktop" in text diff --git a/studio/__init__.py b/studio/__init__.py index 5468893..22a39b6 100644 --- a/studio/__init__.py +++ b/studio/__init__.py @@ -6,4 +6,4 @@ """Code Flow Studio orchestration package. Symbol locations: docs/code-index.md.""" -__version__ = "0.4.1" +__version__ = "0.4.2" diff --git a/studio/execution.py b/studio/execution.py index b0c442d..341d299 100644 --- a/studio/execution.py +++ b/studio/execution.py @@ -25,7 +25,7 @@ from studio.locations import tool_locations from studio.models import Project -IMAGE = "code-flow-worker:0.4.1" +IMAGE = "code-flow-worker:0.4.2" DOCKER = os.environ.get("STUDIO_DOCKER", shutil.which("docker") or "docker") diff --git a/tests/desktop.spec.ts b/tests/desktop.spec.ts index bf882a9..24de658 100644 --- a/tests/desktop.spec.ts +++ b/tests/desktop.spec.ts @@ -103,7 +103,7 @@ test("bundled desktop authenticates, seals history and provides a sandboxed edit ); expect(metadata.schema).toBe(1); expect(metadata.product).toBe("code-flow-studio"); - expect(metadata.version).toBe("0.4.1"); + expect(metadata.version).toBe("0.4.2"); expect(metadata.source).toMatch(/^[a-f0-9]{40}$/); expect(typeof metadata.source_dirty).toBe("boolean"); expect(metadata.worker_sha256).toBe( @@ -150,7 +150,7 @@ test("bundled desktop authenticates, seals history and provides a sandboxed edit if (browserErrors.length < 8) browserErrors.push(error.name); }); const health = await page.request.get(new URL("/health", page.url()).href); - expect((await health.json()).version).toBe("0.4.1"); + expect((await health.json()).version).toBe("0.4.2"); try { console.info("NATIVE_JOURNEY_STAGE", "account:start"); await createSession(page, "native_fictional"); @@ -332,7 +332,7 @@ test("bundled desktop authenticates, seals history and provides a sandboxed edit "artifacts/native-resource-acceptance.json", JSON.stringify({ schema: 1, - version: "0.4.1", + version: "0.4.2", source: acceptedPackage.source, worker_sha256: acceptedPackage.worker_sha256, compiled_app: true, diff --git a/tests/test_release.py b/tests/test_release.py index aa5ca64..f9925ec 100644 --- a/tests/test_release.py +++ b/tests/test_release.py @@ -23,10 +23,10 @@ @pytest.mark.parametrize( "name", [ - "Code Flow Studio Setup 0.4.1.exe", - "Code Flow Studio-0.4.1-arm64.dmg", - "Code Flow Studio-0.4.1.AppImage", - "CodeFlowStudio-0.4.1-windows-x64.zip", + "Code Flow Studio Setup 0.4.2.exe", + "Code Flow Studio-0.4.2-arm64.dmg", + "Code Flow Studio-0.4.2.AppImage", + "CodeFlowStudio-0.4.2-windows-x64.zip", ], ) def test_checksum_download_filename(tmp_path, name): diff --git a/tests/test_release_gate.py b/tests/test_release_gate.py index 1f20fae..73ef792 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@L412 -# 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_package_schema_is_strict(value)@L245; test_metadata_duplicates_nonfinite_and_nonobjects_are_errors(source)@L253; portable(file, value, worker, system, symlink)@L259; test_actual_platform_archive_metadata(tmp_path, system, suffix)@L283; test_archive_metadata_link_is_never_followed(tmp_path)@L290; staged(tmp_path, monkeypatch)@L298; test_invalid_local_or_remote_package_cannot_reach_upload(tmp_path, monkeypatch, defect)@L339; test_exact_staged_files_upload_to_prepared_id_without_overwrite(tmp_path, monkeypatch)@L370; send(identifier, file, name, digest)@L375; test_fixed_https_transport_id_digest_and_redirect_refusal(tmp_path, monkeypatch, status, digest_ok)@L405; __init__(self, host, timeout)@L415; putrequest(self, method, url)@L418; putheader(self, key, value)@L421; endheaders(self)@L425; send(self, block)@L428; getresponse(self)@L431; close(self)@L447; test_cli_command_allowlist_precedes_execution(monkeypatch)@L462; test_tag_absence_distinguishes_real404_from_permission_errors(monkeypatch)@L477; prepared_context(tmp_path, monkeypatch)@L500; test_actual_prepare_orders_all_checks_before_the_only_write(tmp_path, monkeypatch, defect)@L524; transport(endpoint, body)@L545; test_untrusted_context_never_reaches_github(tmp_path, monkeypatch, field, value)@L586; test_final_verification_rechecks_security_but_keeps_release_draft(tmp_path, monkeypatch, capsys)@L595; test_dirty_source_and_publish_operation_are_refused_before_network(tmp_path, monkeypatch)@L623; test_actual_checked_in_product_version_and_worker_metadata()@L636; test_api_structured_body_and_fixed_namespace(monkeypatch)@L641; execute(arguments, data)@L645 -# 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; bad@L247; prefix@L261; members@L262; archive@L267; content@L268; name@L268; archive@L271; content@L272; name@L272; entry@L273; file@L285; file@L292; directory@L302; installer@L304; archive@L306; file@L308; digest@L309; record@L324; changes@L341; directory@L348; record@L348; _@L372; record@L372; writes@L373; row@L378; row@L380; file@L407; digest@L409; events@L410; key@L459; value@L459; arguments@L467; accepted@L480; code@L480; error@L480; output@L480; workflow@L502; key@L509; value@L509; responses@L527; listing@L528; record@L540; reads@L543; writes@L543; record@L600; system@L601; name@L602; responses@L612; result@L617; error@L631; calls@L643; body@L651 -# 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; value@L245; source@L253; file@L259; symlink@L259; system@L259; value@L259; worker@L259; suffix@L283; system@L283; tmp_path@L283; tmp_path@L290; metadata_changes@L298; monkeypatch@L298; tmp_path@L298; tag@L325; endpoint@L329; defect@L339; monkeypatch@L339; tmp_path@L339; args@L364; monkeypatch@L370; tmp_path@L370; digest@L375; file@L375; identifier@L375; name@L375; digest_ok@L405; monkeypatch@L405; status@L405; tmp_path@L405; host@L415; self@L415; timeout@L415; method@L418; self@L418; url@L418; key@L421; self@L421; value@L421; self@L425; block@L428; self@L428; self@L431; limit@L437; self@L437; self@L447; monkeypatch@L462; args@L465; kwargs@L465; monkeypatch@L477; name@L479; args@L489; code@L489; error@L489; kwargs@L489; output@L489; monkeypatch@L500; tmp_path@L500; defect@L524; monkeypatch@L524; tmp_path@L524; body@L545; endpoint@L545; tag@L565; field@L586; monkeypatch@L586; tmp_path@L586; value@L586; args@L590; kwargs@L590; capsys@L595; monkeypatch@L595; tmp_path@L595; tag@L615; monkeypatch@L623; tmp_path@L623; args@L627; kwargs@L627; monkeypatch@L641; arguments@L645; data@L645 +# 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 # """Jason Stys: independent real-shape main/security/draft fixtures and actual archive/transport oracles. @@ -31,7 +31,7 @@ VALUE = { "schema": 1, "product": "code-flow-studio", - "version": "0.4.1", + "version": "0.4.2", "source": SOURCE, "source_dirty": False, "worker_sha256": hashlib.sha256(b"fixed worker fixture").hexdigest(), @@ -94,7 +94,7 @@ def release_record(**changes): prefix = gate.BASE + "/releases/123" return { "id": 123, - "tag_name": "v0.4.1", + "tag_name": "v0.4.2", "target_commitish": SOURCE, "draft": True, "prerelease": False, @@ -241,6 +241,66 @@ def test_actual_workflow_policy_rejects_security_mutants(before, after): gate.workflow_policy(changed) +@pytest.mark.parametrize("scope,before,after", [ + ("contents", "write", "read"), + ("contents", "write", "none"), + ("actions", "read", "write"), + ("security-events", "read", "write"), +]) +def test_real_final_job_permission_mutations_cannot_authorize_draft(scope, before, after): + """The actual final job needs draft visibility; unrelated API privileges cannot silently expand.""" + original = (gate.ROOT / ".github/workflows/release-native.yml").read_text(encoding="utf-8") + gate.workflow_policy(original) + prefix, separator, verifier = original.partition("\n verify-draft:") + assert separator and verifier.count(f" {scope}: {before}") == 1 + changed = prefix + separator + verifier.replace(f" {scope}: {before}", f" {scope}: {after}", 1) + with pytest.raises(ValueError): + gate.workflow_policy(changed) + + +@pytest.mark.parametrize("extra", [ + " packages: write # extra privilege\n", + " contents: read # later actual override\n", + " packages: ${{ github.token }}\n", +]) +def test_commented_duplicate_or_unparsed_final_permissions_fail_closed(extra): + """Extra scopes cannot hide in comments or duplicate keys that GitHub could interpret differently.""" + original = (gate.ROOT / ".github/workflows/release-native.yml").read_text(encoding="utf-8") + prefix, separator, verifier = original.partition("\n verify-draft:") + assert separator and verifier.count(" steps:\n") == 1 + changed = prefix + separator + verifier.replace(" steps:\n", extra + " steps:\n", 1) + with pytest.raises(ValueError): + gate.workflow_policy(changed) + + +def test_legitimate_final_scope_comments_are_parsed_not_ignored(): + """An inline explanation preserves its permission value, with no ignored mapping lines.""" + original = (gate.ROOT / ".github/workflows/release-native.yml").read_text(encoding="utf-8") + prefix, separator, verifier = original.partition("\n verify-draft:") + assert separator + changed = prefix + separator + verifier.replace(" contents: write", " contents: write # draft visibility", 1) + gate.workflow_policy(changed) + + +@pytest.mark.parametrize("position,field", [ + ("before-steps", "permissions"), + ("after-steps", "permissions"), + ("after-steps", '"permissions"'), +]) +def test_duplicate_whole_final_permission_blocks_fail_closed(position, field): + """A later YAML mapping must not override the already validated final-job permissions.""" + original = (gate.ROOT / ".github/workflows/release-native.yml").read_text(encoding="utf-8") + prefix, separator, verifier = original.partition("\n verify-draft:") + assert separator + extra = f" {field}:\n contents: read\n" + if position == "before-steps": + changed = prefix + separator + verifier.replace(" steps:\n", extra + " steps:\n", 1) + else: + changed = original + extra + with pytest.raises(ValueError): + gate.workflow_policy(changed) + + @pytest.mark.parametrize("value", [True, "1", 2]) def test_package_schema_is_strict(value): """Python boolean/integer equality must not authorize malformed package provenance.""" @@ -301,9 +361,9 @@ def staged(tmp_path, monkeypatch, **metadata_changes): monkeypatch.setenv("RUNNER_OS", "Windows") directory = tmp_path / "release/installers" directory.mkdir(parents=True) - installer = directory / "Code Flow Studio Setup 0.4.1.exe" + installer = directory / "Code Flow Studio Setup 0.4.2.exe" installer.write_bytes(b"inert fixture, never executable") - archive = directory / "CodeFlowStudio-0.4.1-windows-x64.zip" + archive = directory / "CodeFlowStudio-0.4.2-windows-x64.zip" portable(archive, {**VALUE, **metadata_changes}) for file in (installer, archive): digest = hashlib.sha256(file.read_bytes()).hexdigest() @@ -313,7 +373,7 @@ def staged(tmp_path, monkeypatch, **metadata_changes): json.dumps( { "schema": 1, - "version": "0.4.1", + "version": "0.4.2", "source": SOURCE, "worker_sha256": VALUE["worker_sha256"], "compiled_app": True, @@ -347,14 +407,14 @@ def test_invalid_local_or_remote_package_cannot_reach_upload(tmp_path, monkeypat ) record, directory = staged(tmp_path, monkeypatch, **changes) if defect == "bad-sidecar": - (directory / "Code Flow Studio Setup 0.4.1.exe.sha256").write_text("wrong") + (directory / "Code Flow Studio Setup 0.4.2.exe.sha256").write_text("wrong") elif defect == "missing-receipt": (tmp_path / "artifacts/native-resource-acceptance.json").unlink() elif defect == "colliding-asset": record["assets"] = [ { "id": 77, - "name": "Code.Flow.Studio.Setup.0.4.1.exe", + "name": "Code.Flow.Studio.Setup.0.4.2.exe", "size": 12, "state": "uploaded", "digest": "sha256:" + "c" * 64, @@ -391,10 +451,10 @@ def send(identifier, file, name, digest): monkeypatch.setattr(gate, "upload_bytes", send) gate.upload(VALUE, 123) assert set(writes) == { - "Code.Flow.Studio.Setup.0.4.1.exe", - "Code.Flow.Studio.Setup.0.4.1.exe.sha256", - "CodeFlowStudio-0.4.1-windows-x64.zip", - "CodeFlowStudio-0.4.1-windows-x64.zip.sha256", + "Code.Flow.Studio.Setup.0.4.2.exe", + "Code.Flow.Studio.Setup.0.4.2.exe.sha256", + "CodeFlowStudio-0.4.2-windows-x64.zip", + "CodeFlowStudio-0.4.2-windows-x64.zip.sha256", } with pytest.raises(ValueError, match="collision"): gate.upload(VALUE, 123) @@ -491,10 +551,10 @@ def test_tag_absence_distinguishes_real404_from_permission_errors(monkeypatch): ), ) if accepted: - gate.no_tag("v0.4.1") + gate.no_tag("v0.4.2") else: with pytest.raises((ValueError, RuntimeError)): - gate.no_tag("v0.4.1") + gate.no_tag("v0.4.2") def prepared_context(tmp_path, monkeypatch): @@ -503,7 +563,7 @@ def prepared_context(tmp_path, monkeypatch): (tmp_path / ".github/workflows").mkdir(parents=True) (tmp_path / ".github/workflows/release-native.yml").write_text(workflow) (tmp_path / "docs/releases").mkdir(parents=True) - (tmp_path / "docs/releases/v0.4.1.md").write_text("Owned synthetic release notes") + (tmp_path / "docs/releases/v0.4.2.md").write_text("Owned synthetic release notes") monkeypatch.setattr(gate, "ROOT", tmp_path) monkeypatch.setattr(gate, "metadata", lambda: copy.deepcopy(VALUE)) for key, value in { @@ -547,11 +607,11 @@ def transport(endpoint, *, body=None): if body is not None: assert endpoint == gate.BASE + "/releases" assert body == { - "tag_name": "v0.4.1", + "tag_name": "v0.4.2", "target_commitish": SOURCE, "draft": True, "prerelease": False, - "name": "Code Flow Studio 0.4.1", + "name": "Code Flow Studio 0.4.2", "body": "Owned synthetic release notes", } assert listing in reads and len(reads) >= 6 @@ -566,7 +626,7 @@ def transport(endpoint, *, body=None): if defect == "none": gate.main() assert len(writes) == 1 - assert (tmp_path / "outputs").read_text() == f"sha={SOURCE}\ntag=v0.4.1\nrelease_id=123\n" + assert (tmp_path / "outputs").read_text() == f"sha={SOURCE}\ntag=v0.4.2\nrelease_id=123\n" else: with pytest.raises(ValueError): gate.main() @@ -599,7 +659,7 @@ def test_final_verification_rechecks_security_but_keeps_release_draft(tmp_path, monkeypatch.setenv("EXPECTED_RELEASE_ID", "123") record = release_record() for system in ("Windows", "Linux", "macOS"): - for name in gate.names("0.4.1", system): + for name in gate.names("0.4.2", system): record["assets"].append( { "id": 100 + len(record["assets"]), @@ -635,7 +695,7 @@ def test_dirty_source_and_publish_operation_are_refused_before_network(tmp_path, def test_actual_checked_in_product_version_and_worker_metadata(): """Exercise the production data-only coherence reader independently of prepared-context fixtures.""" - assert gate.product_version() == "0.4.1" + assert gate.product_version() == "0.4.2" def test_api_structured_body_and_fixed_namespace(monkeypatch): @@ -648,7 +708,7 @@ def execute(arguments, *, data=None): return '{"id":123}' monkeypatch.setattr(gate, "command", execute) - body = {"draft": True, "tag_name": "v0.4.1", "target_commitish": SOURCE} + body = {"draft": True, "tag_name": "v0.4.2", "target_commitish": SOURCE} assert gate.api(gate.BASE + "/releases", body=body) == {"id": 123} assert calls[0][0] == ["gh", "api", gate.BASE + "/releases", "--method", "POST", "--input", "-"] assert json.loads(calls[0][1]) == body diff --git a/ui/App.tsx b/ui/App.tsx index df4c212..2a8ac5b 100644 --- a/ui/App.tsx +++ b/ui/App.tsx @@ -439,7 +439,7 @@ export default function App({
- ENGINEERING WORKBENCH / v0.4.1 + ENGINEERING WORKBENCH / v0.4.2

{tab === "workspace" ? "Make your next iteration count."