ACTIVITY-WP-0043: owner-return pickup adapter (STATE-WP-0094-T03 owner side)
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Build and Publish Container Image / build-and-push (push) Successful in 1m28s

Disabled reviewed definition, pinned profile/grant, server-side unique
submission adapter with receipts, and opt-in activation plan. No schedule,
deployment or dispatch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 671493@bnt-lap001
Assistant-Session: 60d7b11f-78f9-4731-9367-918511b2762d
This commit is contained in:
tegwick 2026-10-04 12:42:43 +02:00
parent ab10835a0c
commit 5a9012453c
5 changed files with 541 additions and 0 deletions

View file

@ -0,0 +1,43 @@
---
id: owner-return-pickup
name: State Hub Owner-Return Pickup
enabled: false
owner: activity-core
governance: custodian
status: proposed
trigger:
type: scheduled
at: "2099-01-01T00:00:00Z"
timezone: UTC
---
# State Hub Owner-Return Pickup
Reviewed source definition for ACTIVITY-WP-0043 (owner side of State Hub
STATE-WP-0094-T03). **Not activated.** `enabled: false` prevents any Temporal
schedule; the far-future timestamp is only a valid definition shape. The rule
condition is `False`, so even a forced run emits nothing.
Owner-return ops runs are inserted only by the submission adapter
`activity_core.owner_return_pickup.submit_owner_returns`, from candidates
produced by State Hub's offline planner (`scripts/owner_return_handoff.py`).
The adapter refuses unless `ACTIVITY_CORE_OWNER_RETURN_PICKUP_ENABLED` is set by
a founder-reviewed activation (`docs/owner-return-pickup-activation-plan.md`).
Pinned identities (also asserted by `tests/test_owner_return_pickup.py`):
- Definition UUID: `uuid5(uuid5(NAMESPACE_URL, "activity-core:activity-definition"), "owner-return-pickup")`
- Harness profile: `harness.agent-dev-local@1.0.0`
- Repository grant v1: `allowed_paths: [workplans]`, `commit_count 1..1`, `publish: false`
- Idempotency key: `<definition-uuid>:<repo>/<task_record_id>:owner-return:<sha256([repo,task,message])>`
```rule
id: owner-return-pickup-placeholder
condition: "False"
action:
task_template: Review owner return (adapter-only; never rule-emitted)
description: Owner-return work is inserted by the submission adapter, not by this rule.
priority: low
labels: ["automated", "owner-return"]
harness_profile_ref: harness.agent-dev-local@1.0.0
```

View file

@ -0,0 +1,60 @@
# Owner-Return Pickup — Activation Plan (opt-in, founder-reviewed)
Status: **proposed, not executed.** Workplan ACTIVITY-WP-0043; State Hub
STATE-WP-0094-T03. Nothing below has been applied.
## What exists at source level
| Item | Identity |
| --- | --- |
| ActivityDefinition | `activity-definitions/owner-return-pickup.md`, id `owner-return-pickup`, `enabled: false`, rule condition `False` |
| Definition UUID | `uuid5(uuid5(NAMESPACE_URL,"activity-core:activity-definition"),"owner-return-pickup")` (`owner_return_pickup.ACTIVITY_DEFINITION_ID`) |
| Harness profile | `harness.agent-dev-local@1.0.0` |
| Repository grant | v1, `allowed_paths: [workplans]`, commits 1..1, `publish: false` |
| Adapter | `activity_core.owner_return_pickup.submit_owner_returns(session, candidates, collection_complete=...)` |
| Gate | env `ACTIVITY_CORE_OWNER_RETURN_PICKUP_ENABLED` (unset = refuse before any write) |
## Preconditions (all required before activation)
1. State Hub C-25 message collection paginated to completion per awaited agent
(current cap: 200 replies). Owned by state-hub; ACTIVITY-WP-0043-T03.
2. A caller that builds `current_tasks` from fresh authoritative source bound to
the scanned repository revision, resolving every predecessor and decision
(contract: "Current eligibility guard"). No manufactured empty predecessor lists.
3. The caller runs inside the existing supported runner (Custodian dependency
monitor / established Activity Core worker); no new schedule is introduced.
4. Founder review of this plan and of the definition/grant above.
## Exactly what would be enabled
1. Set `ACTIVITY_CORE_OWNER_RETURN_PICKUP_ENABLED=true` on the single process
that invokes the planner-to-adapter handoff (no other component reads it).
2. Leave `owner-return-pickup.md` at `enabled: false`; the definition row is
synced only so the `activity_definition_id` foreign identity exists.
3. First run: one repository, operator-triggered, with the planner output saved
alongside the adapter receipts.
No Helm values, CronJob, Temporal schedule or credential changes are part of
this plan; any such change needs its own reviewed workplan.
## Evidence to capture (per run)
- Planner input: checker report revision, `observed_on`, `current_tasks` source
revision, and pagination completeness proof (reply counts per agent, last page).
- Planner output candidates (dispositions incl. `stopped` with reasons).
- Adapter receipts: `triggering_event_id`, `idempotency_key`, `status`
(`inserted`/`duplicate`/`skipped`), `ops_run_id`, `state`.
- Re-run of the same input proving every prior `inserted` becomes `duplicate`
and no new rows appear (server-side uniqueness).
- Ops-run close evidence via existing exact-repeat terminal close.
Store under `docs/evidence/ACTIVITY-WP-0043-*.md` and post a State Hub progress event.
## Rollback
1. Unset `ACTIVITY_CORE_OWNER_RETURN_PICKUP_ENABLED` — the adapter refuses
immediately, before any write.
2. Open/claimed owner-return rows (`source_type = state_hub_owner_return`) are
failed/closed through the normal ops-run close path; never deleted, so the
unique keys keep blocking re-insertion of the same messages.
3. No workplan field is changed by the pickup itself (review preparation only),
so there is no repository state to revert.

View file

@ -0,0 +1,195 @@
"""Owner-return pickup submission/receipt adapter (ACTIVITY-WP-0043).
Owner side of State Hub STATE-WP-0094-T03. State Hub's offline planner
(``state-hub/scripts/owner_return_handoff.py``) turns C-25 owner-return
findings plus authoritative current-task eligibility into candidates keyed by
``triggering_event_id = "owner-return:" + sha256([repo, task, message])``.
This module accepts those candidates and inserts them through the existing
ops-run queue (``create_ops_run_from_spec``). Deduplication is *server-side*:
the ``ops_runs.idempotency_key`` unique index with ``ON CONFLICT DO NOTHING``
decides, never a list snapshot taken earlier. Lease, retry and exact-repeat
terminal close semantics are those of ``ops_run_queue`` unchanged.
Nothing here schedules, dispatches or enables anything. Submission refuses
unless ``ACTIVITY_CORE_OWNER_RETURN_PICKUP_ENABLED`` is set (founder-reviewed
activation, see ``docs/owner-return-pickup-activation-plan.md``) and the caller
asserts that message collection was paginated to completion.
"""
from __future__ import annotations
import hashlib
import json
import os
import re
import uuid
from dataclasses import dataclass
from typing import Any, Mapping, Sequence
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from activity_core.ops_run_queue import (
build_idempotency_key,
create_ops_run_from_spec,
ops_run_queue_enabled,
)
from activity_core.orm import OpsRun
from activity_core.repository_grant import validate_repository_grant
from activity_core.rules.models import TaskSpec
DEFINITION_SLUG = "owner-return-pickup"
# Same derivation as sync_activity_definitions._definition_uuid (uuid5 over the
# file-authored slug); kept literal here so the identity is reviewable.
ACTIVITY_DEFINITION_ID_NAMESPACE = uuid.uuid5(
uuid.NAMESPACE_URL, "activity-core:activity-definition"
)
ACTIVITY_DEFINITION_ID = str(
uuid.uuid5(ACTIVITY_DEFINITION_ID_NAMESPACE, DEFINITION_SLUG)
)
HARNESS_PROFILE_REF = "harness.agent-dev-local@1.0.0"
REPOSITORY_GRANT: dict[str, Any] = validate_repository_grant(
{
"version": "1",
"allowed_paths": ["workplans"],
"commit_count": {"min": 1, "max": 1},
"publish": False,
}
)
SOURCE_TYPE = "state_hub_owner_return"
ENABLE_ENV = "ACTIVITY_CORE_OWNER_RETURN_PICKUP_ENABLED"
_REPO = re.compile(r"^[a-z0-9][a-z0-9._-]{0,127}$")
_TASK = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$")
_EVENT = re.compile(r"^owner-return:[0-9a-f]{64}$")
class OwnerReturnError(ValueError):
"""A candidate or submission precondition is invalid."""
def pickup_enabled() -> bool:
return os.getenv(ENABLE_ENV, "").strip().lower() in {"1", "true", "yes", "on"}
def owner_return_event_id(repo: str, task: str, message_id: str) -> str:
"""Recompute State Hub's planner event identity (must stay byte-identical)."""
encoded = json.dumps([repo, task, message_id], separators=(",", ":")).encode()
return "owner-return:" + hashlib.sha256(encoded).hexdigest()
@dataclass(frozen=True)
class OwnerReturnBinding:
"""Validated State Hub event/source binding for one owner-return message."""
target_repo: str
task_record_id: str
message_id: str
triggering_event_id: str
@classmethod
def from_candidate(cls, candidate: Mapping[str, Any]) -> OwnerReturnBinding:
repo = candidate.get("target_repo")
task = candidate.get("task_record_id")
if not isinstance(repo, str) or not _REPO.fullmatch(repo):
raise OwnerReturnError("invalid target_repo")
if not isinstance(task, str) or not _TASK.fullmatch(task):
raise OwnerReturnError("invalid task_record_id")
try:
message = str(uuid.UUID(str(candidate.get("message_id"))))
definition = str(uuid.UUID(str(candidate.get("activity_definition_id"))))
except ValueError as exc:
raise OwnerReturnError("invalid message or definition identity") from exc
if definition != ACTIVITY_DEFINITION_ID:
raise OwnerReturnError("candidate planned for a different definition")
event = candidate.get("triggering_event_id")
if not isinstance(event, str) or not _EVENT.fullmatch(event):
raise OwnerReturnError("invalid triggering_event_id")
if event != owner_return_event_id(repo, task, message):
raise OwnerReturnError("triggering_event_id does not bind repo/task/message")
return cls(repo, task, message, event)
def task_spec(self) -> TaskSpec:
return TaskSpec(
title=f"Owner return: review {self.task_record_id} in {self.target_repo}",
description=(
f"An owner reply (State Hub message {self.message_id}) answers the "
f"wait on {self.task_record_id}. Re-read the message and the current "
"workplan task; prepare a review only. Do not clear the wait marker, "
"task status, human gate or any other field without owner review."
),
target_repo=self.target_repo,
priority="medium",
labels=["automated", "owner-return"],
source_type=SOURCE_TYPE,
source_id=f"{self.target_repo}/{self.task_record_id}",
triggering_event_id=self.triggering_event_id,
activity_definition_id=ACTIVITY_DEFINITION_ID,
)
async def submit_owner_returns(
session: AsyncSession,
candidates: Sequence[Mapping[str, Any]],
*,
collection_complete: bool,
) -> list[dict[str, Any]]:
"""Insert reviewable candidates; return one receipt per candidate.
Receipt ``status``: ``inserted`` (new row), ``duplicate`` (unique key already
held; existing row reported), ``skipped`` (planner disposition not
``reviewable`` or already correlated). Raises before any write when the
pickup is not enabled, the queue is disabled, collection was not paginated
to completion, or any candidate fails binding validation.
"""
if not pickup_enabled():
raise OwnerReturnError(f"{ENABLE_ENV} is not enabled")
if not ops_run_queue_enabled():
raise OwnerReturnError("ops-run queue is disabled")
if collection_complete is not True:
raise OwnerReturnError(
"message collection must be paginated to completion (C-25 caps at 200)"
)
bound = [(c, OwnerReturnBinding.from_candidate(c)) for c in candidates]
receipts: list[dict[str, Any]] = []
for candidate, binding in bound:
spec = binding.task_spec()
key = build_idempotency_key(spec)
receipt: dict[str, Any] = {
"triggering_event_id": binding.triggering_event_id,
"idempotency_key": key,
"target_repo": binding.target_repo,
"task_record_id": binding.task_record_id,
"message_id": binding.message_id,
"ops_run_id": None,
"state": None,
}
if candidate.get("disposition") != "reviewable" or candidate.get("ops_run_id"):
receipts.append({**receipt, "status": "skipped",
"disposition": candidate.get("disposition")})
continue
row_id = await create_ops_run_from_spec(
session,
spec,
harness_profile_ref=HARNESS_PROFILE_REF,
execution_refs={
"correlation_id": binding.triggering_event_id,
"assignment_ref": binding.task_record_id,
},
repository_grant=REPOSITORY_GRANT,
)
if row_id is not None:
receipts.append({**receipt, "status": "inserted",
"ops_run_id": str(row_id), "state": "open"})
continue
existing = (
await session.execute(
select(OpsRun.id, OpsRun.state).where(OpsRun.idempotency_key == key)
)
).first()
if existing is None:
raise OwnerReturnError(f"insert neither created nor found {key}")
receipts.append({**receipt, "status": "duplicate",
"ops_run_id": str(existing[0]), "state": existing[1]})
return receipts

View file

@ -0,0 +1,159 @@
"""Tests for the owner-return pickup adapter (ACTIVITY-WP-0043)."""
from __future__ import annotations
import uuid
from pathlib import Path
from unittest.mock import AsyncMock, MagicMock
import pytest
from activity_core import owner_return_pickup as orp
from activity_core.definition_parser import parse_file
from activity_core.sync_activity_definitions import _definition_uuid
REPO, TASK = "ops-warden", "WARDEN-WP-0041-T03"
MSG = "11111111-2222-3333-4444-555555555555"
DEF_PATH = Path(__file__).parent.parent / "activity-definitions" / "owner-return-pickup.md"
def _candidate(**over):
c = {
"target_repo": REPO, "task_record_id": TASK, "message_id": MSG,
"activity_definition_id": orp.ACTIVITY_DEFINITION_ID,
"triggering_event_id": orp.owner_return_event_id(REPO, TASK, MSG),
"disposition": "reviewable", "stop_reason": None, "ops_run_id": None,
}
c.update(over)
return c
@pytest.fixture
def enabled(monkeypatch):
monkeypatch.setenv(orp.ENABLE_ENV, "true")
monkeypatch.delenv("OPS_RUN_QUEUE_ENABLED", raising=False)
def _session(insert_id, existing=None):
session = MagicMock()
ins = MagicMock(); ins.scalar_one_or_none.return_value = insert_id
sel = MagicMock(); sel.first.return_value = existing
session.execute = AsyncMock(side_effect=[ins, sel])
return session
def test_identity_matches_sync_derivation():
assert orp.ACTIVITY_DEFINITION_ID == str(_definition_uuid("owner-return-pickup"))
def test_definition_source_is_disabled_and_inert():
d = parse_file(DEF_PATH)
assert d.id == "owner-return-pickup"
assert d.enabled is False
text = DEF_PATH.read_text()
assert "enabled: false" in text and 'condition: "False"' in text
assert orp.HARNESS_PROFILE_REF in text
def test_event_id_matches_state_hub_planner_encoding():
# Reference value: json.dumps([repo, task, msg], separators=(",", ":")) sha256.
import hashlib, json
raw = json.dumps([REPO, TASK, MSG], separators=(",", ":")).encode()
assert orp.owner_return_event_id(REPO, TASK, MSG) == (
"owner-return:" + hashlib.sha256(raw).hexdigest()
)
def test_grant_is_local_only():
assert orp.REPOSITORY_GRANT["publish"] is False
assert orp.REPOSITORY_GRANT["allowed_paths"] == ["workplans"]
@pytest.mark.parametrize("over", [
{"triggering_event_id": "owner-return:" + "0" * 64},
{"message_id": "not-a-uuid"},
{"activity_definition_id": str(uuid.uuid4())},
{"target_repo": "Bad Repo"},
{"task_record_id": ""},
])
def test_binding_rejects_mismatch(over):
with pytest.raises(orp.OwnerReturnError):
orp.OwnerReturnBinding.from_candidate(_candidate(**over))
def test_spec_key_is_stable():
b = orp.OwnerReturnBinding.from_candidate(_candidate())
spec = b.task_spec()
assert spec.source_id == f"{REPO}/{TASK}"
assert spec.triggering_event_id == b.triggering_event_id
assert spec.activity_definition_id == orp.ACTIVITY_DEFINITION_ID
async def test_refuses_when_not_enabled(monkeypatch):
monkeypatch.delenv(orp.ENABLE_ENV, raising=False)
session = _session(None)
with pytest.raises(orp.OwnerReturnError):
await orp.submit_owner_returns(session, [_candidate()], collection_complete=True)
session.execute.assert_not_called()
async def test_refuses_when_queue_disabled(enabled, monkeypatch):
monkeypatch.setenv("OPS_RUN_QUEUE_ENABLED", "false")
with pytest.raises(orp.OwnerReturnError):
await orp.submit_owner_returns(_session(None), [_candidate()], collection_complete=True)
async def test_refuses_incomplete_collection(enabled):
session = _session(None)
with pytest.raises(orp.OwnerReturnError, match="paginated"):
await orp.submit_owner_returns(session, [_candidate()], collection_complete=False)
session.execute.assert_not_called()
async def test_invalid_candidate_aborts_before_any_write(enabled):
session = _session(None)
bad = _candidate(triggering_event_id="owner-return:" + "f" * 64)
with pytest.raises(orp.OwnerReturnError):
await orp.submit_owner_returns(session, [_candidate(), bad], collection_complete=True)
session.execute.assert_not_called()
async def test_inserted_receipt(enabled):
rid = uuid.uuid4()
session = _session(rid)
[r] = await orp.submit_owner_returns(session, [_candidate()], collection_complete=True)
assert r["status"] == "inserted" and r["ops_run_id"] == str(rid)
assert r["idempotency_key"] == (
f"{orp.ACTIVITY_DEFINITION_ID}:{REPO}/{TASK}:{orp.owner_return_event_id(REPO, TASK, MSG)}"
)
stmt = session.execute.await_args_list[0].args[0]
from sqlalchemy.dialects import postgresql
sql = str(stmt.compile(dialect=postgresql.dialect())).upper()
assert "ON CONFLICT (IDEMPOTENCY_KEY) DO NOTHING" in sql
assert session.execute.await_count == 1
async def test_duplicate_reports_existing_row(enabled):
existing_id = uuid.uuid4()
session = _session(None, existing=(existing_id, "succeeded"))
[r] = await orp.submit_owner_returns(session, [_candidate()], collection_complete=True)
assert r["status"] == "duplicate"
assert r["ops_run_id"] == str(existing_id) and r["state"] == "succeeded"
async def test_duplicate_without_row_raises(enabled):
with pytest.raises(orp.OwnerReturnError):
await orp.submit_owner_returns(_session(None, existing=None), [_candidate()],
collection_complete=True)
@pytest.mark.parametrize("over", [
{"disposition": "stopped", "stop_reason": "needs_human"},
{"disposition": "pending", "ops_run_id": str(uuid.uuid4())},
{"disposition": "completed"},
])
async def test_non_reviewable_is_skipped_without_write(enabled, over):
session = _session(None)
[r] = await orp.submit_owner_returns(session, [_candidate(**over)], collection_complete=True)
assert r["status"] == "skipped"
session.execute.assert_not_called()

View file

@ -0,0 +1,84 @@
---
id: ACTIVITY-WP-0043
type: workplan
title: "Owner-return pickup adapter for State Hub STATE-WP-0094-T03"
domain: infotech
repo: activity-core
status: active
owner: claude-activity-core
topic_slug: activity-core
created: "2026-10-04"
updated: "2026-10-04"
---
Origin: State Hub STATE-WP-0094-T03 ("Integrate deduplicated owner wakeups with
the supported runner"). Contract: `state-hub/docs/owner-return-handoff.md`
sections "Remaining T03 integration" and "Current eligibility guard". State Hub
already ships the offline planner `scripts/owner_return_handoff.py`; this plan
is the Activity Core owner half. Source level only: no schedule, deployment,
Helm change, credential handling or real ops-run dispatch.
Pinned identities:
- ActivityDefinition `owner-return-pickup` (UUID = uuid5 of the slug under the
`activity-core:activity-definition` namespace, as `sync_activity_definitions`)
- Harness profile `harness.agent-dev-local@1.0.0`
- Repository grant v1: `allowed_paths: [workplans]`, commits 1..1, `publish: false`
- Event binding `owner-return:sha256([repo, task_record_id, message_id])`,
recomputed and verified on intake; ops-run source_id `<repo>/<task_record_id>`
## Reviewed source definition (not activated)
```task
id: ACTIVITY-WP-0043-T01
status: done
priority: medium
```
`activity-definitions/owner-return-pickup.md`: `enabled: false`, far-future
scheduled shape, rule condition `False`. Pins profile and documents identities.
## Submission/receipt adapter with server-side uniqueness
```task
id: ACTIVITY-WP-0043-T02
status: done
priority: high
```
`src/activity_core/owner_return_pickup.py`: validates planner candidates
(repo/task/message identity, definition UUID, event hash), inserts only
`reviewable` uncorrelated candidates through `create_ops_run_from_spec`
(`ON CONFLICT (idempotency_key) DO NOTHING`), and on conflict reports the
existing row server-side. Lease, retry and exact-repeat terminal close remain
`ops_run_queue` semantics. Refuses before any write unless
`ACTIVITY_CORE_OWNER_RETURN_PICKUP_ENABLED` is set, the queue is enabled, the
caller asserts paginated collection, and every candidate binds. Tests:
`tests/test_owner_return_pickup.py` (20 passed; full suite 696 passed, 1 skipped).
## Paginated message collection (dependency on State Hub)
```task
id: ACTIVITY-WP-0043-T03
status: wait
priority: high
needs_human: false
blocking_reason: "State Hub C-25 fetches at most 200 replies per counterpart; pagination belongs in state-hub (STATE-WP-0094-T03)"
```
The adapter requires `collection_complete=True`. The caller (State Hub C-25 /
planner collection) must page through all replies per awaited agent before a
run may claim complete coverage. Not edited here; dependency on state-hub.
## Founder-reviewed activation
```task
id: ACTIVITY-WP-0043-T04
status: wait
priority: medium
needs_human: true
blocking_reason: "Activation is a separately reviewed founder action"
```
Plan: `docs/owner-return-pickup-activation-plan.md`. Requires T03 and an
eligibility-source binding (fresh authoritative `current_tasks`).