Close residual gaps: CEO escalation from blocked, ref repair, reset-script state

Three independent residual hardenings surfaced by the smoke run:

- escalate_to_ceo is now reachable from a blocked task (source widened to
  {awaiting_pm_review, blocked} with a matching status-transition row), so a
  main_pm or board agent has a clean verb to surface a task it cannot resolve
  to the CEO — who can then approve, reject, or cancel it. Regenerated the
  lifecycle artifacts (status-transitions doc + panel JSON) to match.

- WorkspaceService now prunes broken loose refs (.bak debris and any ref whose
  contents are neither an object id nor a symref) before the refresh fetch, so
  a ref left corrupt by an interrupted recovery no longer produces per-operation
  "broken ref" warnings or wedges ref enumeration. Best-effort, file-reads-only.

- The reset script's full-reset Claude-state clear now defaults to the
  replay-state subdirs (projects/, todos/) of the mounted Claude home and
  refuses to clear the home root, so a full reset actually clears conversation
  replay state by default without wiping the host's stored credentials.
This commit is contained in:
Renn F
2026-06-08 05:17:15 +02:00
parent 69da8de4b5
commit 718a16c4f7
8 changed files with 178 additions and 14 deletions
@@ -1025,7 +1025,7 @@ async def test_escalate_to_ceo_matches_spec(role: str, status: str) -> None:
- role in {main_pm, product_owner, head_marketing},
- composed ``escalate_to_ceo`` action's source_status
(AWAITING_PM_REVIEW only).
(AWAITING_PM_REVIEW or BLOCKED).
The verb body keeps the journal:decision preflight (the spec doesn't
model journal side effects); satisfied here so the spec gate is the
+25 -8
View File
@@ -242,6 +242,8 @@ def test_status_transitions_includes_ceo_paths() -> None:
) in sources
assert (spec.Status.AWAITING_CEO_APPROVAL, spec.Status.COMPLETED) in sources
assert (spec.Status.AWAITING_CEO_APPROVAL, spec.Status.NEEDS_REVISION) in sources
# A blocked task the PM cannot resolve can also be surfaced to the CEO.
assert (spec.Status.BLOCKED, spec.Status.AWAITING_CEO_APPROVAL) in sources
def test_status_transitions_includes_block_pause_paths() -> None:
@@ -307,20 +309,35 @@ def test_status_transitions_role_constraints_match_canon() -> None:
assert by_pair[
(spec.Status.AWAITING_PM_REVIEW, spec.Status.COMPLETED, "complete")
] == frozenset({spec.Role.CELL_PM, spec.Role.MAIN_PM})
# escalate_to_ceo: main_pm + product_owner + head_marketing
assert by_pair[
(
spec.Status.AWAITING_PM_REVIEW,
spec.Status.AWAITING_CEO_APPROVAL,
"escalate_to_ceo",
)
] == frozenset(
# escalate_to_ceo: main_pm + product_owner + head_marketing — from a
# completed review and from a blocked task, same role gate.
escalate_roles = frozenset(
{
spec.Role.MAIN_PM,
spec.Role.PRODUCT_OWNER,
spec.Role.HEAD_MARKETING,
}
)
assert (
by_pair[
(
spec.Status.AWAITING_PM_REVIEW,
spec.Status.AWAITING_CEO_APPROVAL,
"escalate_to_ceo",
)
]
== escalate_roles
)
assert (
by_pair[
(
spec.Status.BLOCKED,
spec.Status.AWAITING_CEO_APPROVAL,
"escalate_to_ceo",
)
]
== escalate_roles
)
# CEO actions: CEO only
assert by_pair[
(spec.Status.AWAITING_CEO_APPROVAL, spec.Status.COMPLETED, "ceo_approve")