The prior #708 slice added assess_connected_namespace_attachment() as a pure
decision function with no call site: nothing invoked it, so a session whose
role namespaces were Connected at the host but absent from the active session
tool surface still passed every mutation gate. Detection existed on paper only.
This makes it load-bearing.
Decision layer (mcp_namespace_health.py)
- assess_connected_namespace_attachment() gains secret-free telemetry
(connected/attached/required/missing counts, discovery cache hit and age,
reconnect_required, auto_attach_attempted, auto_recovered, error_type) and
reports reconnect_required, auto_recovered and startup_ordering_race.
- Startup ordering: a namespace whose connect completed after the session tool
snapshot cannot be in that snapshot, so parallel multi-role startup is
identified as its own race with the affected namespaces listed.
- attachment_gate_from_session() is a fail-closed gate keyed by
ATTACHMENT_GATED_TASKS. An unassessed namespace does not gate, matching #543
semantics, so this never fabricates a block.
- SANCTIONED_ATTACH_RECOVERY_TOOL names gitea_request_mcp_reconnect (#678) as
the only recovery.
Server wiring (gitea_mcp_server.py)
- New tool gitea_assess_mcp_namespace_attachment classifies the condition and
records a per-namespace verdict in _LIVE_NAMESPACE_ATTACHMENT.
- gitea_submit_pr_review and gitea_merge_pr now consult
_namespace_attachment_gate() alongside the existing #543 health gate, so both
fail closed while a required namespace is unattached.
- Watchdog check-in emits status only, never namespace contents.
The typed condition mcp_connected_namespaces_missing stays distinct from config
drift (#672), transport-closed (#584) and resolver EOF (#685). Recovery never
routes through direct imports, CLI or raw API mutation, profile hopping,
session-state overrides, or process kills.
Docs
- docs/mcp-namespace-health.md documents the tool arguments, startup ordering,
the fail-closed gate, and the telemetry contract.
- skills/llm-project-workflow/SKILL.md states Connected is not attached, and
that preflight proof is live tool visibility plus gitea_whoami on the role
namespace rather than host status alone.
- docs/mcp-tool-inventory.md lists the new tool.
- docs/remote-mcp/threat-model-anchors.json and threat-model.md: 21 #956 anchors
restamped for the line shift these additions caused in gitea_mcp_server.py.
Every anchor was re-derived from its recorded expect substring; none guessed.
Tests
- tests/test_issue_708_attachment_wiring.py (19 cases): typed detection, proof
mapping, reconnect-only next action, auto-attach success and failure,
reconnect rediscovery, multi-role startup ordering, telemetry including a
no-secret-leak assertion, fail-closed gate per role, unassessed and unmapped
tasks not gating, partial attachment gating only the affected role, and no
healthy verdict without attachment proof.
Verification
- tests/test_issue_708_attachment_wiring.py + test_issue_708_mcp_namespace_attachment.py: 24 passed
- namespace/session/runtime/review sweep: 427 passed, 12 subtests
- full suite head: 31 failed, 5885 passed, 6 skipped, 1047 subtests
- full suite base 17ba1ff035: 30 failed, 5862 passed, 6 skipped, 1047 subtests
- failing identifier sets match, plus tests/test_mirror_refs.py DryRunBanner,
which fails on the unmodified base in isolation and passes here: flaky, not a
regression from this branch.
- Gate proven by execution, not inspection: registering the assessment blocks
merge_pr and review_pr, and attaching the namespaces clears the block.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
A session that ran `pkill -f mcp_server.py`, waited for the IDE to respawn
the daemons, and then closed an issue left no trace distinguishing that
closure from one performed over a sanctioned runtime. Detection existed only
as advisory strings (`native_mcp_preference.classify_command_path`,
`review_workflow_boundary`) and never failed closed on the mutations that
followed.
Adds `runtime_recovery_guard.py`, mirroring the #671 stable-branch
contamination model so the two cannot drift apart: classification of
kill/pkill/killall commands and known-pid kills, a durable
`runtime_recovery_contamination` marker, a fail-closed pre-flight gate over
the shared review/merge/close/completion task set, and reconciler-only
clearing. `comment_issue` and `lock_issue` stay allowed so a contaminated
worker can still post its audit comment and hand off. The marker is
recovery-critical, so contamination cannot expire into cleanliness with the
session-state TTL.
Read-only inspection (`ps aux | grep mcp_server`), sanctioned reconnects,
and process management unrelated to the daemons are never flagged; a bare
`kill` of an unknown pid is reported as ambiguous rather than contaminating,
so ordinary subprocess work is not false-blocked. A pattern broad enough to
sweep unrelated namespaces (`pkill -f python`) is contamination even when it
never names MCP.
Operator-authorized host maintenance stays permitted, but the authorization
is read from GITEA_OPERATOR_DAEMON_MAINTENANCE_AUTHORIZATION in the process
environment only and never from a tool argument, so a session cannot
authorize itself.
Final-report rules reject clean-session claims and require the contaminated
recovery to be surfaced. Two tools are registered (inventory 112 to 114) and
the sanctioned-versus-forbidden contrast is documented in the namespace
recovery doc and the workflow skill.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
The gitea-workflow documentation named a gitea_edit_issue tool that no
namespace had ever registered. There was no sanctioned MCP path to change an
issue title or body at all: the only edit tool, gitea_edit_pr, PATCHes the
pull-request endpoint. An authorized body correction on issue #780 therefore
had to be recorded as a discussion comment instead.
Add edit_issue.py as the authoritative rule and gitea_edit_issue as the tool
built on it:
- Only the fields the caller names are sent, so labels, state, assignee, and
milestone cannot be overwritten from a stale read.
- A pull-request number is refused. Gitea serves pull requests from the same
/issues/{n} collection, so without that check the issue path would quietly
become a second, ungated PR edit path. gitea_edit_pr stays PR-only.
- Structurally invalid requests raise before any credential or network work;
a request that would change nothing is reported as an explicit no-op with a
next action rather than a silent success.
- Read-after-write proves the applied title/body and proves that state,
labels, assignees, and milestone did not move. Transport failures on the
pre-read, the PATCH, and the read-back are each reported, redacted, with
the correct performed/verified state.
Gates match every other issue mutation: profile permission via the shared
capability map (resolver task edit_issue, gitea.issue.comment), preflight
purity, branches worktree validation, anti-stomp inventory membership, and
audited mutation.
Fix the drift that hid this. docs/mcp-tool-inventory.md is now the canonical
registered-tool list, and mcp_tool_inventory.py compares it to the live
registry in both directions, plus checks that every tool named under skills/
is registered. The new guard immediately found a second instance of the same
defect: gitea_record_pre_review_command had lost its @mcp.tool() decorator
while the canonical review workflow still instructed reviewers to call it, so
that registration is restored.
Validation:
PASSED: venv/bin/python -m pytest tests/test_issue_781_edit_issue_tool.py -s -q
— 50 passed
FAILED: venv/bin/python -m pytest -s -q — 4095 passed, 11 failed, 6 skipped.
The identical 11 tests fail on clean master 8e149e6 with no changes applied
(254 passed, 11 failed across those five files), so they are pre-existing
and proven by a baseline run rather than asserted.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Adds the canonical cross-role handoff schema and its fail-closed validator,
live-state recovery, role-limited continuation, mandatory durable posting,
the merged-awaiting-controller boundary, controller accept/reject
continuation, and workflow-failure escalation with duplicate handling.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Prevention hardening for the #670 incident (bare direct-to-master commit
2fa97c26 and the PR #654 merger stable-branch push attempt). Worker sessions
must never publish stable branches directly; stable updates land only through
sanctioned Gitea merge tooling or an authorized reconciler path.
New pure module stable_branch_push_guard.py:
- classify_push_command: detects git push <remote> <stable-ref> equivalents
including refspecs (HEAD:<ref>, +refs/heads/x:refs/heads/<ref>), --force,
--delete/:<ref>, and --dry-run/-n no-op probes (dry-run still proves intent).
Fetch/pull and feature-branch pushes are never flagged; sanctioned
gitea_merge_pr / API merge is not a push.
- assess_root_checkout_local_commit: detects control-checkout commits not on an
issue feature branch (branches/ worktrees exempt).
- redact_command: strips URL userinfo/token assignments before logging.
- build_contamination_record + assess_contamination_gate: durable marker shape
and fail-closed gate (reconciler-exempt; comment/lock stay allowed for handoff).
Server wiring (gitea_mcp_server.py, mcp_session_state.py):
- KIND_STABLE_BRANCH_CONTAMINATION durable session marker (per profile identity).
- _enforce_stable_branch_contamination_gate wired into verify_preflight_purity
so review/merge/close/completion mutations fail closed while contaminated.
- gitea_record_stable_branch_push_attempt: classify + mark on detection.
- gitea_audit_stable_branch_contamination: reconciler-only inspect/clear; a
worker session can never self-clear.
Tests (AC5): no-op dry-run push, real direct push, sanctioned Gitea merge,
fetch-only, root-checkout local commit, feature-branch push allowed, gate
block/reconciler-exempt, redaction. 51 new tests; full suite 2596 passed.
Docs: llm-project-workflow SKILL.md — universal rule, blocker class, and a
dedicated "Stable Branch Push Protection" section (worker sessions must never
push stable branches directly).
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Expose gitea-workflow / llm-project-workflow / git-pr-workflows as one
workflow router in mcp_list_project_skills, add preflight + Codex install
script, and document multi-runtime skill name parity.
Document the narrow controller-authorized path for landing MCP workflow
fixes when live daemons cannot canonically review themselves, with
required audits, allowed/forbidden actions, and post-land restart steps.
- Updated llm-project-workflow/SKILL.md with universal BLOCKED + DIAGNOSE rule, covered blocker classes, explicit prohibition of unsafe fallbacks, and proof section.
- Added templates/blocked-diagnose-report.md (standard template with all required fields).
- Updated workflows/work-issue.md, review-merge-pr.md, create-issue.md and templates/start-issue.md, review-pr.md, merge-pr.md, recover-bad-state.md to require and reference the rule at load.
- Updated docs/llm-workflow-runbooks.md to reinforce the rule for controller prompts and all workflows.
- All changes in isolated worktree branches/issue-552-blocked-diagnose. No root mutation. No unsafe fallbacks used.
- Tests (workflow + cleanup proofs): 26+ passed in run.
- git diff --check prgs/master...HEAD: clean.
Add canonical issue/PR/discussion state comment templates and validators,
final-report rules for STATE/WHO_IS_NEXT/NEXT_ACTION/NEXT_PROMPT, and
queue-controller guidance. Posting enforcement remains in #496.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Adds canonical pr-queue-cleanup workflow, report verifier, reviewer-only task
routing, and runbook guidance so operators can drain open PRs one canonical
review at a time with fail-closed boundaries on batching and unauthorized merges.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Consolidate all four canonical workflow files and router SKILL.md:
- review-merge-pr.md (from #334 canonical prompt)
- reconcile-landed-pr.md (reconciliation gates + handoff schema)
- create-issue.md (canonical issue-creation prompt)
- work-issue.md (canonical author/coder prompt)
SKILL.md is now a short router with mode isolation and universal rules.
Adds final-report schemas for all modes and 13 doc-contract tests.
Supersedes partial split work in PR #335; full #333 acceptance in one PR.
Document default tool-call and wall-time budgets for deterministic MCP
work, forbid subagent commit delegation when gitea_commit_files is
available, and pin the rules in runbooks, portable skill, operator guide,
and doc-contract tests.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Require mutations only from branches/ worktrees after proving project
root, cwd, branch, stable main-checkout branch, and session worktree
path. Surface in portable skill, runbooks, wiki, operator guide, and
start-issue template.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Require lease verification (open PRs, branches, worktrees, leases,
merged completion) before any issue or PR work. Surface the rule in
the portable skill, Gitea runbooks, operator guide, and start-issue
template; add doc-contract tests.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Operators can supply an active PR backlog for one repo while agents
inventory another and falsely report trusted_empty. reconcile_queue_target
locks the target repo from operator context/backlog before list_prs runs;
pr_inventory_trust_gate blocks target_repo_mismatch and unresolved locks.
assess_reviewer_queue_inventory wires the lock into the canonical path.
Tests cover Gitea-Tools backlog vs mcp-control-plane inventory mismatch,
ambiguous dual-repo context, conflicting directive vs backlog, and final-
report field requirements.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Gitea expects review event APPROVED, not APPROVE; the wrong event left
submissions as PENDING drafts. Verify terminal verdicts after submit,
submit pending drafts when needed, and block merge when approval_visible
is false.
gitea_lock_issue and gitea_create_pr accept worktree_path so lock
preconditions validate the caller's scratch clone instead of the shared
MCP server CWD. Lock records store the validated path; PR creation fails
closed when the declared path does not match.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
- Add 'mark_issue' to TASK_MAP in resolve_task_capability for exact mutation proof (closes gap on unknown treated as allowed).
- Add assess_controller_handoff and integrate into build_final_report (downgrades missing handoff).
- Add tests for handoff and update existing.
- Update SKILL.md and review-pr.md to mandate exact 'Controller Handoff' section, exact sweep evidence, PR head SHA in reports.
- Author profile used throughout; no review/merge.
Refs #183
Reviewer agents could post probe APPROVE/REQUEST_CHANGES reviews while
testing lock paths, polluting PR audit trails. Add a review decision lock
seeded by gitea_resolve_task_capability(review_pr) that requires
gitea_mark_final_review_decision and final_review_decision_ready=True
before gitea_submit_pr_review performs a live mutation.
Add gitea_dry_run_pr_review for read-only submission validation,
gitea_authorize_review_correction for operator-approved fixes, and
assess_review_mutation_final_report for final-report proof. One live
review mutation per run unless correction is explicitly authorized.
Extends review_proofs.py with the four #179 proofs, the successor set to
the #173 checkout/inventory proofs:
- assess_capability_evidence: a capability claim (review_pr, merge_pr, ...)
counts only with exact evidence citing gitea_resolve_task_capability
output or equivalent runtime context; no claims at all fails closed.
- assess_sweep_evidence: secret/provenance sweeps must state the exact
command/script/pattern/named method, the scope scanned, and a boolean
result; vague summaries are downgraded and a missing sweep fails closed.
- assess_live_state_recheck: an explicit pre-mutation recheck must prove
the PR is still open, the live head equals the pinned head (full
40-hex), the base branch is unchanged, and blocking review state was
checked and absent; not performing it fails closed.
- assess_role_boundary: a reviewer run using an author namespace (or vice
versa) is clean only with an explicit justification; unreported
namespace usage fails closed.
build_final_report now takes the four proofs as keyword arguments: any
missing or failed proof downgrades the grade, merge_allowed additionally
requires the proven live-state recheck, and a merge performed without it
is a blocked violation. Existing #173 semantics are unchanged otherwise;
gates only get stricter.
tests/test_review_proofs.py adds 29 tests covering the issue's harness
assertions: capability claims without evidence downgraded, vague sweeps
downgraded, missing/stale live-state recheck downgrades and blocks merge
(violation when a merge is claimed anyway), unjustified author-namespace
use downgraded, and the #173 positive baseline preserved.
SKILL.md sections F/G and the review-pr/merge-pr templates now require the
capability evidence, exact sweep, pre-verdict and pre-merge live-state
rechecks, and reviewer-namespace discipline.
Closes#179
Co-Authored-By: Claude Fable 5 <[email protected]>
- Upgrade SKILL.md §K compact format to the issue #182 canonical field set
(Task/Repo/Role/Identity/Issue-PR/Branch-SHA/Files/Validation/Mutations/
Current status/Blockers/Next/Safety) plus role-specific field lists for
review/merge, author, and queue/inventory tasks.
- Point the review-pr, merge-pr, and start-issue template handoff lines at
the exactly-titled Controller Handoff section with their role fields.
- Add review_proofs.assess_controller_handoff(): reports without the exact
section are 'missing' (downgraded), present-but-partial are 'incomplete'
with the absent fields listed, and role extras are enforced per role.
- Add TestControllerHandoff (8 tests) including a SKILL.md doc-contract
test so the documented requirement cannot silently rot.
The handoff supplements the full report; full-report validation rules are
unchanged.
Closes#182
Co-Authored-By: Claude Fable 5 <[email protected]>
Adds author_proofs.py, the author-side counterpart of review_proofs.py
(#173), turning the branch-drift incident from PR #176 into fail-closed
gates:
- verify_branch_for_commit: the current branch must equal the intended
feature branch and may never be a protected branch (master, main,
develop, development, dev); missing state fails closed.
- detect_branch_drift: any branch or HEAD change between validation time
and commit time — including an external branch switch in a shared
worktree, which is treated as an expected event to detect — blocks the
commit until reconciled.
- verify_push_target: a push requires the local branch, remote target
branch, and intended issue branch to all match, none protected.
- assess_protected_branch_commit: an accidental protected-branch commit
must never be pushed, requires repair, and the repair must be reported;
pushing the accident or silently continuing is a violation.
- build_commit_push_report: final-report block carrying the branch proof
before commit and before push; any failed proof, drift, or violation
makes the status blocked.
tests/test_author_proofs.py (27 tests) covers the issue's harness
assertions: commit on master blocked; drift between validation and commit
blocked; push target mismatch blocked; shared-worktree branch switch
detected; repair path cannot silently continue without reporting.
SKILL.md section E and templates/start-issue.md now require recording the
validation-time branch/HEAD, the branch proof before commit, the branch
proof before push, and non-silent accident repair. No review/merge/
permission gate is weakened.
Closes#177
Co-Authored-By: Claude Fable 5 <[email protected]>
Adds review_proofs.py with pure, fail-closed helpers for the reviewer-side
blind queue workflow:
- verify_pinned_head_checkout: proves HEAD == pinned PR head (full 40-hex,
no prefix matches) and diff base == PR base; any mismatch blocks
review/merge.
- assess_inventory_completeness: exhaustive "only PRs found" claims require
every configured repo listed with stated filters, proven pagination, and
reported open-PR totals.
- assess_validation_report: validation results are unclaimable unless the
exact command was stated and its output read; missing pass/fail/skip
counts, unjustified ignored paths, or unexplained deviation from the
canonical command downgrade the report to weak.
- assess_self_review_contamination: contamination must be evidence-backed;
bare same-session claims (either way) yield "unknown" with a
choose-another-PR-or-stop action, never an assumed verdict.
- build_final_report: distinguishes identity eligibility, author/reviewer
distinctness, session contamination, validation-on-pinned-head, merge,
and issue verification; grades "A" only when every proof is present,
downgrades otherwise, and marks a merge without allowing proofs as a
blocked violation.
tests/test_review_proofs.py covers all seven harness assertions from the
issue (unchecked-out pinned head blocks merge; multi-repo inventory;
pagination; missing counts flagged weak; evidence-required contamination;
unread output cannot claim validation passed; ignored paths need
justification).
SKILL.md section F and templates/review-pr.md now require the checkout,
inventory, validation-evidence, and contamination proofs and the
distinguished final report. No review/merge safety gate is weakened.
Closes#173
Co-Authored-By: Claude Fable 5 <[email protected]>
Make the nine-line compact Controller Handoff the default end-of-task
format; reserve the long Controller Handoff Summary for high-risk/complex
tasks (merge/tag/release, failed validation, blocked gates, secrets/prod,
complicated owner decisions, cross-repo state, or explicit owner request).
Compact form is for controller-LLM readability, safety confirmations are
never omitted, and PR bodies still carry full review detail.
Updates SKILL.md §K, llm-workflow-runbooks.md, and the start-issue /
review-pr templates. Documentation only.
Refs #101. Closes#108.
Co-Authored-By: Claude Fable 5 <[email protected]>
Automate the documented release-tag checklist (#48) without bypassing safety
gates.
scripts/release-tag:
- Requires a SemVer tag (vMAJOR.MINOR.PATCH); validates before any git/network.
- Fetch/prune first, then refuses: dirty worktree, non-master branch, local
master != remote master, HEAD not on remote master, and an existing local or
remote tag of the same name.
- Runs the full suite by default; --skip-tests is an explicit opt-out that warns.
- Creates an ANNOTATED tag (git tag -a), never lightweight.
- Safe by default: no push unless --push; --dry-run prints planned actions and
changes nothing. Supports --notes-file <path> for the annotation message.
- Prints: commit, tag, tests_run, tag_created, tag_pushed.
- Env injection points for testing/CI: RELEASE_TAG_REMOTE, RELEASE_TAG_TEST_CMD.
tests/test_release_tag.py (14 cases): valid SemVer dry-run; invalid version;
dirty worktree; non-master; master/remote mismatch; existing tag; missing
notes-file; annotated-not-lightweight; no-push-without-flag; push-only-with-flag;
notes-file message; --skip-tests warns; default runs tests (fail blocks tag,
pass tags). Each test builds a throwaway repo with a LOCAL bare remote (cloned,
not pushed) and stubs the test command — no network, no real tags, no pushing
from the project repo.
Docs: reference scripts/release-tag from the runbook, SKILL, and the release-tag
template (script preferred; manual steps are the fallback).
Full suite 305 passed / 0 failures; bash -n clean; git diff --check clean; no
secrets.
Closes#50. Refs #48.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Documents and enforces rules for closed-not-merged PR reconciliation, direct-master-push prevention, and issue label cleanup.
Rules added:
- Explicit definitions for Merged, Landed, Closed-not-merged, and Reconciled.
- A PR is done only when Gitea reports it merged or reconciliation proves content is present on master.
- Direct push to master is forbidden except as a documented recovery exception.
- PRs closed but not merged trigger the reconciliation process.
- Branch and worktree cleanup is forbidden until merge or reconciliation is confirmed.
- Final reports require PR metadata and Git content verification.
Closes#51.
Formalize the branch↔issue relationship and add a release/version-tagging policy.
Branch/issue linkage:
- scripts/worktree-start now validates branch names: implementation branches
must match (fix|feat|docs|chore)/issue-<number>-<slug>; review branches
review/pr-<number>-<slug>. Untraceable names are rejected with a clear error
(exit 2). New --allow-unlinked override for genuine exceptions. --dry-run
preserved.
- Documented issue → branch → worktree → PR → cleanup traceability in the
runbook and the portable SKILL, including the claim-comment convention and
Closes #n / Refs #n PR-body usage.
- Noted that Gitea exposes no native issue→branch API field (only a PR head
branch), so linkage is enforced via branch name + claim comment + PR body +
cleanup.
Versioning / tagging policy (docs only; no release automation yet):
- SemVer vMAJOR.MINOR.PATCH (v0.x.y while unstable) with PATCH/MINOR/MAJOR bump
rules.
- Annotated tags only, from the exact commit on remote master, only after the
full suite passes, with release notes referencing merged PRs/issues. Never tag
feature branches, dirty worktrees, unreviewed/self-authored work, or commits
not on remote master.
- Release runbook in the runbook + SKILL, plus a new
skills/llm-project-workflow/templates/release-tag.md prompt template.
Tests: worktree-start branch validation — accepts fix/feat/docs/chore/issue-*
and review/pr-*, rejects fix/random-name / my-branch / non-numeric issue,
honors --allow-unlinked, preserves --dry-run. Full suite 291 passed / 0 failures;
bash -n clean; git diff --check clean; no secrets.
Release-tag automation (a scripts/release-tag helper) intentionally deferred to a
later issue to keep this diff narrow and testable.
Closes#48. Refs #38, #39, #46.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>