diff --git a/goal/cli/governance_cmd.py b/goal/cli/governance_cmd.py index 1b7c659..c35a061 100644 --- a/goal/cli/governance_cmd.py +++ b/goal/cli/governance_cmd.py @@ -17,6 +17,7 @@ from goal.config import load_config from goal.governance.delivery import ( GOVERNANCE_PACKAGE_FILES, + SOURCE_HUB_WORKSPACE_CHECKER, authorize_hook_push, check_delivery_hook, governance_diagnostic_guidance, @@ -390,21 +391,21 @@ def _reject_reserved_validator_options(arguments): def governance_check(target_root, validator_args): """Run the deterministic validator from the adopted governance package.""" target = target_root.resolve() + if is_new_project_source_hub(target): + if validator_args: + raise click.UsageError( + "source-hub health does not accept target-validator arguments" + ) + result = run_source_hub_health(target) + if result.stdout: + click.echo(result.stdout, nl=False) + if result.stderr: + click.echo(result.stderr, err=True, nl=False) + if result.returncode != 0: + raise click.exceptions.Exit(result.returncode) + return missing = missing_governance_package_files(target) if missing: - if is_new_project_source_hub(target): - if validator_args: - raise click.UsageError( - "source-hub health does not accept target-validator arguments" - ) - result = run_source_hub_health(target) - if result.stdout: - click.echo(result.stdout, nl=False) - if result.stderr: - click.echo(result.stderr, err=True, nl=False) - if result.returncode != 0: - raise click.exceptions.Exit(result.returncode) - return raise click.ClickException( "adopted governance package is incomplete; missing: " + ", ".join(missing) @@ -472,7 +473,14 @@ def workspace_check(target_root, workspace_root, allow, output_format): """Run the read-only checker from the adopted governance package.""" target = target_root.resolve() checker = target / WORKSPACE_LIFECYCLE_CHECKER - if not checker.is_file(): + if is_new_project_source_hub(target): + hub_checker = target / SOURCE_HUB_WORKSPACE_CHECKER + if not hub_checker.is_file(): + raise click.ClickException( + f"source-hub workspace lifecycle checker is missing: {SOURCE_HUB_WORKSPACE_CHECKER}" + ) + checker = hub_checker + elif not checker.is_file(): raise click.ClickException( f"adopted workspace lifecycle checker is missing: {WORKSPACE_LIFECYCLE_CHECKER}; " "run `goal governance adopt` with a published source revision" diff --git a/goal/governance/delivery.py b/goal/governance/delivery.py index 5ccbc80..3e0d5cf 100644 --- a/goal/governance/delivery.py +++ b/goal/governance/delivery.py @@ -43,6 +43,11 @@ "governance/manifest.default.json", "scripts/governance_check.py", ) +SOURCE_HUB_MANIFEST = "governance/manifest.hub.json" +SOURCE_HUB_STACK_PROFILES = "governance/stack-profiles.json" +SOURCE_HUB_WORK_CLASSIFICATION = "governance/work-classification.dsl.json" +SOURCE_HUB_WORKSPACE_CHECKER = "scripts/workspace_lifecycle_check.py" +SOURCE_HUB_DIAGNOSTICS = "governance/diagnostics.json" SOURCE_HUB_WORKFLOW = ".github/workflows/ci.yml" SOURCE_HUB_REQUIRED_CHECKS = "scripts/check_required_checks.py" SOURCE_HUB_JSON_DIRECTORY = "governance" @@ -613,6 +618,8 @@ def _safe_runbook(root: Path, raw_path: Any) -> str | None: def governance_diagnostic_guidance(root: Path, output: str) -> list[str]: """Resolve safe canonical v2 guidance for codes emitted by a validator.""" catalog_path = root / GOVERNANCE_DIAGNOSTICS + if not catalog_path.is_file() and is_new_project_source_hub(root): + catalog_path = root / SOURCE_HUB_DIAGNOSTICS try: catalog = json.loads(catalog_path.read_text(encoding="utf-8")) except (OSError, UnicodeDecodeError, json.JSONDecodeError): @@ -647,22 +654,89 @@ def governance_diagnostic_guidance(root: Path, output: str) -> list[str]: return guidance -def _governance_gate(root: Path, *, base: str | None = None) -> None: - missing = missing_governance_package_files(root) +def _source_hub_candidate_gate( + root: Path, *, base: str, head: str | None = None +) -> None: + missing = [ + relative + for relative in ( + SOURCE_HUB_MANIFEST, + SOURCE_HUB_STACK_PROFILES, + "scripts/governance_check.py", + ) + if not (root / relative).is_file() + ] if missing: - if is_new_project_source_hub(root): - result = run_source_hub_health(root) - if result.returncode == 0: - return - detail = ( - "\n".join( - part.strip() - for part in (result.stdout, result.stderr) - if part.strip() - ) - or "source-hub health failed" + raise click.ClickException( + f"{SOURCE_HUB_DIAGNOSTIC}: missing source-hub candidate contract: " + + ", ".join(missing) + ) + resolved_head = head or _git_value("rev-parse", "HEAD", cwd=root) + arguments = [ + sys.executable, + str(root / "scripts" / "governance_check.py"), + "--root", + str(root), + "--manifest", + SOURCE_HUB_MANIFEST, + "--stack-profiles", + SOURCE_HUB_STACK_PROFILES, + ] + if (root / SOURCE_HUB_WORK_CLASSIFICATION).is_file(): + arguments.extend(["--work-classification", SOURCE_HUB_WORK_CLASSIFICATION]) + arguments.extend( + [ + "--base", + base, + "--head", + resolved_head, + "--actor", + "ci", + "--format", + "text", + ] + ) + result = _run(arguments, cwd=root) + if result.returncode != 0: + detail = ( + "\n".join( + part.strip() + for part in (result.stdout, result.stderr) + if part.strip() + ) + or "source-hub candidate governance failed" + ) + from goal.governance.remediation import publish_governance_remediation + + remediation = publish_governance_remediation(root, detail) + if remediation.detail and remediation.proposal_path is not None: + detail += f"\nGoal remediation: {remediation.detail}" + guidance = governance_diagnostic_guidance(root, detail) + if guidance: + detail += "\n" + "\n".join(guidance) + raise click.ClickException(detail) + + +def _governance_gate( + root: Path, *, base: str | None = None, head: str | None = None +) -> None: + if is_new_project_source_hub(root): + if base is not None: + _source_hub_candidate_gate(root, base=base, head=head) + result = run_source_hub_health(root) + if result.returncode == 0: + return + detail = ( + "\n".join( + part.strip() + for part in (result.stdout, result.stderr) + if part.strip() ) - raise click.ClickException(detail) + or "source-hub health failed" + ) + raise click.ClickException(detail) + missing = missing_governance_package_files(root) + if missing: raise click.ClickException( "GOV-MANIFEST-001: governance delivery requires a complete adopted " "package; missing: " @@ -1080,8 +1154,15 @@ def resolve_pull_request_ticket( cwd: Path | None = None, ) -> str | None: """Resolve one governed ticket before pull-request workflow mutations.""" - if policy.mode != "pull-request" or ticket: + if policy.mode != "pull-request": return ticket + if ticket is not None: + cleaned = str(ticket).strip() + if re.fullmatch(r"ticket-[0-9]{3,}", cleaned) is None: + raise click.ClickException( + "pull-request delivery requires canonical `--ticket ticket-NNN`" + ) + return cleaned root = _repository_root(cwd) active: list[str] = [] @@ -1544,6 +1625,10 @@ def deliver_pull_request( ) -> tuple[str, str]: """Push a controlled head branch and create or reuse its pull request.""" root = _repository_root(cwd) + if policy.require_clean_governance and is_new_project_source_hub(root): + validation_base = _pull_request_validation_base(policy, root) + head_sha = _git_value("rev-parse", "HEAD", cwd=root) + _source_hub_candidate_gate(root, base=validation_base, head=head_sha) head = _publication_head(policy, ticket, root) current = _git_value("branch", "--show-current", cwd=root) push_arguments = ["git", "push"] diff --git a/goal/push/core.py b/goal/push/core.py index e4362a1..85948b7 100644 --- a/goal/push/core.py +++ b/goal/push/core.py @@ -554,6 +554,10 @@ def _resume_pending_pull_request( head=head, ) click.echo(click.style(f"Pull request: {pr_url}", fg="green")) + if "_start_time" in ctx_obj: + elapsed = time.time() - ctx_obj["_start_time"] + ctx_obj["_elapsed_time"] = elapsed + click.echo(click.style(f"\n⏱️ Total time: {elapsed:.1f}s", fg="cyan")) def _push_completion_release_tag(delivery: Any, new_version: str) -> bool: @@ -648,6 +652,10 @@ def _run_test_stage_or_exit( os.environ["GOAL_SKIP_COSTS_BADGE"] = goal_skip_costs_badge if test_exit_code != 0 and ctx_obj["yes"]: + if "_start_time" in ctx_obj: + elapsed = time.time() - ctx_obj["_start_time"] + ctx_obj["_elapsed_time"] = elapsed + click.echo(click.style(f"\n⏱️ Total time: {elapsed:.1f}s", fg="cyan")) click.echo( click.style("Aborting workflow because tests failed.", fg="red", bold=True) ) @@ -679,6 +687,10 @@ def execute_push_workflow( ) -> None: """Execute the complete push workflow.""" + start_time = time.time() + ctx_obj["_start_time"] = start_time + ctx_obj["_preflight_time"] = 0.0 + # Keep the invariant at the workflow boundary as well as in the Click # context. This covers library callers and compatibility shims that pass # only ``all_flags`` instead of going through ``_configure_main_context``. @@ -736,13 +748,31 @@ def execute_push_workflow( no_publish or ctx_obj.get("no_publish", False) ): raise click.ClickException("publish-only conflicts with --no-publish") - validate_delivery_ready(delivery) + if delivery.mode == "pull-request" and ticket is not None: + ticket = resolve_pull_request_ticket(delivery, ticket) + preflight_start = time.time() + try: + validate_delivery_ready(delivery) + finally: + ctx_obj["_preflight_time"] = ( + ctx_obj.get("_preflight_time", 0.0) + (time.time() - preflight_start) + ) + ctx_obj["_elapsed_time"] = time.time() - start_time ctx_obj["delivery_mode"] = delivery.mode else: - no_change = validate_legacy_governance( - check_no_change=all_flags - and not any((ticket, force, explicit_force_publish, ctx_obj.get("version"))) - ) + preflight_start = time.time() + try: + no_change = validate_legacy_governance( + check_no_change=all_flags + and not any( + (ticket, force, explicit_force_publish, ctx_obj.get("version")) + ) + ) + finally: + ctx_obj["_preflight_time"] = ( + ctx_obj.get("_preflight_time", 0.0) + (time.time() - preflight_start) + ) + ctx_obj["_elapsed_time"] = time.time() - start_time if no_change is True: if all_flags and _has_committed_unreleased_sources(): click.echo( @@ -755,8 +785,6 @@ def execute_push_workflow( _validate_toml_or_exit(dry_run) - start_time = time.time() - _initialize_context(ctx_obj, bump, message, yes, markdown) yes = ctx_obj["yes"] @@ -772,7 +800,8 @@ def execute_push_workflow( ) if delivery is not None: - ticket = resolve_pull_request_ticket(delivery, ticket) + if ticket is None: + ticket = resolve_pull_request_ticket(delivery, ticket) if delivery.mode == "pull-request" and ticket is None: disagreeing = _managed_version_values() if len(disagreeing) > 1: @@ -896,7 +925,14 @@ def execute_push_workflow( sys.exit(2) if delivery is not None and delivery.mode == "pull-request": - validate_delivery_ready(delivery) + preflight_start = time.time() + try: + validate_delivery_ready(delivery) + finally: + ctx_obj["_preflight_time"] = ( + ctx_obj.get("_preflight_time", 0.0) + (time.time() - preflight_start) + ) + ctx_obj["_elapsed_time"] = time.time() - start_time if not dry_run: run_git("add", "-A") diff --git a/project/TICKETS.md b/project/TICKETS.md index 659262c..763977b 100644 --- a/project/TICKETS.md +++ b/project/TICKETS.md @@ -133,135 +133,10 @@ This file indexes governance tickets without taking ownership of | **ticket-187** | [`README.md`](./ticket-187/README.md) | - | - | - | - | - | | **ticket-188** | [`README.md`](./ticket-188/README.md) | - | - | - | - | - | | **ticket-190** | [`README.md`](./ticket-190/README.md) | - | - | - | - | - | -| **ticket-196** | [`README.md`](./ticket-196/README.md) | - | - | - | - | - | -| **ticket-197** | [`README.md`](./ticket-197/README.md) | - | - | - | - | - | -| **ticket-001** | [`README.md`](./ticket-001/README.md) | [`preprompt.md`](./ticket-001/preprompt.md) | - | [`ai-codex.md`](./ticket-001/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-001/ai-codex-logs.txt) | [`changelog.md`](./ticket-001/changelog.md) | -| **ticket-002** | [`README.md`](./ticket-002/README.md) | [`preprompt.md`](./ticket-002/preprompt.md) | - | [`ai-codex.md`](./ticket-002/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-002/ai-codex-logs.txt) | [`changelog.md`](./ticket-002/changelog.md) | -| **ticket-003** | [`README.md`](./ticket-003/README.md) | [`preprompt.md`](./ticket-003/preprompt.md) | - | [`ai-codex.md`](./ticket-003/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-003/ai-codex-logs.txt) | [`changelog.md`](./ticket-003/changelog.md) | -| **ticket-004** | [`README.md`](./ticket-004/README.md) | [`preprompt.md`](./ticket-004/preprompt.md) | - | [`ai-codex.md`](./ticket-004/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-004/ai-codex-logs.txt) | [`changelog.md`](./ticket-004/changelog.md) | -| **ticket-005** | [`README.md`](./ticket-005/README.md) | [`preprompt.md`](./ticket-005/preprompt.md) | - | [`ai-codex.md`](./ticket-005/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-005/ai-codex-logs.txt) | [`changelog.md`](./ticket-005/changelog.md) | -| **ticket-006** | [`README.md`](./ticket-006/README.md) | [`preprompt.md`](./ticket-006/preprompt.md) | - | [`ai-codex.md`](./ticket-006/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-006/ai-codex-logs.txt) | [`changelog.md`](./ticket-006/changelog.md) | -| **ticket-007** | [`README.md`](./ticket-007/README.md) | [`preprompt.md`](./ticket-007/preprompt.md) | - | [`ai-codex.md`](./ticket-007/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-007/ai-codex-logs.txt) | [`changelog.md`](./ticket-007/changelog.md) | -| **ticket-008** | [`README.md`](./ticket-008/README.md) | [`preprompt.md`](./ticket-008/preprompt.md) | - | [`ai-codex.md`](./ticket-008/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-008/ai-codex-logs.txt) | [`changelog.md`](./ticket-008/changelog.md) | -| **ticket-009** | [`README.md`](./ticket-009/README.md) | [`preprompt.md`](./ticket-009/preprompt.md) | - | [`ai-codex.md`](./ticket-009/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-009/ai-codex-logs.txt) | [`changelog.md`](./ticket-009/changelog.md) | -| **ticket-010** | [`README.md`](./ticket-010/README.md) | [`preprompt.md`](./ticket-010/preprompt.md) | - | [`ai-codex.md`](./ticket-010/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-010/ai-codex-logs.txt) | [`changelog.md`](./ticket-010/changelog.md) | -| **ticket-011** | [`README.md`](./ticket-011/README.md) | [`preprompt.md`](./ticket-011/preprompt.md) | - | [`ai-codex.md`](./ticket-011/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-011/ai-codex-logs.txt) | [`changelog.md`](./ticket-011/changelog.md) | -| **ticket-012** | [`README.md`](./ticket-012/README.md) | [`preprompt.md`](./ticket-012/preprompt.md) | - | [`ai-codex.md`](./ticket-012/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-012/ai-codex-logs.txt) | [`changelog.md`](./ticket-012/changelog.md) | -| **ticket-013** | [`README.md`](./ticket-013/README.md) | [`preprompt.md`](./ticket-013/preprompt.md) | - | [`ai-codex.md`](./ticket-013/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-013/ai-codex-logs.txt) | [`changelog.md`](./ticket-013/changelog.md) | -| **ticket-014** | [`README.md`](./ticket-014/README.md) | [`preprompt.md`](./ticket-014/preprompt.md) | - | [`ai-codex.md`](./ticket-014/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-014/ai-codex-logs.txt) | [`changelog.md`](./ticket-014/changelog.md) | -| **ticket-015** | [`README.md`](./ticket-015/README.md) | [`preprompt.md`](./ticket-015/preprompt.md) | - | [`ai-codex.md`](./ticket-015/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-015/ai-codex-logs.txt) | [`changelog.md`](./ticket-015/changelog.md) | -| **ticket-016** | [`README.md`](./ticket-016/README.md) | [`preprompt.md`](./ticket-016/preprompt.md) | - | [`ai-codex.md`](./ticket-016/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-016/ai-codex-logs.txt) | [`changelog.md`](./ticket-016/changelog.md) | -| **ticket-017** | [`README.md`](./ticket-017/README.md) | [`preprompt.md`](./ticket-017/preprompt.md) | - | [`ai-codex.md`](./ticket-017/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-017/ai-codex-logs.txt) | [`changelog.md`](./ticket-017/changelog.md) | -| **ticket-018** | [`README.md`](./ticket-018/README.md) | [`preprompt.md`](./ticket-018/preprompt.md) | - | [`ai-codex.md`](./ticket-018/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-018/ai-codex-logs.txt) | [`changelog.md`](./ticket-018/changelog.md) | -| **ticket-019** | [`README.md`](./ticket-019/README.md) | [`preprompt.md`](./ticket-019/preprompt.md) | - | [`ai-codex.md`](./ticket-019/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-019/ai-codex-logs.txt) | [`changelog.md`](./ticket-019/changelog.md) | -| **ticket-020** | [`README.md`](./ticket-020/README.md) | [`preprompt.md`](./ticket-020/preprompt.md) | - | [`ai-codex.md`](./ticket-020/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-020/ai-codex-logs.txt) | [`changelog.md`](./ticket-020/changelog.md) | -| **ticket-021** | [`README.md`](./ticket-021/README.md) | [`preprompt.md`](./ticket-021/preprompt.md) | - | [`ai-codex.md`](./ticket-021/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-021/ai-codex-logs.txt) | [`changelog.md`](./ticket-021/changelog.md) | -| **ticket-023** | [`README.md`](./ticket-023/README.md) | [`preprompt.md`](./ticket-023/preprompt.md) | - | [`ai-codex.md`](./ticket-023/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-023/ai-codex-logs.txt) | [`changelog.md`](./ticket-023/changelog.md) | -| **ticket-024** | [`README.md`](./ticket-024/README.md) | [`preprompt.md`](./ticket-024/preprompt.md) | - | [`ai-codex.md`](./ticket-024/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-024/ai-codex-logs.txt) | [`changelog.md`](./ticket-024/changelog.md) | -| **ticket-025** | [`README.md`](./ticket-025/README.md) | [`preprompt.md`](./ticket-025/preprompt.md) | - | [`ai-codex.md`](./ticket-025/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-025/ai-codex-logs.txt) | [`changelog.md`](./ticket-025/changelog.md) | -| **ticket-026** | [`README.md`](./ticket-026/README.md) | [`preprompt.md`](./ticket-026/preprompt.md) | - | [`ai-codex.md`](./ticket-026/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-026/ai-codex-logs.txt) | [`changelog.md`](./ticket-026/changelog.md) | -| **ticket-027** | [`README.md`](./ticket-027/README.md) | [`preprompt.md`](./ticket-027/preprompt.md) | - | [`ai-codex.md`](./ticket-027/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-027/ai-codex-logs.txt) | [`changelog.md`](./ticket-027/changelog.md) | -| **ticket-028** | [`README.md`](./ticket-028/README.md) | [`preprompt.md`](./ticket-028/preprompt.md) | - | [`ai-codex.md`](./ticket-028/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-028/ai-codex-logs.txt) | [`changelog.md`](./ticket-028/changelog.md) | -| **ticket-029** | [`README.md`](./ticket-029/README.md) | [`preprompt.md`](./ticket-029/preprompt.md) | - | [`ai-codex.md`](./ticket-029/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-029/ai-codex-logs.txt) | [`changelog.md`](./ticket-029/changelog.md) | -| **ticket-030** | [`README.md`](./ticket-030/README.md) | [`preprompt.md`](./ticket-030/preprompt.md) | - | [`ai-codex.md`](./ticket-030/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-030/ai-codex-logs.txt) | [`changelog.md`](./ticket-030/changelog.md) | -| **ticket-032** | [`README.md`](./ticket-032/README.md) | [`preprompt.md`](./ticket-032/preprompt.md) | - | [`ai-codex.md`](./ticket-032/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-032/ai-codex-logs.txt) | [`changelog.md`](./ticket-032/changelog.md) | -| **ticket-033** | [`README.md`](./ticket-033/README.md) | [`preprompt.md`](./ticket-033/preprompt.md) | - | [`ai-codex.md`](./ticket-033/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-033/ai-codex-logs.txt) | [`changelog.md`](./ticket-033/changelog.md) | -| **ticket-034** | [`README.md`](./ticket-034/README.md) | [`preprompt.md`](./ticket-034/preprompt.md) | - | [`ai-codex.md`](./ticket-034/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-034/ai-codex-logs.txt) | [`changelog.md`](./ticket-034/changelog.md) | -| **ticket-035** | [`README.md`](./ticket-035/README.md) | [`preprompt.md`](./ticket-035/preprompt.md) | - | [`ai-codex.md`](./ticket-035/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-035/ai-codex-logs.txt) | [`changelog.md`](./ticket-035/changelog.md) | -| **ticket-036** | [`README.md`](./ticket-036/README.md) | [`preprompt.md`](./ticket-036/preprompt.md) | - | [`ai-codex.md`](./ticket-036/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-036/ai-codex-logs.txt) | [`changelog.md`](./ticket-036/changelog.md) | -| **ticket-037** | [`README.md`](./ticket-037/README.md) | [`preprompt.md`](./ticket-037/preprompt.md) | - | [`ai-codex.md`](./ticket-037/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-037/ai-codex-logs.txt) | [`changelog.md`](./ticket-037/changelog.md) | -| **ticket-038** | [`README.md`](./ticket-038/README.md) | [`preprompt.md`](./ticket-038/preprompt.md) | - | [`ai-codex.md`](./ticket-038/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-038/ai-codex-logs.txt) | [`changelog.md`](./ticket-038/changelog.md) | -| **ticket-039** | [`README.md`](./ticket-039/README.md) | [`preprompt.md`](./ticket-039/preprompt.md) | - | [`ai-codex.md`](./ticket-039/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-039/ai-codex-logs.txt) | [`changelog.md`](./ticket-039/changelog.md) | -| **ticket-040** | [`README.md`](./ticket-040/README.md) | [`preprompt.md`](./ticket-040/preprompt.md) | - | [`ai-codex.md`](./ticket-040/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-040/ai-codex-logs.txt) | [`changelog.md`](./ticket-040/changelog.md) | -| **ticket-041** | [`README.md`](./ticket-041/README.md) | [`preprompt.md`](./ticket-041/preprompt.md) | - | [`ai-codex.md`](./ticket-041/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-041/ai-codex-logs.txt) | [`changelog.md`](./ticket-041/changelog.md) | -| **ticket-042** | [`README.md`](./ticket-042/README.md) | [`preprompt.md`](./ticket-042/preprompt.md) | - | [`ai-codex.md`](./ticket-042/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-042/ai-codex-logs.txt) | [`changelog.md`](./ticket-042/changelog.md) | -| **ticket-043** | [`README.md`](./ticket-043/README.md) | [`preprompt.md`](./ticket-043/preprompt.md) | - | [`ai-codex.md`](./ticket-043/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-043/ai-codex-logs.txt) | [`changelog.md`](./ticket-043/changelog.md) | -| **ticket-044** | [`README.md`](./ticket-044/README.md) | [`preprompt.md`](./ticket-044/preprompt.md) | - | [`ai-codex.md`](./ticket-044/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-044/ai-codex-logs.txt) | [`changelog.md`](./ticket-044/changelog.md) | -| **ticket-046** | [`README.md`](./ticket-046/README.md) | [`preprompt.md`](./ticket-046/preprompt.md) | - | [`ai-codex.md`](./ticket-046/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-046/ai-codex-logs.txt) | [`changelog.md`](./ticket-046/changelog.md) | -| **ticket-047** | [`README.md`](./ticket-047/README.md) | [`preprompt.md`](./ticket-047/preprompt.md) | - | [`ai-codex.md`](./ticket-047/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-047/ai-codex-logs.txt) | [`changelog.md`](./ticket-047/changelog.md) | -| **ticket-048** | [`README.md`](./ticket-048/README.md) | [`preprompt.md`](./ticket-048/preprompt.md) | - | [`ai-codex.md`](./ticket-048/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-048/ai-codex-logs.txt) | [`changelog.md`](./ticket-048/changelog.md) | -| **ticket-049** | [`README.md`](./ticket-049/README.md) | [`preprompt.md`](./ticket-049/preprompt.md) | - | [`ai-codex.md`](./ticket-049/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-049/ai-codex-logs.txt) | [`changelog.md`](./ticket-049/changelog.md) | -| **ticket-050** | [`README.md`](./ticket-050/README.md) | [`preprompt.md`](./ticket-050/preprompt.md) | - | [`ai-codex.md`](./ticket-050/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-050/ai-codex-logs.txt) | [`changelog.md`](./ticket-050/changelog.md) | -| **ticket-051** | [`README.md`](./ticket-051/README.md) | [`preprompt.md`](./ticket-051/preprompt.md) | - | [`ai-codex.md`](./ticket-051/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-051/ai-codex-logs.txt) | [`changelog.md`](./ticket-051/changelog.md) | -| **ticket-052** | [`README.md`](./ticket-052/README.md) | [`preprompt.md`](./ticket-052/preprompt.md) | - | [`ai-codex.md`](./ticket-052/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-052/ai-codex-logs.txt) | [`changelog.md`](./ticket-052/changelog.md) | -| **ticket-053** | [`README.md`](./ticket-053/README.md) | [`preprompt.md`](./ticket-053/preprompt.md) | - | [`ai-codex.md`](./ticket-053/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-053/ai-codex-logs.txt) | [`changelog.md`](./ticket-053/changelog.md) | -| **ticket-054** | [`README.md`](./ticket-054/README.md) | [`preprompt.md`](./ticket-054/preprompt.md) | - | [`ai-codex.md`](./ticket-054/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-054/ai-codex-logs.txt) | [`changelog.md`](./ticket-054/changelog.md) | -| **ticket-055** | [`README.md`](./ticket-055/README.md) | [`preprompt.md`](./ticket-055/preprompt.md) | - | [`ai-codex.md`](./ticket-055/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-055/ai-codex-logs.txt) | [`changelog.md`](./ticket-055/changelog.md) | -| **ticket-056** | [`README.md`](./ticket-056/README.md) | [`preprompt.md`](./ticket-056/preprompt.md) | - | [`ai-codex.md`](./ticket-056/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-056/ai-codex-logs.txt) | [`changelog.md`](./ticket-056/changelog.md) | -| **ticket-057** | [`README.md`](./ticket-057/README.md) | [`preprompt.md`](./ticket-057/preprompt.md) | - | [`ai-codex.md`](./ticket-057/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-057/ai-codex-logs.txt) | [`changelog.md`](./ticket-057/changelog.md) | -| **ticket-058** | [`README.md`](./ticket-058/README.md) | [`preprompt.md`](./ticket-058/preprompt.md) | - | [`ai-codex.md`](./ticket-058/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-058/ai-codex-logs.txt) | [`changelog.md`](./ticket-058/changelog.md) | -| **ticket-059** | [`README.md`](./ticket-059/README.md) | [`preprompt.md`](./ticket-059/preprompt.md) | - | [`ai-codex.md`](./ticket-059/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-059/ai-codex-logs.txt) | [`changelog.md`](./ticket-059/changelog.md) | -| **ticket-060** | [`README.md`](./ticket-060/README.md) | [`preprompt.md`](./ticket-060/preprompt.md) | - | [`ai-codex.md`](./ticket-060/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-060/ai-codex-logs.txt) | [`changelog.md`](./ticket-060/changelog.md) | -| **ticket-061** | [`README.md`](./ticket-061/README.md) | [`preprompt.md`](./ticket-061/preprompt.md) | - | [`ai-codex.md`](./ticket-061/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-061/ai-codex-logs.txt) | [`changelog.md`](./ticket-061/changelog.md) | -| **ticket-062** | [`README.md`](./ticket-062/README.md) | [`preprompt.md`](./ticket-062/preprompt.md) | - | [`ai-codex.md`](./ticket-062/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-062/ai-codex-logs.txt) | [`changelog.md`](./ticket-062/changelog.md) | -| **ticket-063** | [`README.md`](./ticket-063/README.md) | [`preprompt.md`](./ticket-063/preprompt.md) | - | [`ai-codex.md`](./ticket-063/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-063/ai-codex-logs.txt) | [`changelog.md`](./ticket-063/changelog.md) | -| **ticket-064** | [`README.md`](./ticket-064/README.md) | [`preprompt.md`](./ticket-064/preprompt.md) | - | [`ai-codex.md`](./ticket-064/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-064/ai-codex-logs.txt) | [`changelog.md`](./ticket-064/changelog.md) | -| **ticket-066** | [`README.md`](./ticket-066/README.md) | [`preprompt.md`](./ticket-066/preprompt.md) | - | [`ai-codex.md`](./ticket-066/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-066/ai-codex-logs.txt) | [`changelog.md`](./ticket-066/changelog.md) | -| **ticket-067** | [`README.md`](./ticket-067/README.md) | [`preprompt.md`](./ticket-067/preprompt.md) | - | [`ai-codex.md`](./ticket-067/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-067/ai-codex-logs.txt) | [`changelog.md`](./ticket-067/changelog.md) | -| **ticket-068** | [`README.md`](./ticket-068/README.md) | [`preprompt.md`](./ticket-068/preprompt.md) | - | [`ai-gpt-5.6-sol.md`](./ticket-068/ai-gpt-5.6-sol.md) | [`ai-gpt-5.6-sol-logs.txt`](./ticket-068/ai-gpt-5.6-sol-logs.txt) | [`changelog.md`](./ticket-068/changelog.md) | -| **ticket-069** | [`README.md`](./ticket-069/README.md) | [`preprompt.md`](./ticket-069/preprompt.md) | - | [`ai-codex.md`](./ticket-069/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-069/ai-codex-logs.txt) | [`changelog.md`](./ticket-069/changelog.md) | -| **ticket-071** | [`README.md`](./ticket-071/README.md) | - | - | [`ai-codex.md`](./ticket-071/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-071/ai-codex-logs.txt) | - | -| **ticket-072** | [`README.md`](./ticket-072/README.md) | - | - | - | - | - | -| **ticket-073** | [`README.md`](./ticket-073/README.md) | - | - | [`ai-codex.md`](./ticket-073/ai-codex.md) | - | - | -| **ticket-074** | [`README.md`](./ticket-074/README.md) | - | - | [`ai-codex.md`](./ticket-074/ai-codex.md) | - | - | -| **ticket-075** | [`README.md`](./ticket-075/README.md) | - | - | - | - | - | -| **ticket-078** | [`README.md`](./ticket-078/README.md) | - | - | - | - | - | -| **ticket-079** | [`README.md`](./ticket-079/README.md) | - | - | - | - | - | -| **ticket-080** | [`README.md`](./ticket-080/README.md) | - | - | - | - | - | -| **ticket-081** | [`README.md`](./ticket-081/README.md) | - | - | - | - | - | -| **ticket-082** | [`README.md`](./ticket-082/README.md) | - | - | - | - | - | -| **ticket-083** | [`README.md`](./ticket-083/README.md) | - | - | [`ai-codex.md`](./ticket-083/ai-codex.md) | - | - | -| **ticket-084** | [`README.md`](./ticket-084/README.md) | - | - | - | - | - | -| **ticket-085** | [`README.md`](./ticket-085/README.md) | - | - | [`ai-codex.md`](./ticket-085/ai-codex.md) | - | - | -| **ticket-086** | [`README.md`](./ticket-086/README.md) | - | - | - | - | - | -| **ticket-087** | [`README.md`](./ticket-087/README.md) | - | - | - | - | - | -| **ticket-088** | [`README.md`](./ticket-088/README.md) | - | - | - | - | - | -| **ticket-089** | [`README.md`](./ticket-089/README.md) | - | - | - | - | - | -| **ticket-090** | [`README.md`](./ticket-090/README.md) | - | - | [`ai-codex.md`](./ticket-090/ai-codex.md) | - | - | -| **ticket-091** | [`README.md`](./ticket-091/README.md) | - | - | - | - | - | -| **ticket-092** | [`README.md`](./ticket-092/README.md) | - | - | - | - | - | -| **ticket-093** | [`README.md`](./ticket-093/README.md) | - | - | - | - | - | -| **ticket-094** | [`README.md`](./ticket-094/README.md) | - | - | - | - | - | -| **ticket-095** | [`README.md`](./ticket-095/README.md) | - | - | - | - | - | -| **ticket-096** | [`README.md`](./ticket-096/README.md) | - | - | - | - | - | -| **ticket-097** | [`README.md`](./ticket-097/README.md) | - | - | - | - | - | -| **ticket-099** | [`README.md`](./ticket-099/README.md) | - | - | - | - | - | -| **ticket-100** | [`README.md`](./ticket-100/README.md) | - | - | - | - | - | -| **ticket-101** | [`README.md`](./ticket-101/README.md) | - | - | - | - | - | -| **ticket-102** | [`README.md`](./ticket-102/README.md) | - | - | - | - | - | -| **ticket-103** | [`README.md`](./ticket-103/README.md) | - | - | - | - | - | -| **ticket-104** | [`README.md`](./ticket-104/README.md) | - | - | - | - | - | -| **ticket-105** | [`README.md`](./ticket-105/README.md) | - | - | [`ai-codex.md`](./ticket-105/ai-codex.md) | - | - | -| **ticket-106** | [`README.md`](./ticket-106/README.md) | - | - | - | - | - | -| **ticket-107** | [`README.md`](./ticket-107/README.md) | - | - | - | - | - | -| **ticket-108** | [`README.md`](./ticket-108/README.md) | - | - | - | - | - | -| **ticket-109** | [`README.md`](./ticket-109/README.md) | - | - | - | - | - | -| **ticket-110** | [`README.md`](./ticket-110/README.md) | - | - | - | - | - | -| **ticket-111** | [`README.md`](./ticket-111/README.md) | - | - | [`ai-codex.md`](./ticket-111/ai-codex.md) | - | - | -| **ticket-112** | [`README.md`](./ticket-112/README.md) | - | - | [`ai-codex.md`](./ticket-112/ai-codex.md) | - | - | -| **ticket-113** | [`README.md`](./ticket-113/README.md) | - | - | - | - | - | -| **ticket-114** | [`README.md`](./ticket-114/README.md) | - | - | - | - | - | -| **ticket-167** | [`README.md`](./ticket-167/README.md) | - | - | - | - | - | -| **ticket-168** | [`README.md`](./ticket-168/README.md) | - | - | - | - | - | -| **ticket-169** | [`README.md`](./ticket-169/README.md) | - | - | - | - | - | -| **ticket-170** | [`README.md`](./ticket-170/README.md) | - | - | - | - | - | -| **ticket-173** | [`README.md`](./ticket-173/README.md) | - | - | - | - | - | -| **ticket-174** | [`README.md`](./ticket-174/README.md) | - | - | - | - | - | -| **ticket-175** | [`README.md`](./ticket-175/README.md) | - | - | - | - | - | -| **ticket-176** | [`README.md`](./ticket-176/README.md) | - | - | - | - | - | -| **ticket-177** | [`README.md`](./ticket-177/README.md) | - | - | - | - | - | -| **ticket-178** | [`README.md`](./ticket-178/README.md) | - | - | - | - | - | -| **ticket-179** | [`README.md`](./ticket-179/README.md) | - | - | - | - | - | -| **ticket-180** | [`README.md`](./ticket-180/README.md) | - | - | - | - | - | -| **ticket-181** | [`README.md`](./ticket-181/README.md) | - | - | - | - | - | -| **ticket-182** | [`README.md`](./ticket-182/README.md) | - | - | - | - | - | -| **ticket-183** | [`README.md`](./ticket-183/README.md) | - | - | - | - | - | -| **ticket-184** | [`README.md`](./ticket-184/README.md) | - | - | - | - | - | -| **ticket-185** | [`README.md`](./ticket-185/README.md) | - | - | - | - | - | -| **ticket-186** | [`README.md`](./ticket-186/README.md) | - | - | - | - | - | -| **ticket-187** | [`README.md`](./ticket-187/README.md) | - | - | - | - | - | -| **ticket-188** | [`README.md`](./ticket-188/README.md) | - | - | - | - | - | -| **ticket-190** | [`README.md`](./ticket-190/README.md) | - | - | - | - | - | +| **ticket-191** | [`README.md`](./ticket-191/README.md) | - | - | - | - | - | +| **ticket-193** | [`README.md`](./ticket-193/README.md) | - | - | - | - | - | +| **ticket-194** | [`README.md`](./ticket-194/README.md) | - | - | - | - | - | +| **ticket-195** | [`README.md`](./ticket-195/README.md) | - | - | - | - | - | | **ticket-196** | [`README.md`](./ticket-196/README.md) | - | - | - | - | - | | **ticket-197** | [`README.md`](./ticket-197/README.md) | - | - | - | - | - | | **ticket-198** | [`README.md`](./ticket-198/README.md) | - | - | - | - | - | @@ -277,152 +152,8 @@ This file indexes governance tickets without taking ownership of | **ticket-208** | [`README.md`](./ticket-208/README.md) | - | - | - | - | - | | **ticket-209** | [`README.md`](./ticket-209/README.md) | - | - | - | - | - | | **ticket-210** | [`README.md`](./ticket-210/README.md) | - | - | - | - | - | -| **ticket-212** | [`README.md`](./ticket-212/README.md) | - | - | - | - | - | | **ticket-211** | [`README.md`](./ticket-211/README.md) | - | - | - | - | - | -| **ticket-001** | [`README.md`](./ticket-001/README.md) | [`preprompt.md`](./ticket-001/preprompt.md) | - | [`ai-codex.md`](./ticket-001/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-001/ai-codex-logs.txt) | [`changelog.md`](./ticket-001/changelog.md) | -| **ticket-002** | [`README.md`](./ticket-002/README.md) | [`preprompt.md`](./ticket-002/preprompt.md) | - | [`ai-codex.md`](./ticket-002/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-002/ai-codex-logs.txt) | [`changelog.md`](./ticket-002/changelog.md) | -| **ticket-003** | [`README.md`](./ticket-003/README.md) | [`preprompt.md`](./ticket-003/preprompt.md) | - | [`ai-codex.md`](./ticket-003/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-003/ai-codex-logs.txt) | [`changelog.md`](./ticket-003/changelog.md) | -| **ticket-004** | [`README.md`](./ticket-004/README.md) | [`preprompt.md`](./ticket-004/preprompt.md) | - | [`ai-codex.md`](./ticket-004/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-004/ai-codex-logs.txt) | [`changelog.md`](./ticket-004/changelog.md) | -| **ticket-005** | [`README.md`](./ticket-005/README.md) | [`preprompt.md`](./ticket-005/preprompt.md) | - | [`ai-codex.md`](./ticket-005/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-005/ai-codex-logs.txt) | [`changelog.md`](./ticket-005/changelog.md) | -| **ticket-006** | [`README.md`](./ticket-006/README.md) | [`preprompt.md`](./ticket-006/preprompt.md) | - | [`ai-codex.md`](./ticket-006/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-006/ai-codex-logs.txt) | [`changelog.md`](./ticket-006/changelog.md) | -| **ticket-007** | [`README.md`](./ticket-007/README.md) | [`preprompt.md`](./ticket-007/preprompt.md) | - | [`ai-codex.md`](./ticket-007/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-007/ai-codex-logs.txt) | [`changelog.md`](./ticket-007/changelog.md) | -| **ticket-008** | [`README.md`](./ticket-008/README.md) | [`preprompt.md`](./ticket-008/preprompt.md) | - | [`ai-codex.md`](./ticket-008/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-008/ai-codex-logs.txt) | [`changelog.md`](./ticket-008/changelog.md) | -| **ticket-009** | [`README.md`](./ticket-009/README.md) | [`preprompt.md`](./ticket-009/preprompt.md) | - | [`ai-codex.md`](./ticket-009/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-009/ai-codex-logs.txt) | [`changelog.md`](./ticket-009/changelog.md) | -| **ticket-010** | [`README.md`](./ticket-010/README.md) | [`preprompt.md`](./ticket-010/preprompt.md) | - | [`ai-codex.md`](./ticket-010/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-010/ai-codex-logs.txt) | [`changelog.md`](./ticket-010/changelog.md) | -| **ticket-011** | [`README.md`](./ticket-011/README.md) | [`preprompt.md`](./ticket-011/preprompt.md) | - | [`ai-codex.md`](./ticket-011/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-011/ai-codex-logs.txt) | [`changelog.md`](./ticket-011/changelog.md) | -| **ticket-012** | [`README.md`](./ticket-012/README.md) | [`preprompt.md`](./ticket-012/preprompt.md) | - | [`ai-codex.md`](./ticket-012/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-012/ai-codex-logs.txt) | [`changelog.md`](./ticket-012/changelog.md) | -| **ticket-013** | [`README.md`](./ticket-013/README.md) | [`preprompt.md`](./ticket-013/preprompt.md) | - | [`ai-codex.md`](./ticket-013/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-013/ai-codex-logs.txt) | [`changelog.md`](./ticket-013/changelog.md) | -| **ticket-014** | [`README.md`](./ticket-014/README.md) | [`preprompt.md`](./ticket-014/preprompt.md) | - | [`ai-codex.md`](./ticket-014/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-014/ai-codex-logs.txt) | [`changelog.md`](./ticket-014/changelog.md) | -| **ticket-015** | [`README.md`](./ticket-015/README.md) | [`preprompt.md`](./ticket-015/preprompt.md) | - | [`ai-codex.md`](./ticket-015/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-015/ai-codex-logs.txt) | [`changelog.md`](./ticket-015/changelog.md) | -| **ticket-016** | [`README.md`](./ticket-016/README.md) | [`preprompt.md`](./ticket-016/preprompt.md) | - | [`ai-codex.md`](./ticket-016/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-016/ai-codex-logs.txt) | [`changelog.md`](./ticket-016/changelog.md) | -| **ticket-017** | [`README.md`](./ticket-017/README.md) | [`preprompt.md`](./ticket-017/preprompt.md) | - | [`ai-codex.md`](./ticket-017/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-017/ai-codex-logs.txt) | [`changelog.md`](./ticket-017/changelog.md) | -| **ticket-018** | [`README.md`](./ticket-018/README.md) | [`preprompt.md`](./ticket-018/preprompt.md) | - | [`ai-codex.md`](./ticket-018/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-018/ai-codex-logs.txt) | [`changelog.md`](./ticket-018/changelog.md) | -| **ticket-019** | [`README.md`](./ticket-019/README.md) | [`preprompt.md`](./ticket-019/preprompt.md) | - | [`ai-codex.md`](./ticket-019/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-019/ai-codex-logs.txt) | [`changelog.md`](./ticket-019/changelog.md) | -| **ticket-020** | [`README.md`](./ticket-020/README.md) | [`preprompt.md`](./ticket-020/preprompt.md) | - | [`ai-codex.md`](./ticket-020/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-020/ai-codex-logs.txt) | [`changelog.md`](./ticket-020/changelog.md) | -| **ticket-021** | [`README.md`](./ticket-021/README.md) | [`preprompt.md`](./ticket-021/preprompt.md) | - | [`ai-codex.md`](./ticket-021/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-021/ai-codex-logs.txt) | [`changelog.md`](./ticket-021/changelog.md) | -| **ticket-023** | [`README.md`](./ticket-023/README.md) | [`preprompt.md`](./ticket-023/preprompt.md) | - | [`ai-codex.md`](./ticket-023/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-023/ai-codex-logs.txt) | [`changelog.md`](./ticket-023/changelog.md) | -| **ticket-024** | [`README.md`](./ticket-024/README.md) | [`preprompt.md`](./ticket-024/preprompt.md) | - | [`ai-codex.md`](./ticket-024/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-024/ai-codex-logs.txt) | [`changelog.md`](./ticket-024/changelog.md) | -| **ticket-025** | [`README.md`](./ticket-025/README.md) | [`preprompt.md`](./ticket-025/preprompt.md) | - | [`ai-codex.md`](./ticket-025/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-025/ai-codex-logs.txt) | [`changelog.md`](./ticket-025/changelog.md) | -| **ticket-026** | [`README.md`](./ticket-026/README.md) | [`preprompt.md`](./ticket-026/preprompt.md) | - | [`ai-codex.md`](./ticket-026/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-026/ai-codex-logs.txt) | [`changelog.md`](./ticket-026/changelog.md) | -| **ticket-027** | [`README.md`](./ticket-027/README.md) | [`preprompt.md`](./ticket-027/preprompt.md) | - | [`ai-codex.md`](./ticket-027/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-027/ai-codex-logs.txt) | [`changelog.md`](./ticket-027/changelog.md) | -| **ticket-028** | [`README.md`](./ticket-028/README.md) | [`preprompt.md`](./ticket-028/preprompt.md) | - | [`ai-codex.md`](./ticket-028/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-028/ai-codex-logs.txt) | [`changelog.md`](./ticket-028/changelog.md) | -| **ticket-029** | [`README.md`](./ticket-029/README.md) | [`preprompt.md`](./ticket-029/preprompt.md) | - | [`ai-codex.md`](./ticket-029/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-029/ai-codex-logs.txt) | [`changelog.md`](./ticket-029/changelog.md) | -| **ticket-030** | [`README.md`](./ticket-030/README.md) | [`preprompt.md`](./ticket-030/preprompt.md) | - | [`ai-codex.md`](./ticket-030/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-030/ai-codex-logs.txt) | [`changelog.md`](./ticket-030/changelog.md) | -| **ticket-032** | [`README.md`](./ticket-032/README.md) | [`preprompt.md`](./ticket-032/preprompt.md) | - | [`ai-codex.md`](./ticket-032/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-032/ai-codex-logs.txt) | [`changelog.md`](./ticket-032/changelog.md) | -| **ticket-033** | [`README.md`](./ticket-033/README.md) | [`preprompt.md`](./ticket-033/preprompt.md) | - | [`ai-codex.md`](./ticket-033/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-033/ai-codex-logs.txt) | [`changelog.md`](./ticket-033/changelog.md) | -| **ticket-034** | [`README.md`](./ticket-034/README.md) | [`preprompt.md`](./ticket-034/preprompt.md) | - | [`ai-codex.md`](./ticket-034/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-034/ai-codex-logs.txt) | [`changelog.md`](./ticket-034/changelog.md) | -| **ticket-035** | [`README.md`](./ticket-035/README.md) | [`preprompt.md`](./ticket-035/preprompt.md) | - | [`ai-codex.md`](./ticket-035/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-035/ai-codex-logs.txt) | [`changelog.md`](./ticket-035/changelog.md) | -| **ticket-036** | [`README.md`](./ticket-036/README.md) | [`preprompt.md`](./ticket-036/preprompt.md) | - | [`ai-codex.md`](./ticket-036/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-036/ai-codex-logs.txt) | [`changelog.md`](./ticket-036/changelog.md) | -| **ticket-037** | [`README.md`](./ticket-037/README.md) | [`preprompt.md`](./ticket-037/preprompt.md) | - | [`ai-codex.md`](./ticket-037/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-037/ai-codex-logs.txt) | [`changelog.md`](./ticket-037/changelog.md) | -| **ticket-038** | [`README.md`](./ticket-038/README.md) | [`preprompt.md`](./ticket-038/preprompt.md) | - | [`ai-codex.md`](./ticket-038/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-038/ai-codex-logs.txt) | [`changelog.md`](./ticket-038/changelog.md) | -| **ticket-039** | [`README.md`](./ticket-039/README.md) | [`preprompt.md`](./ticket-039/preprompt.md) | - | [`ai-codex.md`](./ticket-039/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-039/ai-codex-logs.txt) | [`changelog.md`](./ticket-039/changelog.md) | -| **ticket-040** | [`README.md`](./ticket-040/README.md) | [`preprompt.md`](./ticket-040/preprompt.md) | - | [`ai-codex.md`](./ticket-040/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-040/ai-codex-logs.txt) | [`changelog.md`](./ticket-040/changelog.md) | -| **ticket-041** | [`README.md`](./ticket-041/README.md) | [`preprompt.md`](./ticket-041/preprompt.md) | - | [`ai-codex.md`](./ticket-041/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-041/ai-codex-logs.txt) | [`changelog.md`](./ticket-041/changelog.md) | -| **ticket-042** | [`README.md`](./ticket-042/README.md) | [`preprompt.md`](./ticket-042/preprompt.md) | - | [`ai-codex.md`](./ticket-042/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-042/ai-codex-logs.txt) | [`changelog.md`](./ticket-042/changelog.md) | -| **ticket-043** | [`README.md`](./ticket-043/README.md) | [`preprompt.md`](./ticket-043/preprompt.md) | - | [`ai-codex.md`](./ticket-043/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-043/ai-codex-logs.txt) | [`changelog.md`](./ticket-043/changelog.md) | -| **ticket-044** | [`README.md`](./ticket-044/README.md) | [`preprompt.md`](./ticket-044/preprompt.md) | - | [`ai-codex.md`](./ticket-044/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-044/ai-codex-logs.txt) | [`changelog.md`](./ticket-044/changelog.md) | -| **ticket-046** | [`README.md`](./ticket-046/README.md) | [`preprompt.md`](./ticket-046/preprompt.md) | - | [`ai-codex.md`](./ticket-046/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-046/ai-codex-logs.txt) | [`changelog.md`](./ticket-046/changelog.md) | -| **ticket-047** | [`README.md`](./ticket-047/README.md) | [`preprompt.md`](./ticket-047/preprompt.md) | - | [`ai-codex.md`](./ticket-047/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-047/ai-codex-logs.txt) | [`changelog.md`](./ticket-047/changelog.md) | -| **ticket-048** | [`README.md`](./ticket-048/README.md) | [`preprompt.md`](./ticket-048/preprompt.md) | - | [`ai-codex.md`](./ticket-048/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-048/ai-codex-logs.txt) | [`changelog.md`](./ticket-048/changelog.md) | -| **ticket-049** | [`README.md`](./ticket-049/README.md) | [`preprompt.md`](./ticket-049/preprompt.md) | - | [`ai-codex.md`](./ticket-049/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-049/ai-codex-logs.txt) | [`changelog.md`](./ticket-049/changelog.md) | -| **ticket-050** | [`README.md`](./ticket-050/README.md) | [`preprompt.md`](./ticket-050/preprompt.md) | - | [`ai-codex.md`](./ticket-050/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-050/ai-codex-logs.txt) | [`changelog.md`](./ticket-050/changelog.md) | -| **ticket-051** | [`README.md`](./ticket-051/README.md) | [`preprompt.md`](./ticket-051/preprompt.md) | - | [`ai-codex.md`](./ticket-051/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-051/ai-codex-logs.txt) | [`changelog.md`](./ticket-051/changelog.md) | -| **ticket-052** | [`README.md`](./ticket-052/README.md) | [`preprompt.md`](./ticket-052/preprompt.md) | - | [`ai-codex.md`](./ticket-052/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-052/ai-codex-logs.txt) | [`changelog.md`](./ticket-052/changelog.md) | -| **ticket-053** | [`README.md`](./ticket-053/README.md) | [`preprompt.md`](./ticket-053/preprompt.md) | - | [`ai-codex.md`](./ticket-053/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-053/ai-codex-logs.txt) | [`changelog.md`](./ticket-053/changelog.md) | -| **ticket-054** | [`README.md`](./ticket-054/README.md) | [`preprompt.md`](./ticket-054/preprompt.md) | - | [`ai-codex.md`](./ticket-054/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-054/ai-codex-logs.txt) | [`changelog.md`](./ticket-054/changelog.md) | -| **ticket-055** | [`README.md`](./ticket-055/README.md) | [`preprompt.md`](./ticket-055/preprompt.md) | - | [`ai-codex.md`](./ticket-055/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-055/ai-codex-logs.txt) | [`changelog.md`](./ticket-055/changelog.md) | -| **ticket-056** | [`README.md`](./ticket-056/README.md) | [`preprompt.md`](./ticket-056/preprompt.md) | - | [`ai-codex.md`](./ticket-056/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-056/ai-codex-logs.txt) | [`changelog.md`](./ticket-056/changelog.md) | -| **ticket-057** | [`README.md`](./ticket-057/README.md) | [`preprompt.md`](./ticket-057/preprompt.md) | - | [`ai-codex.md`](./ticket-057/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-057/ai-codex-logs.txt) | [`changelog.md`](./ticket-057/changelog.md) | -| **ticket-058** | [`README.md`](./ticket-058/README.md) | [`preprompt.md`](./ticket-058/preprompt.md) | - | [`ai-codex.md`](./ticket-058/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-058/ai-codex-logs.txt) | [`changelog.md`](./ticket-058/changelog.md) | -| **ticket-059** | [`README.md`](./ticket-059/README.md) | [`preprompt.md`](./ticket-059/preprompt.md) | - | [`ai-codex.md`](./ticket-059/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-059/ai-codex-logs.txt) | [`changelog.md`](./ticket-059/changelog.md) | -| **ticket-060** | [`README.md`](./ticket-060/README.md) | [`preprompt.md`](./ticket-060/preprompt.md) | - | [`ai-codex.md`](./ticket-060/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-060/ai-codex-logs.txt) | [`changelog.md`](./ticket-060/changelog.md) | -| **ticket-061** | [`README.md`](./ticket-061/README.md) | [`preprompt.md`](./ticket-061/preprompt.md) | - | [`ai-codex.md`](./ticket-061/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-061/ai-codex-logs.txt) | [`changelog.md`](./ticket-061/changelog.md) | -| **ticket-062** | [`README.md`](./ticket-062/README.md) | [`preprompt.md`](./ticket-062/preprompt.md) | - | [`ai-codex.md`](./ticket-062/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-062/ai-codex-logs.txt) | [`changelog.md`](./ticket-062/changelog.md) | -| **ticket-063** | [`README.md`](./ticket-063/README.md) | [`preprompt.md`](./ticket-063/preprompt.md) | - | [`ai-codex.md`](./ticket-063/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-063/ai-codex-logs.txt) | [`changelog.md`](./ticket-063/changelog.md) | -| **ticket-064** | [`README.md`](./ticket-064/README.md) | [`preprompt.md`](./ticket-064/preprompt.md) | - | [`ai-codex.md`](./ticket-064/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-064/ai-codex-logs.txt) | [`changelog.md`](./ticket-064/changelog.md) | -| **ticket-066** | [`README.md`](./ticket-066/README.md) | [`preprompt.md`](./ticket-066/preprompt.md) | - | [`ai-codex.md`](./ticket-066/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-066/ai-codex-logs.txt) | [`changelog.md`](./ticket-066/changelog.md) | -| **ticket-067** | [`README.md`](./ticket-067/README.md) | [`preprompt.md`](./ticket-067/preprompt.md) | - | [`ai-codex.md`](./ticket-067/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-067/ai-codex-logs.txt) | [`changelog.md`](./ticket-067/changelog.md) | -| **ticket-068** | [`README.md`](./ticket-068/README.md) | [`preprompt.md`](./ticket-068/preprompt.md) | - | [`ai-gpt-5.6-sol.md`](./ticket-068/ai-gpt-5.6-sol.md) | [`ai-gpt-5.6-sol-logs.txt`](./ticket-068/ai-gpt-5.6-sol-logs.txt) | [`changelog.md`](./ticket-068/changelog.md) | -| **ticket-069** | [`README.md`](./ticket-069/README.md) | [`preprompt.md`](./ticket-069/preprompt.md) | - | [`ai-codex.md`](./ticket-069/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-069/ai-codex-logs.txt) | [`changelog.md`](./ticket-069/changelog.md) | -| **ticket-071** | [`README.md`](./ticket-071/README.md) | - | - | [`ai-codex.md`](./ticket-071/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-071/ai-codex-logs.txt) | - | -| **ticket-072** | [`README.md`](./ticket-072/README.md) | - | - | - | - | - | -| **ticket-073** | [`README.md`](./ticket-073/README.md) | - | - | [`ai-codex.md`](./ticket-073/ai-codex.md) | - | - | -| **ticket-074** | [`README.md`](./ticket-074/README.md) | - | - | [`ai-codex.md`](./ticket-074/ai-codex.md) | - | - | -| **ticket-075** | [`README.md`](./ticket-075/README.md) | - | - | - | - | - | -| **ticket-078** | [`README.md`](./ticket-078/README.md) | - | - | - | - | - | -| **ticket-079** | [`README.md`](./ticket-079/README.md) | - | - | - | - | - | -| **ticket-080** | [`README.md`](./ticket-080/README.md) | - | - | - | - | - | -| **ticket-081** | [`README.md`](./ticket-081/README.md) | - | - | - | - | - | -| **ticket-082** | [`README.md`](./ticket-082/README.md) | - | - | - | - | - | -| **ticket-083** | [`README.md`](./ticket-083/README.md) | - | - | [`ai-codex.md`](./ticket-083/ai-codex.md) | - | - | -| **ticket-084** | [`README.md`](./ticket-084/README.md) | - | - | - | - | - | -| **ticket-085** | [`README.md`](./ticket-085/README.md) | - | - | [`ai-codex.md`](./ticket-085/ai-codex.md) | - | - | -| **ticket-086** | [`README.md`](./ticket-086/README.md) | - | - | - | - | - | -| **ticket-087** | [`README.md`](./ticket-087/README.md) | - | - | - | - | - | -| **ticket-088** | [`README.md`](./ticket-088/README.md) | - | - | - | - | - | -| **ticket-089** | [`README.md`](./ticket-089/README.md) | - | - | - | - | - | -| **ticket-090** | [`README.md`](./ticket-090/README.md) | - | - | [`ai-codex.md`](./ticket-090/ai-codex.md) | - | - | -| **ticket-091** | [`README.md`](./ticket-091/README.md) | - | - | - | - | - | -| **ticket-092** | [`README.md`](./ticket-092/README.md) | - | - | - | - | - | -| **ticket-093** | [`README.md`](./ticket-093/README.md) | - | - | - | - | - | -| **ticket-094** | [`README.md`](./ticket-094/README.md) | - | - | - | - | - | -| **ticket-095** | [`README.md`](./ticket-095/README.md) | - | - | - | - | - | -| **ticket-096** | [`README.md`](./ticket-096/README.md) | - | - | - | - | - | -| **ticket-097** | [`README.md`](./ticket-097/README.md) | - | - | - | - | - | -| **ticket-099** | [`README.md`](./ticket-099/README.md) | - | - | - | - | - | -| **ticket-100** | [`README.md`](./ticket-100/README.md) | - | - | - | - | - | -| **ticket-101** | [`README.md`](./ticket-101/README.md) | - | - | - | - | - | -| **ticket-102** | [`README.md`](./ticket-102/README.md) | - | - | - | - | - | -| **ticket-103** | [`README.md`](./ticket-103/README.md) | - | - | - | - | - | -| **ticket-104** | [`README.md`](./ticket-104/README.md) | - | - | - | - | - | -| **ticket-105** | [`README.md`](./ticket-105/README.md) | - | - | [`ai-codex.md`](./ticket-105/ai-codex.md) | - | - | -| **ticket-106** | [`README.md`](./ticket-106/README.md) | - | - | - | - | - | -| **ticket-107** | [`README.md`](./ticket-107/README.md) | - | - | - | - | - | -| **ticket-108** | [`README.md`](./ticket-108/README.md) | - | - | - | - | - | -| **ticket-109** | [`README.md`](./ticket-109/README.md) | - | - | - | - | - | -| **ticket-110** | [`README.md`](./ticket-110/README.md) | - | - | - | - | - | -| **ticket-111** | [`README.md`](./ticket-111/README.md) | - | - | [`ai-codex.md`](./ticket-111/ai-codex.md) | - | - | -| **ticket-112** | [`README.md`](./ticket-112/README.md) | - | - | [`ai-codex.md`](./ticket-112/ai-codex.md) | - | - | -| **ticket-113** | [`README.md`](./ticket-113/README.md) | - | - | - | - | - | -| **ticket-114** | [`README.md`](./ticket-114/README.md) | - | - | - | - | - | -| **ticket-167** | [`README.md`](./ticket-167/README.md) | - | - | - | - | - | -| **ticket-168** | [`README.md`](./ticket-168/README.md) | - | - | - | - | - | -| **ticket-169** | [`README.md`](./ticket-169/README.md) | - | - | - | - | - | -| **ticket-170** | [`README.md`](./ticket-170/README.md) | - | - | - | - | - | -| **ticket-173** | [`README.md`](./ticket-173/README.md) | - | - | - | - | - | -| **ticket-174** | [`README.md`](./ticket-174/README.md) | - | - | - | - | - | -| **ticket-175** | [`README.md`](./ticket-175/README.md) | - | - | - | - | - | -| **ticket-176** | [`README.md`](./ticket-176/README.md) | - | - | - | - | - | -| **ticket-177** | [`README.md`](./ticket-177/README.md) | - | - | - | - | - | -| **ticket-178** | [`README.md`](./ticket-178/README.md) | - | - | - | - | - | -| **ticket-179** | [`README.md`](./ticket-179/README.md) | - | - | - | - | - | -| **ticket-180** | [`README.md`](./ticket-180/README.md) | - | - | - | - | - | -| **ticket-181** | [`README.md`](./ticket-181/README.md) | - | - | - | - | - | -| **ticket-182** | [`README.md`](./ticket-182/README.md) | - | - | - | - | - | -| **ticket-183** | [`README.md`](./ticket-183/README.md) | - | - | - | - | - | -| **ticket-184** | [`README.md`](./ticket-184/README.md) | - | - | - | - | - | -| **ticket-185** | [`README.md`](./ticket-185/README.md) | - | - | - | - | - | -| **ticket-186** | [`README.md`](./ticket-186/README.md) | - | - | - | - | - | -| **ticket-187** | [`README.md`](./ticket-187/README.md) | - | - | - | - | - | -| **ticket-188** | [`README.md`](./ticket-188/README.md) | - | - | - | - | - | -| **ticket-190** | [`README.md`](./ticket-190/README.md) | - | - | - | - | - | -| **ticket-196** | [`README.md`](./ticket-196/README.md) | - | - | - | - | - | -| **ticket-197** | [`README.md`](./ticket-197/README.md) | - | - | - | - | - | -| **ticket-198** | [`README.md`](./ticket-198/README.md) | - | - | - | - | - | -| **ticket-199** | [`README.md`](./ticket-199/README.md) | - | - | - | - | - | -| **ticket-200** | [`README.md`](./ticket-200/README.md) | - | - | - | - | - | -| **ticket-201** | [`README.md`](./ticket-201/README.md) | - | - | - | - | - | -| **ticket-202** | [`README.md`](./ticket-202/README.md) | - | - | - | - | - | -| **ticket-203** | [`README.md`](./ticket-203/README.md) | - | - | - | - | - | -| **ticket-204** | [`README.md`](./ticket-204/README.md) | - | - | - | - | - | -| **ticket-205** | [`README.md`](./ticket-205/README.md) | - | - | - | - | - | -| **ticket-206** | [`README.md`](./ticket-206/README.md) | - | - | - | - | - | -| **ticket-207** | [`README.md`](./ticket-207/README.md) | - | - | - | - | - | -| **ticket-208** | [`README.md`](./ticket-208/README.md) | - | - | - | - | - | -| **ticket-209** | [`README.md`](./ticket-209/README.md) | - | - | - | - | - | -| **ticket-210** | [`README.md`](./ticket-210/README.md) | - | - | - | - | - | | **ticket-212** | [`README.md`](./ticket-212/README.md) | - | - | - | - | - | -| **ticket-211** | [`README.md`](./ticket-211/README.md) | - | - | - | - | - | | **ticket-213** | [`README.md`](./ticket-213/README.md) | - | - | - | - | - | | **ticket-215** | [`README.md`](./ticket-215/README.md) | - | - | - | - | - | | **ticket-216** | [`README.md`](./ticket-216/README.md) | - | - | - | - | - | diff --git a/project/ticket-217/README.md b/project/ticket-217/README.md new file mode 100644 index 0000000..80a3d2e --- /dev/null +++ b/project/ticket-217/README.md @@ -0,0 +1,24 @@ +# Ticket 217: Validate exact source-hub candidate governance before publication + +- **ID**: ticket-217 +- **Owner**: agent:antigravity +- **Status**: IN_PROGRESS +- **Workflow state**: EDIT +- **Created**: 2026-10-28 + +## Goal and scope + +Validate the exact source-hub candidate against `governance/manifest.hub.json` with accepted base, exact head, and `--actor ci` before running expensive source-hub suites or publishing a PR, route `goal governance workspace-check` to `scripts/workspace_lifecycle_check.py` in the source hub, reject non-canonical `--ticket` values before expensive preflight suites, and include preflight duration in total reported workflow time (closes #145). + +- `SESSION_EXECUTION_AUTHORIZATION`: autonomous session execution requested by user. + +## Acceptance criteria + +- [x] AC-01: In the recognized source hub (with or without an incidental `.governance` tree), run `scripts/governance_check.py` with `governance/manifest.hub.json`, `governance/stack-profiles.json`, accepted base, exact head, and `--actor ci` before expensive source-hub suites and before push/PR. +- [x] AC-02: Route `goal governance workspace-check` to `scripts/workspace_lifecycle_check.py` when the target root is the source hub. +- [x] AC-03: Reject non-canonical `--ticket` identifiers before expensive preflight/test suites and record preflight/total elapsed time across `execute_push_workflow`. + +## Tracking boundary + +This directory contains the minimal reviewed intent. Optional participant prose +and raw command logs are not required delivery output. diff --git a/project/ticket-217/intent.json b/project/ticket-217/intent.json new file mode 100644 index 0000000..53037dc --- /dev/null +++ b/project/ticket-217/intent.json @@ -0,0 +1,102 @@ +{ + "schema": "new-project.intent/v3", + "ticket": "ticket-217", + "summary": "Validate exact source-hub candidate governance before publication", + "workstream": "application", + "classification": { + "kind": "BUG", + "priority": "P1", + "origin": "regression" + }, + "allowedPaths": [ + "project/ticket-217/**", + "TODO.md", + "project/TICKETS.md", + "goal/cli/governance_cmd.py", + "goal/governance/delivery.py", + "goal/push/core.py", + "tests/test_governance_cmd.py", + "tests/test_governance_delivery.py" + ], + "forbiddenPaths": [ + "project/ticket-*/user-*.md" + ], + "stacks": [], + "dependsOn": [], + "conflictsWith": [], + "integrationTicket": null, + "delivery": { + "acceptedBaseSha": "7607bf6bc6235e3bf0d213fed3f372a80b9af118", + "targetBranch": "main", + "outcome": "In the recognized source hub, validate exact candidate governance against governance/manifest.hub.json with accepted base, exact head, and CI actor before expensive source suites and before PR publication; route workspace-check to scripts/workspace_lifecycle_check.py in the source hub; reject invalid --ticket before preflight suites; include preflight duration in total workflow timing.", + "nonGoals": [ + "Do not weaken source-hub suites or target repository governance gates.", + "Do not modify dependency manifests or lockfiles." + ], + "complexity": "S", + "estimatedMinutes": 30, + "budgets": { + "maxImplementationFiles": 5, + "maxAffectedComponents": 2, + "maxPublicInterfaceChanges": 0, + "maxRuntimeDependencies": 0 + }, + "architecture": { + "status": "accepted", + "decision": "Prioritize source-hub detection in _governance_gate even when an incidental .governance directory exists, invoke scripts/governance_check.py with governance/manifest.hub.json and --actor ci before run_source_hub_health and deliver_pull_request, route workspace-check to scripts/workspace_lifecycle_check.py for source hubs, and validate canonical --ticket and start timing before preflight in execute_push_workflow.", + "components": [ + { + "name": "governance-delivery", + "paths": [ + "goal/governance/delivery.py", + "goal/cli/governance_cmd.py", + "tests/test_governance_delivery.py", + "tests/test_governance_cmd.py" + ] + }, + { + "name": "push-workflow", + "paths": [ + "goal/push/core.py" + ] + } + ], + "responsibilityChanges": false, + "interfaceChanges": [], + "dataChanges": [], + "ui": { + "impact": "none", + "states": [], + "evidence": [] + }, + "rollback": "Protected revert of the ticket-217 commit." + }, + "runtimeDependencies": [], + "validation": [ + { + "criterion": "AC-01", + "commands": [ + "python -m pytest -q tests/test_governance_delivery.py", + "./project/governance-check.sh --base origin/main" + ], + "evidence": "receipt:goal217/source-hub-candidate-gate" + }, + { + "criterion": "AC-02", + "commands": [ + "python -m pytest -q tests/test_governance_cmd.py", + "./project/governance-check.sh --base origin/main" + ], + "evidence": "receipt:goal217/source-hub-workspace-check" + }, + { + "criterion": "AC-03", + "commands": [ + "python -m pytest -q tests/test_governance_delivery.py", + "./project/governance-check.sh --base origin/main" + ], + "evidence": "receipt:goal217/preflight-ticket-and-timing" + } + ] + } +} diff --git a/tests/test_governance_cmd.py b/tests/test_governance_cmd.py index 3414717..57238f9 100644 --- a/tests/test_governance_cmd.py +++ b/tests/test_governance_cmd.py @@ -1138,3 +1138,111 @@ def test_pre_commit_rejects_bootstrap_native_adoption(tmp_path): assert result.exit_code == 2 assert "cannot be combined" in result.output assert "--bootstrap-native-adoption" in result.output + + +@pytest.mark.parametrize("with_incidental_governance", [False, True]) +def test_workspace_check_routes_source_hub_to_canonical_script( + tmp_path, with_incidental_governance +): + target = tmp_path / "new-project" + workspace = tmp_path / "workspace" + allowed = workspace / "active" + target.mkdir() + workspace.mkdir() + for relative in ( + "governance/package-manifest.json", + "governance/manifest.default.json", + "scripts/governance_check.py", + ): + path = target / relative + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text("{}\n", encoding="utf-8") + hub_checker = target / "scripts" / "workspace_lifecycle_check.py" + hub_checker.write_text( + "import sys\nprint('HUB_WORKSPACE_ARGS=' + '|'.join(sys.argv[1:]))\nraise SystemExit(5)\n", + encoding="utf-8", + ) + if with_incidental_governance: + make_workspace_checker(target, exit_code=99) + + result = CliRunner().invoke( + main, + [ + "governance", + "workspace-check", + "--target-root", + str(target), + "--workspace-root", + str(workspace), + "--allow", + str(allowed), + "--format", + "json", + ], + ) + + assert result.exit_code == 5 + assert f"HUB_WORKSPACE_ARGS=--workspace-root|{workspace}|--allow|{allowed}|--format|json" in result.output + + +def test_workspace_check_source_hub_fails_closed_when_canonical_script_missing(tmp_path): + target = tmp_path / "new-project" + workspace = tmp_path / "workspace" + target.mkdir() + workspace.mkdir() + for relative in ( + "governance/package-manifest.json", + "governance/manifest.default.json", + "scripts/governance_check.py", + ): + path = target / relative + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text("{}\n", encoding="utf-8") + + result = CliRunner().invoke( + main, + [ + "governance", + "workspace-check", + "--target-root", + str(target), + "--workspace-root", + str(workspace), + ], + ) + + assert result.exit_code == 1 + assert "source-hub workspace lifecycle checker is missing: scripts/workspace_lifecycle_check.py" in result.output + + +def test_governance_check_prefers_source_hub_even_with_incidental_governance( + tmp_path, monkeypatch +): + target = tmp_path / "new-project" + target.mkdir() + make_adopted_governance(target, exit_code=99) + for relative in ( + "governance/package-manifest.json", + "governance/manifest.default.json", + "scripts/governance_check.py", + ): + path = target / relative + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text("{}\n", encoding="utf-8") + monkeypatch.setattr( + governance_cmd, + "run_source_hub_health", + lambda candidate: SourceHubHealthResult( + 0, f"GOV-HUB-PASS: {candidate.name}\n", "", 3 + ), + ) + + result = CliRunner().invoke( + main, + ["governance", "check", "--target-root", str(target)], + ) + + assert result.exit_code == 0 + assert "GOV-HUB-PASS: new-project" in result.output + assert "VALIDATOR_ARGS=" not in result.output + diff --git a/tests/test_governance_delivery.py b/tests/test_governance_delivery.py index ff66a26..05d3bb7 100644 --- a/tests/test_governance_delivery.py +++ b/tests/test_governance_delivery.py @@ -1110,3 +1110,327 @@ def run(arguments, *, cwd=None): with pytest.raises(click.ClickException, match="could not query"): delivery._query_open_pull_request(_pull_request_policy(), "ticket/180-rest-recovery", root) assert not any(call[:2] == ["gh", "api"] for call in calls) + + +def _source_hub_repository(tmp_path: Path, *, with_incidental_governance: bool = False) -> Path: + root = _publish_repository(tmp_path) + for relative in ( + *delivery.SOURCE_HUB_FILES, + delivery.SOURCE_HUB_MANIFEST, + delivery.SOURCE_HUB_STACK_PROFILES, + delivery.SOURCE_HUB_WORK_CLASSIFICATION, + ): + path = root / relative + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text("{}\n", encoding="utf-8") + if with_incidental_governance: + for relative in delivery.GOVERNANCE_PACKAGE_FILES.values(): + path = root / relative + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text("{}\n", encoding="utf-8") + gate = root / "project" / "governance-check.sh" + gate.parent.mkdir(parents=True, exist_ok=True) + gate.write_text("#!/bin/sh\nexit 99\n", encoding="utf-8") + gate.chmod(0o755) + _git(root, "add", ".") + _git(root, "commit", "--quiet", "-m", "seed source hub") + _git(root, "push", "--quiet", "origin", "main") + return root + + +@pytest.mark.parametrize("incidental_governance", [False, True]) +def test_source_hub_candidate_gate_runs_before_suites_with_or_without_incidental_governance( + tmp_path, monkeypatch, incidental_governance +): + root = _source_hub_repository(tmp_path, with_incidental_governance=incidental_governance) + base_sha = _git(root, "rev-parse", "HEAD").stdout.strip() + _git(root, "switch", "-c", "ticket/230-candidate-scope") + head_sha = _commit_ticket_change(root, "[ticket-230] validate source hub candidate") + + order = [] + original_run = delivery._run + + def fake_run(arguments, *, cwd=None): + if len(arguments) >= 2 and str(arguments[1]).endswith("scripts/governance_check.py"): + order.append(("candidate_gate", arguments)) + return subprocess.CompletedProcess(arguments, 0, "GOV-PASS\n", "") + if len(arguments) >= 1 and str(arguments[0]).endswith("governance-check.sh"): + pytest.fail("target wrapper must never run in source hub") + return original_run(arguments, cwd=cwd) + + monkeypatch.setattr(delivery, "_run", fake_run) + monkeypatch.setattr( + delivery, + "run_source_hub_health", + lambda candidate: ( + order.append(("health_suites", candidate)) + or delivery.SourceHubHealthResult(0, "GOV-HUB-PASS\n", "", 3) + ), + ) + + delivery._governance_gate(root, base=base_sha) + + assert [step for step, _ in order] == ["candidate_gate", "health_suites"] + gate_args = order[0][1] + assert gate_args[gate_args.index("--manifest") + 1] == delivery.SOURCE_HUB_MANIFEST + assert gate_args[gate_args.index("--stack-profiles") + 1] == delivery.SOURCE_HUB_STACK_PROFILES + assert gate_args[gate_args.index("--work-classification") + 1] == delivery.SOURCE_HUB_WORK_CLASSIFICATION + assert gate_args[gate_args.index("--base") + 1] == base_sha + assert gate_args[gate_args.index("--head") + 1] == head_sha + assert gate_args[gate_args.index("--actor") + 1] == "ci" + + +def test_source_hub_candidate_gate_rejects_over_budget_candidate_before_suites( + tmp_path, monkeypatch +): + root = _source_hub_repository(tmp_path, with_incidental_governance=True) + base_sha = _git(root, "rev-parse", "HEAD").stdout.strip() + _git(root, "switch", "-c", "ticket/339-over-budget") + _commit_ticket_change(root, "[ticket-339] over budget candidate") + + original_run = delivery._run + + def fake_run(arguments, *, cwd=None): + if len(arguments) >= 2 and str(arguments[1]).endswith("scripts/governance_check.py"): + return subprocess.CompletedProcess( + arguments, + 1, + "GOV-DELIVERY-001 ERROR: estimatedMinutes=40 exceeds maxActiveMinutes=30\n", + "", + ) + return original_run(arguments, cwd=cwd) + + monkeypatch.setattr(delivery, "_run", fake_run) + monkeypatch.setattr( + delivery, + "run_source_hub_health", + lambda _candidate: pytest.fail("expensive source-hub suites must not run after candidate failure"), + ) + + with pytest.raises(click.ClickException, match="GOV-DELIVERY-001"): + delivery._governance_gate(root, base=base_sha) + + +@pytest.mark.parametrize( + "missing_rel", + [delivery.SOURCE_HUB_MANIFEST, delivery.SOURCE_HUB_STACK_PROFILES], +) +def test_source_hub_candidate_gate_fails_closed_when_source_contract_missing( + tmp_path, monkeypatch, missing_rel +): + root = _source_hub_repository(tmp_path) + base_sha = _git(root, "rev-parse", "HEAD").stdout.strip() + (root / missing_rel).unlink() + monkeypatch.setattr( + delivery, + "run_source_hub_health", + lambda _candidate: pytest.fail("suites must not run when candidate contract is missing"), + ) + + with pytest.raises(click.ClickException, match=f"missing source-hub candidate contract.*{missing_rel}"): + delivery._governance_gate(root, base=base_sha) + + +def test_deliver_pull_request_validates_exact_source_hub_head_before_push( + tmp_path, monkeypatch +): + root = _source_hub_repository(tmp_path) + base_sha = _git(root, "rev-parse", "HEAD").stdout.strip() + _git(root, "switch", "-c", "ticket/230-exact-head") + head_sha = _commit_ticket_change(root, "[ticket-230] validate exact head before push") + + policy = replace(_pull_request_policy(), require_clean_governance=True) + events = [] + original_run = delivery._run + + def fake_run(arguments, *, cwd=None): + if len(arguments) >= 2 and str(arguments[1]).endswith("scripts/governance_check.py"): + events.append(("candidate_gate", arguments)) + return subprocess.CompletedProcess(arguments, 0, "GOV-PASS\n", "") + if arguments[:2] == ["git", "push"]: + events.append(("git_push", arguments)) + return subprocess.CompletedProcess(arguments, 0, "", "") + if arguments[:3] == ["gh", "pr", "list"]: + payload = ( + [{"url": "https://github.com/wellmanifest/new-project/pull/354", "headRefOid": head_sha}] + if any(kind == "pr_create" for kind, _ in events) + else [] + ) + return subprocess.CompletedProcess(arguments, 0, json.dumps(payload), "") + if arguments[:3] == ["gh", "pr", "create"]: + events.append(("pr_create", arguments)) + return subprocess.CompletedProcess( + arguments, 0, "https://github.com/wellmanifest/new-project/pull/354\n", "" + ) + return original_run(arguments, cwd=cwd) + + monkeypatch.setattr(delivery, "_run", fake_run) + + branch, url = delivery.deliver_pull_request( + policy, + ticket="ticket-230", + title="[ticket-230] validate exact head before push", + cwd=root, + ) + + assert branch == "ticket/230-exact-head" + assert url.endswith("/354") + assert [kind for kind, _ in events] == ["candidate_gate", "git_push", "pr_create"] + gate_args = events[0][1] + assert gate_args[gate_args.index("--base") + 1] == base_sha + assert gate_args[gate_args.index("--head") + 1] == head_sha + + +def test_execute_push_workflow_rejects_invalid_ticket_before_preflight_and_records_timing( + monkeypatch, +): + from unittest.mock import patch + from goal.push import core + + policy = _pull_request_policy() + ctx_obj = { + "yes": True, + "markdown": False, + "config": {}, + "user_config": {}, + "delivery_mode": "pull-request", + "all_flags": True, + } + + with ( + patch("goal.governance.delivery.resolve_delivery_policy", return_value=policy), + patch( + "goal.governance.delivery.validate_delivery_ready", + side_effect=AssertionError("preflight must not run for invalid --ticket"), + ), + pytest.raises(click.ClickException, match="canonical `--ticket ticket-NNN`"), + ): + core.execute_push_workflow( + ctx_obj=ctx_obj, + bump="patch", + no_tag=False, + no_changelog=False, + no_version_sync=False, + message=None, + dry_run=False, + yes=True, + markdown=False, + split=False, + ticket="230", + abstraction=None, + todo=False, + ) + + +def test_execute_push_workflow_records_preflight_and_total_time_on_success_and_failure( + monkeypatch, +): + from unittest.mock import patch + from goal.push import core + + policy = _pull_request_policy() + clock = [100.0] + + def fake_time(): + return clock[0] + + monkeypatch.setattr(core.time, "time", fake_time) + + # 1. Failure during preflight records both _preflight_time and _elapsed_time + fail_ctx = { + "yes": True, + "markdown": False, + "config": {}, + "user_config": {}, + "delivery_mode": "pull-request", + "all_flags": True, + } + + def fail_preflight(_delivery): + clock[0] += 185.0 + raise click.ClickException("GOV-DELIVERY-001: over budget") + + with ( + patch("goal.governance.delivery.resolve_delivery_policy", return_value=policy), + patch("goal.governance.delivery.validate_delivery_ready", side_effect=fail_preflight), + pytest.raises(click.ClickException, match="GOV-DELIVERY-001"), + ): + core.execute_push_workflow( + ctx_obj=fail_ctx, + bump="patch", + no_tag=False, + no_changelog=False, + no_version_sync=False, + message=None, + dry_run=False, + yes=True, + markdown=False, + split=False, + ticket="ticket-230", + abstraction=None, + todo=False, + ) + + assert fail_ctx["_preflight_time"] == pytest.approx(185.0) + assert fail_ctx["_elapsed_time"] == pytest.approx(185.0) + + # 2. Committed-candidate resume includes preflight duration in total elapsed time + clock[0] = 500.0 + ok_ctx = { + "yes": True, + "markdown": False, + "config": {}, + "user_config": {}, + "delivery_mode": "pull-request", + "all_flags": True, + } + candidate = delivery.PendingPullRequestDelivery( + base_sha="a" * 40, + head_sha="b" * 40, + title="[ticket-230] fix(delivery): include preflight timing", + files=("goal/push/core.py",), + ) + + def slow_preflight(_delivery): + clock[0] += 180.0 + + def fast_tests(*_args, **_kwargs): + clock[0] += 6.0 + return ("Tests passed", 0) + + with ( + patch("goal.governance.delivery.resolve_delivery_policy", return_value=policy), + patch("goal.governance.delivery.validate_delivery_ready", side_effect=slow_preflight), + patch( + "goal.governance.delivery.pending_pull_request_delivery", + side_effect=[candidate, candidate], + ), + patch( + "goal.governance.delivery.deliver_pull_request", + return_value=("ticket/230-timing", "https://example.test/pr/354"), + ), + patch("goal.governance.delivery.record_delivery_event"), + patch("goal.push.core._validate_toml_or_exit"), + patch("goal.push.core._detect_project_types", return_value=["python"]), + patch("goal.cli.version.get_current_version", return_value="2.2.4"), + patch("goal.push.core.run_test_stage", side_effect=fast_tests), + ): + core.execute_push_workflow( + ctx_obj=ok_ctx, + bump="patch", + no_tag=False, + no_changelog=False, + no_version_sync=False, + message=None, + dry_run=False, + yes=True, + markdown=False, + split=False, + ticket="ticket-230", + abstraction=None, + todo=False, + ) + + assert ok_ctx["_preflight_time"] == pytest.approx(180.0) + assert ok_ctx["_elapsed_time"] == pytest.approx(186.0) +