ACTIVITY-WP-0043: owner-return pickup adapter (STATE-WP-0094-T03 owner side)
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:
parent
ab10835a0c
commit
5a9012453c
5 changed files with 541 additions and 0 deletions
43
activity-definitions/owner-return-pickup.md
Normal file
43
activity-definitions/owner-return-pickup.md
Normal 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
|
||||
```
|
||||
60
docs/owner-return-pickup-activation-plan.md
Normal file
60
docs/owner-return-pickup-activation-plan.md
Normal 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.
|
||||
195
src/activity_core/owner_return_pickup.py
Normal file
195
src/activity_core/owner_return_pickup.py
Normal 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
|
||||
159
tests/test_owner_return_pickup.py
Normal file
159
tests/test_owner_return_pickup.py
Normal 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()
|
||||
84
workplans/ACTIVITY-WP-0043-state-hub-owner-return-pickup.md
Normal file
84
workplans/ACTIVITY-WP-0043-state-hub-owner-return-pickup.md
Normal 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`).
|
||||
Loading…
Add table
Add a link
Reference in a new issue