Files
Gitea-Tools/thread_state_ledger_examples.py
T
sysadminandClaude Opus 4.8 9bf3acfef6 feat: enforce stable-control vs dev runtime modes for Gitea MCP (Closes #615)
The stable-control runtime ADR (docs/architecture/mcp-stable-control-runtime-policy-adr.md)
established the policy but had no runtime enforcement: a daemon relaunched from a
feature worktree still holds production credentials and will happily mutate real
issues. This adds the enforcement layer (acceptance criteria 6-11).

stable_control_runtime.py:
- classify_runtime_mode(): stable-control | dev-test | unknown, inferred from the
  process root and checkout branch, with an explicit GITEA_MCP_RUNTIME_MODE
  declaration for packaged layouts that have no git checkout.
- build_runtime_report(): runtime mode, git SHA, branch, checkout path, process
  root, active workspace, repo binding, profile, identity, dirty files,
  alignment, and real_mutations_allowed.
- assess_runtime_mutation_gate(): fails closed on dev-test targeting production,
  unknown runtime, dirty stable checkout, dev-worktree launch, and unsafe
  process-root/workspace alignment.
- Post-transport-flap re-proving tracked per namespace, so proving the author
  namespace never implies reviewer, merger, or reconciler (#584).
- assess_promotion_record(): promotion must record previous and promoted SHAs
  plus health, identity, profile, workspace, capability, and rollback proof.

Server wiring:
- _profile_operation_gate() consults the runtime gate alongside the #420 parity
  gate. gitea.read is never blocked, so an operator can still diagnose a sick
  runtime.
- The gate reads a startup snapshot rather than shelling out per mutation, for
  the same reason parity uses a startup baseline: the runtime a process serves
  from is fixed when it loads its code. Enforcement is decided from how the
  process was loaded, so per-test production simulation cannot switch it on.
- gitea_get_runtime_context() reports the live runtime under
  stable_control_runtime and points at the promotion runbook when blocked.

Docs and tooling:
- docs/stable-runtime-promotion-runbook.md: operator promotion procedure,
  required record fields, per-namespace re-proving, rollback.
- scripts/promote-stable-runtime: read-only helper that emits and validates a
  promotion record; it never restarts anything.
- Five canonical [THREAD STATE LEDGER] examples: runtime healthy, transport flap
  recovered, namespace not yet re-proven, promotion completed, rollback required.
- ADR section 5 follow-ups marked landed; runbooks cross-link the new runbook.

Validation: 47 new tests in tests/test_stable_control_runtime.py. Full suite
3827 passed / 6 skipped / 2 failed; both failures are pre-existing on master
059ee77 (verified in a clean baseline worktree: identical 2 failures,
3780 passed).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-07-20 10:48:47 -04:00

528 lines
13 KiB
Python

"""Worked examples for the two-comment workflow (#507)."""
from __future__ import annotations
HEAD_SHA = "08202f7eaa6d09b2eca8f2960126994a4b22646b"
EXAMPLES: list[tuple[str, str, str]] = []
def _example(name: str, handoff: str, ledger: str) -> tuple[str, str, str]:
return (name, handoff.strip(), ledger.strip())
EXAMPLES.append(
_example(
"approved_review_posted",
f"""
[CONTROLLER HANDOFF] PR #487 / Issue #485 — review approved
Server-side mutation ledger:
- gitea_submit_pr_review → APPROVED review posted to Gitea (comment id 6566)
Blockers:
- none
""",
f"""
[THREAD STATE LEDGER] PR #487 — APPROVED review posted to Gitea
What is true now:
- PR state: open
- Current head SHA: {HEAD_SHA}
- Server-side decision state: APPROVED review posted to Gitea
- Local verdict/state: APPROVE verdict prepared locally
- Latest known validation: tests passed locally
What changed:
- APPROVED review posted to Gitea at pinned head
What is blocked:
- Blocker classification: no blocker
Who/what acts next:
- Next actor: merger
- Required action: merge on explicit operator command
- Do not do: re-post APPROVE
- Resume from: PR #487 review feedback
""",
)
)
EXAMPLES.append(
_example(
"approve_blocked_before_posting",
f"""
[CONTROLLER HANDOFF] PR #487 / Issue #485 — approve blocked
Server-side mutation ledger:
- none — no server-side state changed
Blockers:
- process/rule blocker: MCP process root still control checkout
""",
f"""
[THREAD STATE LEDGER] PR #487 — APPROVE verdict prepared locally; post blocked
What is true now:
- PR state: open
- Current head SHA: {HEAD_SHA}
- Server-side decision state: no server-side state changed
- Local verdict/state: APPROVE verdict prepared locally
- Latest known validation: tests passed locally
What changed:
- Review validation completed locally; APPROVE not posted
What is blocked:
- Blocker classification: process/rule blocker
Who/what acts next:
- Next actor: operator
- Required action: reconnect reviewer MCP to branches worktree
- Do not do: retry APPROVE from root-bound session
- Resume from: lease + mark_final_review_decision + submit
""",
)
)
EXAMPLES.append(
_example(
"request_changes_posted",
f"""
[CONTROLLER HANDOFF] PR #200 — request changes
Server-side mutation ledger:
- gitea_submit_pr_review → REQUEST_CHANGES posted to Gitea
Blockers:
- none
""",
"""
[THREAD STATE LEDGER] PR #200 — REQUEST_CHANGES posted to Gitea
What is true now:
- PR state: open
- Server-side decision state: REQUEST_CHANGES posted to Gitea
- Local verdict/state: REQUEST_CHANGES prepared locally
- Latest known validation: tests passed locally
What changed:
- REQUEST_CHANGES posted to Gitea
What is blocked:
- Blocker classification: no blocker
Who/what acts next:
- Next actor: author
- Required action: address review feedback and push fixes
- Do not do: merge
- Resume from: PR review comments
""",
)
)
EXAMPLES.append(
_example(
"merge_completed",
f"""
[CONTROLLER HANDOFF] PR #487 — merge performed
Server-side mutation ledger:
- gitea_merge_pr → merge performed
Blockers:
- none
""",
f"""
[THREAD STATE LEDGER] PR #487 — merge performed
What is true now:
- PR state: merged
- Current head SHA: {HEAD_SHA}
- Server-side decision state: merge performed
- Local verdict/state: merge not attempted locally before MCP merge
- Latest known validation: full suite not rerun
What changed:
- merge performed via gitea_merge_pr
What is blocked:
- Blocker classification: no blocker
Who/what acts next:
- Next actor: reconciler
- Required action: verify linked issue closure policy
- Do not do: re-merge
- Resume from: post-merge cleanup checklist
""",
)
)
EXAMPLES.append(
_example(
"merge_blocked",
f"""
[CONTROLLER HANDOFF] PR #487 — merge not performed
Server-side mutation ledger:
- gitea_merge_pr attempted → merge not performed (stale approval gate)
Blockers:
- stale head blocker: approval not at current head
""",
f"""
[THREAD STATE LEDGER] PR #487 — merge not performed
What is true now:
- PR state: open
- Current head SHA: {HEAD_SHA}
- Server-side decision state: merge not performed
- Local verdict/state: merge not performed
- Latest known validation: tests passed locally
What changed:
- merge attempt blocked by stale-head gate
What is blocked:
- Blocker classification: stale head
Who/what acts next:
- Next actor: reviewer
- Required action: re-review at current head
- Do not do: merge until approval_at_current_head is true
- Resume from: gitea_get_pr_review_feedback
""",
)
)
EXAMPLES.append(
_example(
"reconciler_closure",
f"""
[CONTROLLER HANDOFF] PR #99 — reconciler closure
Server-side mutation ledger:
- gitea_reconcile_already_landed_pr → PR closed on Gitea
Blockers:
- none
""",
"""
[THREAD STATE LEDGER] PR #99 — reconciler closure complete
What is true now:
- PR state: closed
- Server-side decision state: server-side state changed
- Local verdict/state: no server-side state changed locally before MCP close
- Latest known validation: ancestor proof passed
What changed:
- superseded PR closed by reconciler
What is blocked:
- Blocker classification: no blocker
Who/what acts next:
- Next actor: controller
- Required action: verify queue has no duplicate open PR
- Do not do: reopen without canonical PR proof
- Resume from: queue inventory
""",
)
)
EXAMPLES.append(
_example(
"environment_tooling_blocker",
f"""
[CONTROLLER HANDOFF] PR #487 — tooling blocker
Server-side mutation ledger:
- none — no server-side state changed
Blockers:
- environment/tooling blocker: gitea-reviewer MCP process root mismatch
""",
f"""
[THREAD STATE LEDGER] PR #487 — lease attempt blocked
What is true now:
- PR state: open
- Current head SHA: {HEAD_SHA}
- Server-side decision state: no server-side state changed
- Local verdict/state: APPROVE verdict prepared locally
- Latest known validation: tests passed locally
What changed:
- lease attempt blocked by workspace guard
What is blocked:
- Blocker classification: environment/tooling blocker
Who/what acts next:
- Next actor: operator
- Required action: set GITEA_AUTHOR_WORKTREE and /mcp reconnect
- Do not do: acquire lease from control checkout
- Resume from: gitea_get_runtime_context
""",
)
)
EXAMPLES.append(
_example(
"stale_head_blocker",
f"""
[CONTROLLER HANDOFF] PR #300 — stale head
Server-side mutation ledger:
- none — no server-side state changed
Blockers:
- stale head blocker: live head differs from pinned reviewed head
""",
"""
[THREAD STATE LEDGER] PR #300 — stale approval
What is true now:
- PR state: open
- Server-side decision state: no server-side state changed
- Local verdict/state: approval_at_current_head is false (stale head)
- Latest known validation: full suite not rerun
What changed:
- author pushed after approval
What is blocked:
- Blocker classification: stale head
Who/what acts next:
- Next actor: reviewer
- Required action: fresh review at current head
- Do not do: merge with stale approval
- Resume from: gitea_get_pr_review_feedback
""",
)
)
# ── Stable control runtime states (#615) ─────────────────────────────────────
EXAMPLES.append(
_example(
"runtime_healthy",
"""
[CONTROLLER HANDOFF] Runtime check — stable control runtime healthy
Server-side mutation ledger:
- none — no server-side state changed
Blockers:
- none
""",
f"""
[THREAD STATE LEDGER] Runtime — stable control runtime healthy
What is true now:
- Runtime mode: stable-control
- Runtime git SHA: {HEAD_SHA}
- Server-side decision state: no server-side state changed
- Local verdict/state: runtime reported real_mutations_allowed=true
- Latest known validation: gitea_get_runtime_context read in this session
What changed:
- nothing; this is a read-only runtime observation
What is blocked:
- Blocker classification: no blocker
Who/what acts next:
- Next actor: author
- Required action: proceed with the allocated workflow phase
- Do not do: restart or relaunch the stable runtime
- Resume from: gitea_workflow_dashboard
""",
)
)
EXAMPLES.append(
_example(
"transport_flap_recovered",
"""
[CONTROLLER HANDOFF] Runtime check — transport flap recovered
Server-side mutation ledger:
- none — no server-side state changed
Blockers:
- environment/tooling blocker: MCP transport dropped mid-session and recovered
""",
f"""
[THREAD STATE LEDGER] Runtime — transport flap recovered, namespaces re-proven
What is true now:
- Runtime mode: stable-control
- Runtime git SHA: {HEAD_SHA}
- Server-side decision state: no server-side state changed
- Local verdict/state: all four namespaces re-proven after the flap
- Latest known validation: whoami + runtime context + capability resolve per namespace
What changed:
- author, reviewer, merger, and reconciler namespaces each re-proven independently
What is blocked:
- Blocker classification: no blocker
Who/what acts next:
- Next actor: author
- Required action: resume the interrupted workflow phase from its last durable state
- Do not do: treat author proof as proof of the other namespaces
- Resume from: the phase handoff that preceded the flap
""",
)
)
EXAMPLES.append(
_example(
"namespace_not_yet_reproven",
"""
[CONTROLLER HANDOFF] Runtime check — reviewer namespace not re-proven
Server-side mutation ledger:
- none — no server-side state changed
Blockers:
- environment/tooling blocker: reviewer namespace not re-proven since the transport flap
""",
f"""
[THREAD STATE LEDGER] Runtime — reviewer namespace not re-proven after flap
What is true now:
- Runtime mode: stable-control
- Runtime git SHA: {HEAD_SHA}
- Server-side decision state: no server-side state changed
- Local verdict/state: reviewer namespace unproven; mutation gate fails closed
- Latest known validation: author namespace re-proven; reviewer not attempted
What changed:
- reviewer mutations blocked with namespace_not_reproven_after_flap
What is blocked:
- Blocker classification: environment/tooling blocker
Who/what acts next:
- Next actor: reviewer
- Required action: run whoami, runtime context, and capability resolve in the reviewer namespace
- Do not do: substitute author proof for reviewer proof
- Resume from: docs/stable-runtime-promotion-runbook.md section 5
""",
)
)
EXAMPLES.append(
_example(
"promotion_completed",
"""
[CONTROLLER HANDOFF] Runtime promotion — completed
Server-side mutation ledger:
- gitea_create_issue_comment on #615 with the promotion record
Blockers:
- none
""",
f"""
[THREAD STATE LEDGER] Runtime — promotion completed and re-proven
What is true now:
- Runtime mode: stable-control
- Runtime git SHA: {HEAD_SHA}
- Server-side decision state: server-side state changed
- Local verdict/state: promotion record carries every required field
- Latest known validation: assess_promotion_record valid=true; all namespaces re-proven
What changed:
- stable control runtime advanced to the promoted SHA and reloaded by the operator
What is blocked:
- Blocker classification: no blocker
Who/what acts next:
- Next actor: author
- Required action: resume normal workflow phases on the promoted runtime
- Do not do: promote again without a fresh record
- Resume from: docs/stable-runtime-promotion-runbook.md section 4
""",
)
)
EXAMPLES.append(
_example(
"rollback_required",
"""
[CONTROLLER HANDOFF] Runtime promotion — rollback required
Server-side mutation ledger:
- gitea_create_issue_comment on #615 with the rollback evidence
Blockers:
- environment/tooling blocker: promoted runtime unhealthy, rollback required
""",
f"""
[THREAD STATE LEDGER] Runtime — promoted runtime unhealthy, rollback required
What is true now:
- Runtime mode: unknown
- Runtime git SHA: {HEAD_SHA}
- Server-side decision state: no server-side state changed after the promotion record
- Local verdict/state: promoted runtime failed namespace health; mutations blocked
- Latest known validation: namespace health probe reported EOF after reload
What changed:
- all PR/review/merge work stopped pending rollback to the previous runtime SHA
What is blocked:
- Blocker classification: environment/tooling blocker
Who/what acts next:
- Next actor: controller
- Required action: operator rolls back to the previous runtime SHA and re-proves every namespace
- Do not do: route around the unhealthy runtime or mutate from a dev/test runtime
- Resume from: docs/stable-runtime-promotion-runbook.md section 6
""",
)
)
EXAMPLES.append(
_example(
"duplicate_canonicalization_blocker",
f"""
[CONTROLLER HANDOFF] Issue filing — duplicate found
Server-side mutation ledger:
- gitea_create_issue_comment on #505 with missing acceptance criteria
Blockers:
- duplicate/canonicalization blocker: #505 tracks CTH umbrella
""",
"""
[THREAD STATE LEDGER] Issue #507 — filed as focused split from #505
What is true now:
- Issue state: open
- Server-side decision state: server-side state changed
- Local verdict/state: no server-side state changed before issue create
- Latest known validation: duplicate search completed
What changed:
- new issue #507 created after duplicate assessment
What is blocked:
- Blocker classification: no blocker
Who/what acts next:
- Next actor: author
- Required action: implement #507 two-comment validator
- Do not do: recreate duplicate CTH issue
- Resume from: issue #507 body
""",
)
)