#!/usr/bin/env python3 """Smoke weekly-sbom-staleness task emission through NullSink and IssueCoreRestSink. Phase 1 always runs in dry-run mode and prints the rendered TaskSpec fields for operator review. Phase 2 (--live) POSTs the same spec to issue-core and asserts the returned task reference is not synthetic. Workstation live smoke requires issue-core's default backend to be `local`. A remote Gitea default backend will hang on ingest. See `~/ops-warden/wiki/playbooks/activity-core-issue-sink.md`. Example: uv run python scripts/smoke_issue_core_emission.py ISSUE_CORE_URL=http://127.0.0.1:8765 ISSUE_CORE_API_KEY=... \\ uv run python scripts/smoke_issue_core_emission.py --live """ from __future__ import annotations import argparse import os import sys import uuid from pathlib import Path from activity_core.definition_parser import parse_file from activity_core.issue_sink import IssueCoreRestSink, NullSink from activity_core.rules.actions import expand_rule_actions from activity_core.rules.models import TaskRef, TaskSpec _DEFINITION = ( Path(__file__).resolve().parent.parent / "activity-definitions" / "weekly-sbom-staleness.md" ) class _CronEvent: pass def _stale_repo_spec() -> dict: rule = parse_file(_DEFINITION).rules[0] specs = expand_rule_actions( [rule], _CronEvent(), {"repos": {"repos": [{"repo_slug": "activity-core", "sbom_age_days": 45}]}}, ) if len(specs) != 1: raise RuntimeError(f"expected one stale-repo spec, got {len(specs)}") return specs[0] def _task_spec(spec_dict: dict, *, trigger: str) -> TaskSpec: return TaskSpec( title=spec_dict["title"], description=spec_dict["description"], target_repo=spec_dict["target_repo"], priority=spec_dict["priority"], labels=spec_dict["labels"], due_in_days=spec_dict["due_in_days"], source_type="rule", source_id=spec_dict["source_id"], triggering_event_id=trigger, activity_definition_id="weekly-sbom-staleness", ) def run_null_sink(spec_dict: dict) -> TaskRef: trigger = str(uuid.uuid4()) spec = _task_spec(spec_dict, trigger=trigger) print("null-sink dry-run spec:") print(f" title={spec.title!r}") print(f" target_repo={spec.target_repo!r}") print(f" source_id={spec.source_id!r}") print(f" labels={spec.labels!r}") print(f" triggering_event_id={spec.triggering_event_id!r}") ref = NullSink().emit(spec) print(f"null-sink ref: backend={ref.backend} external_id={ref.external_id}") if ref.backend != "null" or not ref.external_id.startswith("null-"): raise RuntimeError("NullSink returned an unexpected reference shape") return ref def run_live_rest(spec_dict: dict) -> TaskRef: base_url = os.environ.get("ISSUE_CORE_URL", "http://127.0.0.1:8765").rstrip("/") api_key = os.environ.get("ISSUE_CORE_API_KEY", "").strip() if not api_key: raise RuntimeError("ISSUE_CORE_API_KEY is required for --live") trigger = str(uuid.uuid4()) spec = _task_spec(spec_dict, trigger=trigger) ref = IssueCoreRestSink(base_url=base_url, api_key=api_key).emit(spec) print(f"live rest ref: backend={ref.backend!r} external_id={ref.external_id!r}") if ref.backend == "null" or ref.external_id.startswith("null-"): raise RuntimeError("IssueCoreRestSink returned a synthetic reference") return ref def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument( "--live", action="store_true", help="POST the reviewed spec to issue-core after the null-sink dry-run.", ) args = parser.parse_args() spec_dict = _stale_repo_spec() run_null_sink(spec_dict) if args.live: run_live_rest(spec_dict) print("duplicate posture: issue-core server dedupe is deferred; Temporal retries surface in workflow history") print("smoke passed") return 0 if __name__ == "__main__": try: raise SystemExit(main()) except Exception as exc: print(f"smoke failed: {exc}", file=sys.stderr) raise SystemExit(1) from exc