@@ -58,6 +58,12 @@ class WorkflowTimelineEntryKind(StrEnum): |
| 58 | CLARIFY_EXIT = "clarify_exit" | 58 | CLARIFY_EXIT = "clarify_exit" |
| 59 | PLAN_REFRESH = "plan_refresh" | 59 | PLAN_REFRESH = "plan_refresh" |
| 60 | VERIFY_SKIP = "verify_skip" | 60 | VERIFY_SKIP = "verify_skip" |
| | 61 | + COMPLETION_CHECK = "completion_check" |
| | 62 | + COMPLETION_CONTINUE = "completion_continue" |
| | 63 | + COMPLETION_COMPLETE = "completion_complete" |
| | 64 | + COMPLETION_FINALIZE = "completion_finalize" |
| | 65 | + REPAIR_RETRY = "repair_retry" |
| | 66 | + REPAIR_FAIL = "repair_fail" |
| 61 | | 67 | |
| 62 | | 68 | |
| 63 | @dataclass(slots=True) | 69 | @dataclass(slots=True) |
@@ -291,6 +297,8 @@ class WorkflowTimelineEntry: |
| 291 | clarify_pressure_kind: str | None = None | 297 | clarify_pressure_kind: str | None = None |
| 292 | pressure_pass_complete: bool = False | 298 | pressure_pass_complete: bool = False |
| 293 | missing_readiness_gates: list[str] = field(default_factory=list) | 299 | missing_readiness_gates: list[str] = field(default_factory=list) |
| | 300 | + policy_stage: str | None = None |
| | 301 | + policy_outcome: str | None = None |
| 294 | prompt_format: str | None = None | 302 | prompt_format: str | None = None |
| 295 | prompt_sections: list[str] = field(default_factory=list) | 303 | prompt_sections: list[str] = field(default_factory=list) |
| 296 | artifact_paths: list[str] = field(default_factory=list) | 304 | artifact_paths: list[str] = field(default_factory=list) |
@@ -314,6 +322,8 @@ class WorkflowTimelineEntry: |
| 314 | "clarify_pressure_kind": self.clarify_pressure_kind, | 322 | "clarify_pressure_kind": self.clarify_pressure_kind, |
| 315 | "pressure_pass_complete": self.pressure_pass_complete, | 323 | "pressure_pass_complete": self.pressure_pass_complete, |
| 316 | "missing_readiness_gates": list(self.missing_readiness_gates), | 324 | "missing_readiness_gates": list(self.missing_readiness_gates), |
| | 325 | + "policy_stage": self.policy_stage, |
| | 326 | + "policy_outcome": self.policy_outcome, |
| 317 | "prompt_format": self.prompt_format, | 327 | "prompt_format": self.prompt_format, |
| 318 | "prompt_sections": list(self.prompt_sections), | 328 | "prompt_sections": list(self.prompt_sections), |
| 319 | "artifact_paths": list(self.artifact_paths), | 329 | "artifact_paths": list(self.artifact_paths), |
@@ -339,6 +349,8 @@ class WorkflowTimelineEntry: |
| 339 | clarify_pressure_kind=_optional_text(data.get("clarify_pressure_kind")), | 349 | clarify_pressure_kind=_optional_text(data.get("clarify_pressure_kind")), |
| 340 | pressure_pass_complete=bool(data.get("pressure_pass_complete", False)), | 350 | pressure_pass_complete=bool(data.get("pressure_pass_complete", False)), |
| 341 | missing_readiness_gates=_string_list(data.get("missing_readiness_gates")), | 351 | missing_readiness_gates=_string_list(data.get("missing_readiness_gates")), |
| | 352 | + policy_stage=_optional_text(data.get("policy_stage")), |
| | 353 | + policy_outcome=_optional_text(data.get("policy_outcome")), |
| 342 | prompt_format=_optional_text(data.get("prompt_format")), | 354 | prompt_format=_optional_text(data.get("prompt_format")), |
| 343 | prompt_sections=_string_list(data.get("prompt_sections")), | 355 | prompt_sections=_string_list(data.get("prompt_sections")), |
| 344 | artifact_paths=_string_list(data.get("artifact_paths")), | 356 | artifact_paths=_string_list(data.get("artifact_paths")), |
@@ -386,6 +398,48 @@ class WorkflowTimelineEntry: |
| 386 | artifact_paths=list(artifact_paths or []), | 398 | artifact_paths=list(artifact_paths or []), |
| 387 | ) | 399 | ) |
| 388 | | 400 | |
| | 401 | + @classmethod |
| | 402 | + def accountability( |
| | 403 | + cls, |
| | 404 | + *, |
| | 405 | + kind: WorkflowTimelineEntryKind, |
| | 406 | + mode: WorkflowMode | str, |
| | 407 | + reason_code: str, |
| | 408 | + summary: str, |
| | 409 | + policy_stage: str | None = None, |
| | 410 | + policy_outcome: str | None = None, |
| | 411 | + decision_kind: WorkflowDecisionKind | str | None = WorkflowDecisionKind.FORCED, |
| | 412 | + prompt_format: str | None = None, |
| | 413 | + prompt_sections: list[str] | None = None, |
| | 414 | + signal_summary: list[str] | None = None, |
| | 415 | + evidence_summary: list[str] | None = None, |
| | 416 | + artifact_paths: list[str] | None = None, |
| | 417 | + ) -> WorkflowTimelineEntry: |
| | 418 | + """Build one typed non-routing accountability entry.""" |
| | 419 | + |
| | 420 | + resolved_mode = mode.value if isinstance(mode, WorkflowMode) else str(mode) |
| | 421 | + if isinstance(decision_kind, WorkflowDecisionKind): |
| | 422 | + resolved_decision_kind = decision_kind.value |
| | 423 | + elif decision_kind is None: |
| | 424 | + resolved_decision_kind = None |
| | 425 | + else: |
| | 426 | + resolved_decision_kind = str(decision_kind) |
| | 427 | + return cls( |
| | 428 | + timestamp=_utc_now(), |
| | 429 | + kind=kind.value, |
| | 430 | + mode=resolved_mode, |
| | 431 | + reason_code=reason_code, |
| | 432 | + summary=summary, |
| | 433 | + decision_kind=resolved_decision_kind, |
| | 434 | + signal_summary=list(signal_summary or []), |
| | 435 | + evidence_summary=list(evidence_summary or []), |
| | 436 | + policy_stage=policy_stage, |
| | 437 | + policy_outcome=policy_outcome, |
| | 438 | + prompt_format=prompt_format, |
| | 439 | + prompt_sections=list(prompt_sections or []), |
| | 440 | + artifact_paths=list(artifact_paths or []), |
| | 441 | + ) |
| | 442 | + |
| 389 | | 443 | |
| 390 | class WorkflowPolicy: | 444 | class WorkflowPolicy: |
| 391 | """Scored workflow-policy engine for route and clarify decisions.""" | 445 | """Scored workflow-policy engine for route and clarify decisions.""" |