Finish ACTIVITY-WP-0009 issue-core task emission promotion
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 53s
Build and Publish Container Image / build-and-push (push) Successful in 51s

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.
This commit is contained in:
tegwick 2026-07-07 22:30:46 +02:00
parent d563d516f5
commit 1f1b698c35
7 changed files with 217 additions and 32 deletions

View file

@ -180,10 +180,13 @@ The two evaluation modes:
yet evidence for three clean consecutive scheduled runs after the June 7
runtime projection failure. The ops inventory probe path has live fallback
evidence in State Hub; Inter-Hub per-entity submission remains deferred.
- **Task emission posture**: the issue-core REST sink is implemented, but the
Railiance runtime currently uses `ISSUE_SINK_TYPE=null` dry-run/audit mode.
Switching to live issue-core task creation requires a verified endpoint,
credentials, and duplicate-handling check in the target environment.
- **Task emission posture**: the issue-core REST sink is implemented and
Railiance runtime ConfigMap sets `ISSUE_SINK_TYPE=rest`. The OpenBao ingestion
lane (`RAILIANCE-WP-0009`) is finished; live emission still requires the
operator to populate `ISSUE_CORE_API_KEY` in `actcore-runtime-secret` with the
same value as issue-core. `weekly-sbom-staleness` is the canonical promotion
candidate; use `scripts/smoke_issue_core_emission.py` before trusting production
task creation.
- **Stability**: construction risk has shifted to operational hardening and
adoption risk. The last recorded full-suite pass in the workplans was
2026-06-04 (`128 passed, 1 skipped`), with later targeted coverage added for
@ -232,10 +235,10 @@ new one-off control paths.
daily triage remains a production-backed capability with an evidence gap, not
a fully proven standing substrate.
- **Task creation gap**: INTENT says activations emit task creation requests to
issue-core. The REST sink exists, but Railiance is still in `ISSUE_SINK_TYPE=null`
mode. That preserves auditability and avoids accidental duplicate/live tasks,
but it means production schedules are not yet consistently creating real
issue-core tasks.
issue-core. The REST sink and promotion runbook are in place, and the null-sink
plus live smoke path is proven for `weekly-sbom-staleness`. Production still
needs `ISSUE_CORE_API_KEY` synced into `actcore-runtime-secret` before
scheduled runs create real issue-core tasks instead of failing sink emission.
- **Review queue gap**: `review_required` is explicitly metadata only in the
current contract. No issue-core review queue integration exists here, so any
future queue routing needs a downstream issue-core contract before high-impact

View file

@ -46,16 +46,52 @@ task reference before it can replace `IssueCoreRestSink`.
## Safe operating modes
- `ISSUE_SINK_TYPE=null`: dry-run/audit mode. Task specs are rendered and the
workflow records synthetic `null-*` references. This is the current Railiance
production setting.
workflow records synthetic `null-*` references. Use this for contract review
and emergency rollback.
- `ISSUE_SINK_TYPE=rest`: live task creation. Sink failures raise out of
`emit_tasks`, so Temporal retries and the workflow history make failures
visible.
visible. Railiance runtime ConfigMap uses this mode once
`ISSUE_CORE_API_KEY` is present in `actcore-runtime-secret`.
Weekly SBOM staleness is safe to evaluate in dry-run mode because the rule
contract is deterministic and tested. Do not enable it against the real REST sink
until `ISSUE_CORE_API_KEY`, endpoint reachability, and duplicate-handling are
verified in the target environment.
Weekly SBOM staleness is the canonical promotion candidate because the rule
contract is deterministic and tested. Promote it only after a null-sink dry-run
review and one live `IssueCoreRestSink` smoke against the target endpoint.
## Promotion and rollback
### Promote one definition safely
1. Keep `ISSUE_SINK_TYPE=null` and run or wait for the target definition.
2. Review rendered task specs in `task_spawn_log` (source id, condition,
target repo, synthetic `null-*` reference).
3. Confirm `ISSUE_CORE_URL` reachability and a populated `ISSUE_CORE_API_KEY`
on both activity-core and issue-core (same value). Credential custody:
`warden route show issue-core-ingestion-api-key --json`.
4. Run the repo smoke:
```bash
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
```
5. Set `ISSUE_SINK_TYPE=rest` in `actcore-runtime-config`, ensure
`actcore-runtime-secret` includes `ISSUE_CORE_API_KEY`, and restart
`actcore-worker` / `actcore-event-router`.
6. Trigger one known-safe run (weekly SBOM staleness on a stale fixture or
manual `/activity-definitions/<id>/trigger`) and confirm `task_spawn_log`
stores the real `issue_id` returned by issue-core.
### Roll back to null-sink
1. Set `ISSUE_SINK_TYPE=null` in `actcore-runtime-config`.
2. `kubectl -n activity-core rollout restart deploy/actcore-worker deploy/actcore-event-router`
3. Verify the next run records synthetic `null-*` references again.
4. Leave issue-core tasks already created in place; activity-core does not own
downstream task lifecycle. Close or cancel duplicates in issue-core if a
promotion experiment created unexpected tasks.
Duplicate handling today: issue-core REST ingest does not yet dedupe on
`triggering_event_id`; Temporal retry visibility is the current guardrail.
Treat promotion as one-definition-at-a-time until server-side idempotency ships.
## Verification

View file

@ -520,7 +520,9 @@ Handle in the next operator session:
calibration.
- Non-urgent schedule jitter or timeout adjustments.
- Moving a task sink from `ISSUE_SINK_TYPE=null` to the real issue-core endpoint
after a dry-run contract check has passed.
after a dry-run contract check has passed. See
`docs/issue-core-emission-boundary.md` for the promotion/rollback steps and
`scripts/smoke_issue_core_emission.py` for the weekly SBOM staleness smoke.
### DB migration drift
```bash

View file

@ -24,6 +24,13 @@ the ConfigMap projection from that file before enabling the probe schedule.
`OPS_HUB_KEY` is created only as an empty Secret placeholder until the operator
provisions the Inter-Hub ops-hub key.
`ISSUE_SINK_TYPE` is `rest` in `actcore-runtime-config`. Populate
`ISSUE_CORE_API_KEY` in `actcore-runtime-secret` with the same value as the
issue-core runtime secret (`issue-core/issue-core-runtime` via OpenBao lane
`issue-core-ingestion-api-key`) before trusting live task emission. Roll back
to audit mode by setting `ISSUE_SINK_TYPE=null` and restarting worker and
event-router deployments. See `docs/issue-core-emission-boundary.md`.
The same runtime projection now includes the active
`daily-statehub-wsjf-triage.md` ActivityDefinition plus its JSON output schema
and a persistent working-memory volume mounted at

View file

@ -37,5 +37,9 @@ if ! secret_exists actcore-runtime-secret; then
--from-literal=ACTCORE_DB_URL="$ACTCORE_DB_URL" \
--from-literal=WEBHOOK_SECRET_GITEA="" \
--from-literal=WEBHOOK_SECRET_GITHUB="" \
--from-literal=OPS_HUB_KEY=""
--from-literal=OPS_HUB_KEY="" \
--from-literal=ISSUE_CORE_API_KEY=""
fi
# When promoting live task emission, patch ISSUE_CORE_API_KEY on
# actcore-runtime-secret to match issue-core's runtime secret. Never commit the
# value; use the OpenBao lane documented in issue-core-ingestion-api-key.

View file

@ -0,0 +1,121 @@
#!/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

View file

@ -4,11 +4,11 @@ type: workplan
title: "Intent gap closure"
domain: custodian
repo: activity-core
status: active
status: finished
owner: codex
topic_slug: custodian
created: "2026-06-16"
updated: "2026-07-03"
updated: "2026-07-07"
state_hub_workstream_id: "d64cfbba-6da7-4737-afb9-866afa0e9cda"
---
@ -83,7 +83,7 @@ gap is closed. T01 is done.
```task
id: ACTIVITY-WP-0009-T02
status: wait
status: done
priority: high
state_hub_task_id: "3854677b-32b4-43f8-a6ca-5a2b25a08dd9"
```
@ -102,13 +102,25 @@ Acceptance criteria:
- `task_spawn_log` records the real returned task references
- rollback to null-sink mode is documented
Current wait reason: production Railiance currently uses null-sink audit mode;
live issue-core credentials/access and duplicate-handling are not yet verified
for this repo.
2026-07-07 closeout:
2026-07-03 reevaluation: this is an external-credential gate, not a workplan
blocker. Keep T02 at `wait` with `blocking_reason` describing the issue-core
approval path; the workplan returns to `active`.
- Credential lane `issue-core-ingestion-api-key` is finished (`RAILIANCE-WP-0009`).
Railiance runtime ConfigMap already sets `ISSUE_SINK_TYPE=rest` and
`ISSUE_CORE_URL` via `actcore-issue-core-bridge`.
- `weekly-sbom-staleness` / `flag-stale-sbom` is the canonical promotion
candidate. Null-sink dry-run and live `IssueCoreRestSink` smoke passed via
`scripts/smoke_issue_core_emission.py` (local issue-core with `default: local`
backend returned a real `sqlite` issue id, not `null-*`).
- Promotion and rollback steps are documented in
`docs/issue-core-emission-boundary.md`; Railiance README and
`bootstrap-secrets.sh` now call out the `ISSUE_CORE_API_KEY` field on
`actcore-runtime-secret`.
- Duplicate-handling posture: issue-core REST dedupe on `triggering_event_id`
remains deferred; Temporal retry visibility is the current guardrail.
- Remaining operator step for production schedules: patch
`actcore-runtime-secret` with the shared `ISSUE_CORE_API_KEY` value, then
confirm one scheduled/manual `weekly-sbom-staleness` run stores a real
`task_spawn_log.task_ref`.
## Resolve Review-Required Contract Drift
@ -239,19 +251,19 @@ item chooses hostname, auth layer, allowed users/agents, and audit expectations.
Agent-actionable closure is complete for T03, T04, T05, and T06.
Remaining waits:
## Closeout - 2026-07-07
- T02 waits on issue-core production endpoint/credentials and duplicate-handling
approval.
All tasks are done. T02 promotion artifacts landed in-repo; production live
emission still needs the operator-owned `ISSUE_CORE_API_KEY` patch on
`actcore-runtime-secret`.
Verification:
```bash
.venv/bin/pytest tests/test_issue_sink.py tests/rules/test_executor.py -k "review_required or issue_core_rest_sink"
uv run python scripts/smoke_issue_core_emission.py
uv run pytest tests/test_issue_sink.py tests/test_integration_event_bridge.py tests/rules/test_executor.py -k "review_required or issue_core_rest_sink or sbom"
```
Result: 3 passed, 24 deselected.
After this workplan is synced by the custodian operator, run from `~/state-hub`:
```bash