Add MAIN-to-RX acknowledgement channel
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from dataclasses import asdict, dataclass
|
||||
from dataclasses import asdict, dataclass, replace
|
||||
from datetime import datetime, timezone
|
||||
import json
|
||||
import os
|
||||
@@ -33,6 +33,14 @@ from tools.rf_handoff_channel import ( # noqa: E402
|
||||
load_result,
|
||||
result_digest,
|
||||
)
|
||||
from tools.tx_handoff_channel import ( # noqa: E402
|
||||
CHANNEL_COMPLETE_OBSERVED,
|
||||
CHANNEL_READY_OBSERVED,
|
||||
RX_READY_OBSERVED,
|
||||
TX_AUTHORIZATION_CREATED,
|
||||
GitAcknowledgementChannel,
|
||||
TxHandoffAcknowledgement,
|
||||
)
|
||||
|
||||
|
||||
DEFAULT_STATE_PATH = ROOT / ".local" / "tx_agent_watcher_state.json"
|
||||
@@ -67,6 +75,13 @@ class TxExecutor(Protocol):
|
||||
) -> Path: ...
|
||||
|
||||
|
||||
class AcknowledgementPublisher(Protocol):
|
||||
def publish(
|
||||
self,
|
||||
acknowledgement: TxHandoffAcknowledgement,
|
||||
) -> object: ...
|
||||
|
||||
|
||||
@dataclass
|
||||
class TxWatcherState:
|
||||
state_version: int = STATE_VERSION
|
||||
@@ -410,6 +425,45 @@ def migrate_state_cursor(
|
||||
return True
|
||||
|
||||
|
||||
def acknowledgement_for_result(
|
||||
result: RxHandoffResult,
|
||||
digest: str,
|
||||
result_head: str,
|
||||
status: str,
|
||||
*,
|
||||
observed_at: datetime,
|
||||
main_watcher_pid: int,
|
||||
) -> TxHandoffAcknowledgement | None:
|
||||
"""Build an acknowledgement only for an event handled successfully."""
|
||||
|
||||
acknowledgement_state: str | None = None
|
||||
rf_tx_authorized = False
|
||||
if status == "CHANNEL_TEST_READY":
|
||||
acknowledgement_state = CHANNEL_READY_OBSERVED
|
||||
elif status == "CHANNEL_TEST_COMPLETE":
|
||||
acknowledgement_state = CHANNEL_COMPLETE_OBSERVED
|
||||
elif status == "RED_APPROVAL_BLOCKED":
|
||||
acknowledgement_state = RX_READY_OBSERVED
|
||||
elif status == "TX_AUTHORIZATION_PREPARED":
|
||||
acknowledgement_state = TX_AUTHORIZATION_CREATED
|
||||
rf_tx_authorized = True
|
||||
if acknowledgement_state is None:
|
||||
return None
|
||||
return TxHandoffAcknowledgement(
|
||||
schema_version=1,
|
||||
task_id=result.task_id,
|
||||
state=acknowledgement_state,
|
||||
source_rx_commit=result_head,
|
||||
source_rx_digest=digest,
|
||||
source_head=result.source_head,
|
||||
observed_at=observed_at.astimezone(timezone.utc).isoformat(),
|
||||
main_watcher_pid=main_watcher_pid,
|
||||
rf_tx_authorized=rf_tx_authorized,
|
||||
rf_tx_started=False,
|
||||
rf_tx=0,
|
||||
)
|
||||
|
||||
|
||||
def process_history_cycle(
|
||||
source: ResultSource,
|
||||
state: TxWatcherState,
|
||||
@@ -423,6 +477,8 @@ def process_history_cycle(
|
||||
expected_source_head: str | None = None,
|
||||
max_age_seconds: float = 30.0,
|
||||
min_remaining_seconds: float = 10.0,
|
||||
acknowledgement_publisher: AcknowledgementPublisher | None = None,
|
||||
main_watcher_pid: int | None = None,
|
||||
) -> list[tuple[str, str]]:
|
||||
"""Process every new result commit in chronological order."""
|
||||
|
||||
@@ -432,11 +488,12 @@ def process_history_cycle(
|
||||
outcomes: list[tuple[str, str]] = []
|
||||
for commit in commits:
|
||||
result, digest, result_head = source.read_commit(commit)
|
||||
status, state = handle_result(
|
||||
candidate_state = replace(state)
|
||||
status, candidate_state = handle_result(
|
||||
result,
|
||||
digest,
|
||||
result_head,
|
||||
state,
|
||||
candidate_state,
|
||||
source,
|
||||
executor,
|
||||
now_function=now_function,
|
||||
@@ -447,6 +504,26 @@ def process_history_cycle(
|
||||
max_age_seconds=max_age_seconds,
|
||||
min_remaining_seconds=min_remaining_seconds,
|
||||
)
|
||||
acknowledgement = acknowledgement_for_result(
|
||||
result,
|
||||
digest,
|
||||
result_head,
|
||||
status,
|
||||
observed_at=now_function(),
|
||||
main_watcher_pid=main_watcher_pid or os.getpid(),
|
||||
)
|
||||
if acknowledgement is not None and acknowledgement_publisher is not None:
|
||||
publication = acknowledgement_publisher.publish(acknowledgement)
|
||||
output_function(
|
||||
"TX ACKNOWLEDGEMENT PUBLISH = "
|
||||
f"{getattr(publication, 'status', 'PUBLISHED')}"
|
||||
)
|
||||
output_function(
|
||||
"TX ACKNOWLEDGEMENT HEAD = "
|
||||
f"{getattr(publication, 'commit', 'UNKNOWN')}"
|
||||
)
|
||||
for field_name in TxWatcherState.__dataclass_fields__:
|
||||
setattr(state, field_name, getattr(candidate_state, field_name))
|
||||
state.last_seen_commit = result_head
|
||||
state.last_processed_rx_node_commit = result_head
|
||||
state.state_version = STATE_VERSION
|
||||
@@ -481,6 +558,7 @@ def run_watcher(arguments: argparse.Namespace) -> int:
|
||||
stop_path.unlink(missing_ok=True)
|
||||
|
||||
executor = ManualTxExecutor(arguments.outbox.resolve())
|
||||
acknowledgement_publisher: AcknowledgementPublisher | None = None
|
||||
|
||||
def input_function(prompt: str) -> str:
|
||||
if arguments.approval == "authorize":
|
||||
@@ -511,6 +589,11 @@ def run_watcher(arguments: argparse.Namespace) -> int:
|
||||
remote=arguments.remote,
|
||||
branch=arguments.result_branch,
|
||||
)
|
||||
acknowledgement_publisher = GitAcknowledgementChannel(
|
||||
repository,
|
||||
remote=arguments.remote,
|
||||
branch=arguments.acknowledgement_branch,
|
||||
)
|
||||
if not _HEAD_RE.fullmatch(expected_source_head):
|
||||
raise ValueError(
|
||||
"expected source HEAD must be a full lowercase Git hash"
|
||||
@@ -534,6 +617,8 @@ def run_watcher(arguments: argparse.Namespace) -> int:
|
||||
expected_source_head=expected_source_head,
|
||||
max_age_seconds=arguments.max_age,
|
||||
min_remaining_seconds=arguments.min_remaining,
|
||||
acknowledgement_publisher=acknowledgement_publisher,
|
||||
main_watcher_pid=os.getpid(),
|
||||
)
|
||||
if arguments.once or arguments.dry_run:
|
||||
break
|
||||
@@ -569,6 +654,7 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
parser.add_argument("--remote", default="origin")
|
||||
parser.add_argument("--source-branch", default="video-development")
|
||||
parser.add_argument("--result-branch", default="rx-node")
|
||||
parser.add_argument("--acknowledgement-branch", default="tx-node")
|
||||
parser.add_argument("--expected-source-head")
|
||||
parser.add_argument("--poll-interval", type=float, default=5.0)
|
||||
parser.add_argument("--max-age", type=float, default=30.0)
|
||||
|
||||
Reference in New Issue
Block a user