|
|
@@ -0,0 +1,187 @@
|
|
|
+"""Bounded, non-authoritative observation projections for Host HTTP APIs."""
|
|
|
+
|
|
|
+from __future__ import annotations
|
|
|
+
|
|
|
+from typing import Any
|
|
|
+
|
|
|
+
|
|
|
+def mission_snapshot_view(ledger: Any) -> dict[str, Any]:
|
|
|
+ if not hasattr(ledger, "revision"):
|
|
|
+ raw = ledger.to_dict() if hasattr(ledger, "to_dict") else {}
|
|
|
+ return {
|
|
|
+ key: raw[key]
|
|
|
+ for key in (
|
|
|
+ "schema_version",
|
|
|
+ "revision",
|
|
|
+ "root_trace_id",
|
|
|
+ "root_task_id",
|
|
|
+ "root_objective",
|
|
|
+ "focused_task_id",
|
|
|
+ )
|
|
|
+ if key in raw
|
|
|
+ }
|
|
|
+ return {
|
|
|
+ "schema_version": 1,
|
|
|
+ "revision": int(ledger.revision),
|
|
|
+ "root_trace_id": str(ledger.root_trace_id),
|
|
|
+ "root_task_id": ledger.root_task_id,
|
|
|
+ "root_objective": _text(ledger.root_objective, 2_000),
|
|
|
+ "focused_task_id": ledger.focused_task_id,
|
|
|
+ "tasks": [task_detail_view(item) for item in ledger.tasks.values()],
|
|
|
+ "attempts": [_attempt_view(item) for item in ledger.attempts.values()],
|
|
|
+ "validations": [_validation_view(item) for item in ledger.validations.values()],
|
|
|
+ "decisions": [_decision_view(item) for item in ledger.decisions.values()],
|
|
|
+ "operations": [_operation_view(item) for item in ledger.operations.values()],
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+def task_detail_view(task: Any) -> dict[str, Any]:
|
|
|
+ current = task.current_spec
|
|
|
+ return {
|
|
|
+ "task_id": task.task_id,
|
|
|
+ "parent_task_id": task.parent_task_id,
|
|
|
+ "display_path": task.display_path,
|
|
|
+ "status": _enum(task.status),
|
|
|
+ "current_spec": {
|
|
|
+ "version": current.version,
|
|
|
+ "objective": _text(current.objective, 2_000),
|
|
|
+ "acceptance_criteria": [
|
|
|
+ {
|
|
|
+ "criterion_id": _text(value.criterion_id, 128),
|
|
|
+ "description": _text(value.description, 500),
|
|
|
+ "hard": bool(value.hard),
|
|
|
+ }
|
|
|
+ for value in current.acceptance_criteria
|
|
|
+ ],
|
|
|
+ "context_refs": [_safe_ref(value) for value in current.context_refs],
|
|
|
+ },
|
|
|
+ "child_task_ids": list(task.child_task_ids),
|
|
|
+ "attempt_ids": list(task.attempt_ids),
|
|
|
+ "validation_ids": list(task.validation_ids),
|
|
|
+ "decision_ids": list(task.decision_ids),
|
|
|
+ "blocked_reason": _optional_text(task.blocked_reason, 256),
|
|
|
+ "superseded_by": task.superseded_by,
|
|
|
+ "created_at": str(task.created_at),
|
|
|
+ "updated_at": str(task.updated_at),
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+def _attempt_view(value: Any) -> dict[str, Any]:
|
|
|
+ submission = value.submission
|
|
|
+ return {
|
|
|
+ "attempt_id": value.attempt_id,
|
|
|
+ "task_id": value.task_id,
|
|
|
+ "spec_version": value.spec_version,
|
|
|
+ "worker_trace_id": value.worker_trace_id,
|
|
|
+ "worker_preset": value.worker_preset,
|
|
|
+ "status": _enum(value.status),
|
|
|
+ "operation_id": value.operation_id,
|
|
|
+ "snapshot_id": value.snapshot_id,
|
|
|
+ "accepted_child_decision_ids": (
|
|
|
+ list(value.accepted_child_decision_ids)
|
|
|
+ if value.accepted_child_decision_ids is not None
|
|
|
+ else None
|
|
|
+ ),
|
|
|
+ "submission": (
|
|
|
+ {
|
|
|
+ "summary": _text(submission.summary, 500),
|
|
|
+ "artifact_refs": [_artifact_ref(item) for item in submission.artifact_refs],
|
|
|
+ "evidence_refs": [_artifact_ref(item) for item in submission.evidence_refs],
|
|
|
+ }
|
|
|
+ if submission is not None
|
|
|
+ else None
|
|
|
+ ),
|
|
|
+ "error": _optional_text(value.error, 500),
|
|
|
+ "created_at": str(value.created_at),
|
|
|
+ "updated_at": str(value.updated_at),
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+def _validation_view(value: Any) -> dict[str, Any]:
|
|
|
+ return {
|
|
|
+ "validation_id": value.validation_id,
|
|
|
+ "task_id": value.task_id,
|
|
|
+ "attempt_id": value.attempt_id,
|
|
|
+ "spec_version": value.spec_version,
|
|
|
+ "snapshot_id": value.snapshot_id,
|
|
|
+ "validator_trace_id": value.validator_trace_id,
|
|
|
+ "validator_preset": value.validator_preset,
|
|
|
+ "status": _enum(value.status),
|
|
|
+ "verdict": _enum(value.verdict),
|
|
|
+ "criterion_results": [
|
|
|
+ {
|
|
|
+ "criterion_id": _text(item.criterion_id, 128),
|
|
|
+ "verdict": _enum(item.verdict),
|
|
|
+ "reason": _text(item.reason, 500),
|
|
|
+ "evidence_refs": [_artifact_ref(ref) for ref in item.evidence_refs],
|
|
|
+ }
|
|
|
+ for item in value.criterion_results
|
|
|
+ ],
|
|
|
+ "summary": _text(value.summary, 500),
|
|
|
+ "recommendation": _text(value.recommendation, 256),
|
|
|
+ "error": _optional_text(value.error, 500),
|
|
|
+ "created_at": str(value.created_at),
|
|
|
+ "updated_at": str(value.updated_at),
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+def _decision_view(value: Any) -> dict[str, Any]:
|
|
|
+ return {
|
|
|
+ "decision_id": value.decision_id,
|
|
|
+ "task_id": value.task_id,
|
|
|
+ "action": _enum(value.action),
|
|
|
+ "reason": _text(value.reason, 500),
|
|
|
+ "from_status": _enum(value.from_status),
|
|
|
+ "to_status": _enum(value.to_status),
|
|
|
+ "attempt_id": value.attempt_id,
|
|
|
+ "validation_id": value.validation_id,
|
|
|
+ # The raw payload may contain revised contracts, protected references,
|
|
|
+ # or model text. The action/identities are the observation contract.
|
|
|
+ "created_at": str(value.created_at),
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+def _operation_view(value: Any) -> dict[str, Any]:
|
|
|
+ return {
|
|
|
+ "operation_id": value.operation_id,
|
|
|
+ "kind": _enum(value.kind),
|
|
|
+ "status": _enum(value.status),
|
|
|
+ "task_ids": list(value.task_ids),
|
|
|
+ "attempt_ids": list(value.attempt_ids),
|
|
|
+ "validation_ids": list(value.validation_ids),
|
|
|
+ "deadline_at": str(value.deadline_at) if value.deadline_at else None,
|
|
|
+ "error": _optional_text(value.error, 500),
|
|
|
+ "created_at": str(value.created_at),
|
|
|
+ "updated_at": str(value.updated_at),
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+def _artifact_ref(value: Any) -> dict[str, Any]:
|
|
|
+ return {
|
|
|
+ "uri": _safe_ref(value.uri),
|
|
|
+ "kind": _text(value.kind, 64),
|
|
|
+ "version": _optional_text(value.version, 128),
|
|
|
+ "digest": _optional_text(value.digest, 80),
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+def _safe_ref(value: Any) -> str:
|
|
|
+ text = _text(value, 500)
|
|
|
+ return text if text.startswith("script-build://") else "[internal-ref]"
|
|
|
+
|
|
|
+
|
|
|
+def _optional_text(value: Any, limit: int) -> str | None:
|
|
|
+ return None if value is None else _text(value, limit)
|
|
|
+
|
|
|
+
|
|
|
+def _text(value: Any, limit: int) -> str:
|
|
|
+ return " ".join(str(value).split())[:limit]
|
|
|
+
|
|
|
+
|
|
|
+def _enum(value: Any) -> str | None:
|
|
|
+ if value is None:
|
|
|
+ return None
|
|
|
+ return str(getattr(value, "value", value))
|
|
|
+
|
|
|
+
|
|
|
+__all__ = ["mission_snapshot_view", "task_detail_view"]
|