Close the intent-gap workplan by documenting the weekly-sbom-staleness promotion path, adding a null-sink and live REST smoke script, and recording rollback steps for Railiance. Update SCOPE and deployment docs to reflect ISSUE_SINK_TYPE=rest and the remaining actcore-runtime-secret key patch.
121 lines
No EOL
4.1 KiB
Python
Executable file
121 lines
No EOL
4.1 KiB
Python
Executable file
#!/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 |