feat(mcp): gate Connected-but-unattached MCP namespaces (Closes #708) #967
Merged
sysadmin
merged 6 commits from 2026-07-28 18:31:14 -05:00
feat/issue-708-mcp-namespace-attachment into master
Labels
Clear labels
allocator
anti-stomp
architecture
bug
chore
codex
concurrency
contamination
control-plane
dashboard
database
design
documentation
enhancement
gitea
glitchtip
important
incident
incident-bridge
integration
jenkins
labels
leases
mcp
mcp-health
mcp-menu
multi-project
mutating
nice-to-have
observability
portability
preflight
protected-branch
queue
read-only
reconnect
recovery
refactor
release
reliability
resumable-review
reviewer
roadmap
safety
security
self-hosted
sentry
stale-runtime
status:blocked
status:in-progress
status:pr-open
status:ready
terminal-lock
testing
tracker
type:bug
type:feature
type:feature
type:guardrail
visibility
workflow
workflow-hardening
workflow-hardening
Controller-owned work allocator
Prevent concurrent LLM session stomping
Architecture / structural design
OpenAI Codex client / workflow session surface
Concurrent session safety
Workflow or session contamination incident
MCP control-plane coordination and allocation authority
MCP operational dashboard/queue view
Internal coordination storage (SQLite/Postgres)
Design / investigation, no implementation
Docs / runbooks
New feature or improvement
Gitea MCP workflow
GlitchTip integration
Operational or process incident requiring durable audit trail
Sentry-to-Gitea incident bridging
Integration testing
Jenkins integration
Label taxonomy management
Lease adopt/release/expire lifecycle
MCP server / tooling
MCP namespace and runtime health
MCP menu surface
Work spanning multiple monitoring projects or Gitea repos
Mutating action; requires gating
Observability, metrics, traces, error reporting
Cross-platform / portability
Shared preflight gates before mutation
Protected branch / stable-branch policy concern
Work queue visibility and allocation
Read-only, no mutation
MCP client reconnect/reload recovery path
Recovery paths for stale/foreign leases
Code refactor / restructure
Release / versioning
Reliability / failure handling
Persist and resume prepared review verdicts across sessions
Reviewer workflow tooling
Roadmap / umbrella issue
Safety rails and fail-closed mutation guards
Security / trust boundary
Self-hosted infrastructure integration
Sentry error monitoring integration
Stale backend daemon / runtime-vs-master parity failures
Issue is blocked
Issue is being worked on
Issue has an open pull request
Issue is ready for work
Terminal review lock (#332) path
Tests / test coverage
Issue tracker hygiene / meta
Bug or defect
Feature or enhancement
Feature or enhancement
Safety gate or guardrail
Workflow state visibility for LLMs/operators
Cross-tool workflow
LLM workflow coordination hardening
LLM workflow coordination hardening
No labels
Milestone
No items
No Milestone
Projects
Clear projects
No projects
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: Scaled-Tech-Consulting/Gitea-Tools#967
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Closes #708
Problem this fixes
An earlier slice on this branch added
assess_connected_namespace_attachment()tomcp_namespace_health.pyas a pure decision function with no call site. Nothing invoked it. A session whose role namespaces were Connected at the host but absent from the active session tool surface still passed every mutation gate, so the detection existed on paper only and no workflow failed closed.This PR makes it load-bearing and completes the live acceptance criteria.
Acceptance criteria
AC1 — distinct detection. Connected-but-unattached is typed
mcp_connected_namespaces_missing, carries per-namespaceproof_of_connected_vs_attached({connected, attached}), and stays separate from config drift (#672), transport-closed (#584) and resolver EOF (#685). A required namespace that is not connected at all is typed separately asmcp_required_namespaces_not_connected— see "Review 637 remediation" below.AC2 — automatic attachment or sanctioned recovery.
auto_attach_attempted/auto_attach_succeededproduceauto_recoveredwhen the runtime attaches without an operator. Otherwisereconnect_requiredis set andexact_next_actionoffers only the client reconnect path, naminggitea_request_mcp_reconnect(#678). The verdict is recorded in the session, andgitea_submit_pr_review/gitea_merge_prfail closed while a required namespace lacks attachment proof.AC3 — canonical guidance.
docs/mcp-namespace-health.mdandskills/llm-project-workflow/SKILL.mdstate that Connected is not attached, and that preflight proof is live tool visibility plusgitea_whoamion the role namespace rather than host status alone.AC4 — regression coverage. No healthy verdict is produced without attachment proof for every required namespace, under either failure condition.
AC5 — telemetry. A
telemetryblock reports connected/attached/required/missing/not-connected/contradictory counts, discovery cache hit and age,reconnect_required,auto_attach_attempted,auto_recovered,startup_ordering_race,error_typeanderror_types— namespace names and counts only, asserted secret-free by test.AC6 — no unsafe fallback. Error text and the gate carry the hard-stop policy. Recovery never routes through direct imports, CLI or raw API mutation, profile hopping, session-state overrides, or process kills.
Changes
mcp_namespace_health.py— telemetry and startup-ordering detection;ATTACHMENT_GATED_TASKS;required_namespace_for_attachment();attachment_gate_from_session(), a fail-closed gate that mirrors #543 semantics so an unassessed namespace never fabricates a block;SANCTIONED_ATTACH_RECOVERY_TOOL; and the two-condition classification described below.gitea_mcp_server.py— new toolgitea_assess_mcp_namespace_attachment; session store_LIVE_NAMESPACE_ATTACHMENTwith_record_live_namespace_attachment();_namespace_attachment_gate()consulted bygitea_submit_pr_reviewandgitea_merge_prbeside the existing #543 health gate.Docs — tool arguments, startup ordering, gate and telemetry contract; new tool added to
docs/mcp-tool-inventory.md.docs/remote-mcp/threat-model*— #956 anchors re-derived and restamped for the line shift these additions caused ingitea_mcp_server.py.Review 637 remediation
B1 — threat-model metadata regression (fixed in
58bd8521)Commit
126d76aset the anchors fixturegenerated_against_committoe3fa3b26and leftdocs/remote-mcp/threat-model.md:8citinga143cd06, sotests/test_issue_956_threat_model.py::ThreatModelStructureTests::test_records_the_commit_it_was_generated_againstfailed deterministically from that commit onward.The mismatch was substantive, not cosmetic. Resolving all 58 anchors against each candidate revision:
e3fa3b26ca5f078d(B2 commit)58bd8521(this head)a143cd06The document's own claim about where its
file:linecitations resolve was therefore false; the fixture held the accurate value. The B2 commit then inserted a net 16 lines intogitea_mcp_server.pyat a single point, shifting 21 anchors below it. All 21 were re-derived by that one uniform offset and each verified against its recordedexpectsubstring — none guessed, none ambiguous. Both artifacts now nameca5f078d, the commit the anchors were taken at, the document's inline citations are shifted to match, and the boundary table's "what the code actually enforces at" claim is restamped onto the same revision because it describes that same tree.Generation contract verified: 58/58 anchors resolve at the declared generation commit, 58/58 at this head, no two anchors share a
(file, line)location, and fixture and document metadata agree.B2 — false-healthy verdict for a namespace that never connected (fixed in
ca5f078d)missing_namespaceswas populated only while walkingconnected_servers, so a required namespace absent from that inventory was never counted. The reviewer's reproduction returnedattachment_healthy: true,discovery_status: namespaces_attached,error_type: Nonewith no attachment proof, and the gate text claimed "the host reports Connected" about a service nothing had reported Connected.The two conditions are now classified apart:
missing_namespaceskeeps its meaning — required, Connected at the host, absent from the session tool surface. This is the actual #708 condition and stays typedmcp_connected_namespaces_missing.not_connected_namespacesis new — required, absent from the connected-service inventory. Typedmcp_required_namespaces_not_connected, so a caller is never pointed at an attachment recovery for a service that never connected, and #708 is not collapsed into #672 or #584.attachment_healthyis false whenever any required namespace lacks attachment proof under either condition.error_typeslists every condition present so neither hides the other;error_typenames the primary one.namespace_conditionscarries the per-namespaceconnected/attached/conditionverdict, and_record_live_namespace_attachmentconsumes it instead of pinning the session-wideerror_typeonto every unattached namespace.attachment_gate_from_sessionstates only what the evidence supports: the Connected wording appears solely whenconnectedis true, the not-connected wording when it is false, and a neutral refusal when connected status was never recorded. Review and merge stay fail-closed in all three cases.The reviewer's exact reproduction, run against this head:
Two pre-existing cases in
tests/test_issue_708_mcp_namespace_attachment.pydeclared norequired_namespacesand so inherited a default set containing a namespace theirconnected_serverslist omitted. Under the corrected rule that is a false-healthy assertion, so each now declares the required set it actually means. This is called out explicitly because it is a change to existing test expectations, not only an addition.Tests added
tests/test_issue_708_not_connected_classification.py— 22 cases covering required-connected-and-attached, connected-but-unattached, not-connected-and-not-attached, mixed required namespaces where one is connected/attached and one is not connected, both conditions present at once, unknown / partial / malformed / contradictory evidence, review and merge behaviour for both failure categories, a store entry with no connected evidence, cross-session isolation, and secret-free telemetry.Verification
Every figure below was produced during this remediation run. The two full-suite runs were executed serially.
tests/test_issue_708_not_connected_classification.py(new)tests/test_issue_956_threat_model.py58bd852117ba1ff035eeFailing identifiers were compared directly, not counts. The two sets are identical: 0 failing only at head, 0 failing only at base — no head-only failure requires classification. The B1 regression
tests/test_issue_956_threat_model.py::ThreatModelStructureTests::test_records_the_commit_it_was_generated_againstis absent from the head set.The single failure in the tool-registration scope is
tests/test_issue_781_edit_issue_tool.py::TestDocumentationMatchesRegistry::test_documented_inventory_equals_registered_tools, which reportsgitea_rebind_dirty_same_claimant_author_session,gitea_reconcile_after_restartandgitea_recover_dirty_orphaned_issue_worktreeas registered-but-undocumented. It fails identically at the unmodified base, is unrelated to this branch, and appears in both full-suite failing sets.Correction to the earlier verification record. The previous description claimed
tests/test_issue_956_threat_model.pypassed 17/17 at126d76ad. That was false — the suite was red at that head, exactly as review 637 found. It passes 17/17 at this head. The previous description also claimedtests/test_mirror_refs.py::TestDryRunBanner::test_dry_run_banner_shown_by_defaultfails on the unmodified base in isolation. That claim is withdrawn and is not repeated: the test appears in neither full-suite failing set in this run, and no evidence for it was reproduced here.Test-harness note: concurrent full suites are not trustworthy here
An earlier attempt ran the head and base full suites concurrently and produced head 40 failed / 5898 passed against base 31 failed / 5861 passed, with the head run finishing in 178s against the base run's 512s. Those figures were discarded and both suites re-run serially, which is where the table above comes from.
The cause is shared mutable state rather than anything in this branch. Two suites running at once contend over the same pinned session-state directory (
GITEA_MCP_SESSION_STATE_DIR, default~/.cache/gitea-tools/session-state) and the same control-plane SQLite database, both of which live outside the worktree and are therefore not isolated by running each suite from its ownbranches/checkout. Lock, lease, session and decision records written by one run are visible to the other, so failures appear that neither branch causes.This is recorded here as follow-up issue material only — no fix is attempted in this PR and no test-harness file is touched. A future issue should give the suite a per-run session-state directory and control-plane database so concurrent runs are isolated, or make the harness refuse to start when another run holds them.
Reviewer follow-ups F1-F3
Not addressed here; scope is held to the two blockers.
ATTACHMENT_GATED_TASKSmappings have no production call site. Confirmed: onlyreview_prandmerge_prare wired. The unwired mappings are inert rather than unsafe, since an unassessed namespace never gates.tests/test_issue_781_edit_issue_tool.py::TestDocumentationMatchesRegistry::test_documented_inventory_equals_registered_toolscomparesdocs/mcp-tool-inventory.mdagainst the live tool registry viamcp_tool_inventory.assess_inventory_drift, and it is currently red on three unconnected tools — which is the check working. Offered as evidence for the reviewer to re-judge, not as a change.Recovery-path statement
The recovery path added here does not use direct module import, CLI or raw Gitea API mutation, profile hopping, or session-state overrides. The only offered action is connecting the required server and the sanctioned client reconnect, followed by full preflight.
Handoff
WHO_IS_NEXT: reviewer — fresh independent review requested at head
58bd8521880fe464d5d816b11d1001ffaf7cbdfaagainst the #708 acceptance criteria and the review 637 blockers. Authored through the livegitea-authornamespace (gitea_whoamiverifiedjcwalker3/prgs-author). Do not self-review or merge from the author role.repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #967
issue: #708
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: reviewer-967-126d76ad
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr967-126d76ad
phase: claimed
candidate_head:
126d76ad28target_branch: master
target_branch_sha:
17ba1ff035last_activity: 2026-07-28T21:31:41Z
expires_at: 2026-07-28T21:41:41Z
blocker: none
repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #967
issue: #708
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: reviewer-967-126d76ad
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr967-126d76ad
phase: claimed
candidate_head:
126d76ad28target_branch: master
target_branch_sha:
17ba1ff035last_activity: 2026-07-28T21:45:06Z
expires_at: 2026-07-28T21:55:06Z
blocker: none
REQUEST_CHANGES — PR #967 (Issue #708) at head
126d76ad2871f0782d5c6f40d53cc400557f0052Reviewed
17ba1ff035ee3154a0f2dcacbefe107457cca33f->126d76ad. Reviewersysadmin/prgs-reviewer, distinct from authorjcwalker3.What holds up
The core wiring is genuine and load-bearing. Confirmed in code and by execution against the head module:
_namespace_attachment_gate("review_pr")sits inside_evaluate_pr_review_submissionunderif live:(gitea_mcp_server.py:7638), immediately after the #543 health gate, andgitea_submit_pr_reviewreaches it withlive=True(gitea_mcp_server.py:9947)._namespace_attachment_gate("merge_pr")is Gate 0c ingitea_merge_pr(gitea_mcp_server.py:11200).merge_pr; attachment clears the block.mcp_connected_namespaces_missingcarries per-namespaceproof_of_connected_vs_attached{connected, attached}.exact_next_actionoffers the client reconnect path only, andsanctioned_recovery_toolnamesgitea_request_mcp_reconnect. No unsafe fallback route appears anywhere in the added surface.telemetryblock carries counts and namespace names only — no credentials, endpoints, environment values, or filesystem paths.mcp.list_tools()returns 125 tools includinggitea_assess_mcp_namespace_attachment, withgitea_assess_mcp_namespace_healthintact.(file, line)location._STARTUP_PARITYstartup-head assignment reported at pickup is absent from the diff and from both commitse3fa3b2and126d76a.lease-419dd32b2fc046deon issue #711, which was not touched.Blocking
B1 — this branch introduces a deterministic test failure, and the PR states the opposite
tests/test_issue_956_threat_model.py::ThreatModelStructureTests::test_records_the_commit_it_was_generated_againstfails at head and passes at base.17ba1ff, targeted run: 17 passed126d76ad, targeted run: fails17ba1ff: 28 failed / 5864 passed / 6 skipped / 1047 subtests126d76ad: 29 failed / 5887 passed / 6 skipped / 1047 subtestsCause: commit
126d76asetdocs/remote-mcp/threat-model-anchors.jsongenerated_against_committoe3fa3b263d4b8a04b189e111a345ac5c3a3fe1b6, whiledocs/remote-mcp/threat-model.md:8still statesa143cd065ba06e1a2bdc5143a19ec156e53650ef. The test asserts the fixture value appears in the document. At the preceding commite3fa3b2the fixture still carrieda143cd06and the test passed, so the failure was introduced by the final commit — the one whose message is about restamping that very value.The PR description and the issue ledger both assert
tests/test_issue_956_threat_model.pypasses 17/17 at this head. That is false at126d76ad.Required: bring the document and the fixture into agreement — update
docs/remote-mcp/threat-model.md:8to citee3fa3b263d4b8a04b189e111a345ac5c3a3fe1b6, or revert the fixture stamp. Then re-run at the new head and correct the verification table.B2 — a required namespace that never connected produces a healthy verdict
assess_connected_namespace_attachmentappends tomissing_namespacesonly for namespaces present inconnected_servers. A required namespace absent fromconnected_serversis recorded inproof_of_connected_vs_attachedas{connected: false, attached: false}but is never counted as missing.Executed against the head module:
gitea-mergeris required and has no attachment proof, yet the top-level verdict reports the session fully attached. Issue #708 AC4 requires no healthy final report without namespace attachment proof; a caller or agent readingattachment_healthy: truewould conclude the session is sound.Two consequences:
attachment_healthy = len(missing) == 0 and len(connected) > 0is satisfied by any single connected namespace, however many required namespaces are absent.attachment_gate_from_sessiondoes still blockmerge_prhere, so the gate itself fails closed correctly — but the text reads "the host reports Connected but the namespace is not attached" for a namespace the host does not report Connected at all. Issue #708 explicitly forbids collapsing this condition into config drift (#672) or transport-closed (#584), and this wording does exactly that, pointing an operator at the wrong recovery.Required: count required-but-not-connected namespaces distinctly, hold
attachment_healthyfalse whenever any required namespace lacks attachment proof, and emit a reason that separates not-connected from Connected-but-unattached.Non-blocking — please address or file
_LIVE_NAMESPACE_ATTACHMENTis a process-global dict with no TTL, unlike_REVIEW_DECISION_LOCK, which is keyed by remote plus profile identity with a TTL.discovery_cache_age_secondsis accepted as an argument and surfaced in telemetry, but no gate consults it. An attached verdict therefore persists for the daemon's lifetime even if the namespace later detaches — the fail-open direction, and attachment state is precisely what #708 says changes at runtime. Cross-session clearing is not possible, since the store is per-process.ATTACHMENT_GATED_TASKSmapsreview_pr,submit_review,merge_pr,work_issueandcreate_pr, but onlyreview_prandmerge_prhave call sites. Three of five mappings gate nothing.docs/mcp-tool-inventory.mdto the live registry, so the new entry can drift silently.The reported mirror-reference failure
tests/test_mirror_refs.py::TestDryRunBanner::test_dry_run_banner_shown_by_defaultdid not reproduce:17ba1ff, isolation, three consecutive runs: passedThe claim that it fails on the unmodified base in isolation is contradicted. It is neither a regression from this branch nor the source of the head-versus-base delta. The actual delta is B1.
Issue-lock timing
Commits
e3fa3b2(20:46:09Z) and126d76a(20:46:37Z) predate the reported issue-lock expiry at 20:50:12Z; PR publication and the ledger comment landed at 20:50:49Z, after it. No competing claimant acquired #708 at any point. Ownership of PR #967 is sound. The lapse reflects a workflow-control defect — a short issue-lock TTL against a multi-minute publish sequence — and deserves its own issue, but it does not invalidate this PR.Canonical PR State
STATE:
changes-requested
WHO_IS_NEXT:
author
NEXT_ACTION:
Fix B1 (bring
docs/remote-mcp/threat-model.md:8and the anchors fixturegenerated_against_commitinto agreement) and B2 (required-but-not-connected namespaces must not yield a healthy verdict, and must emit a distinct reason), then re-run the #956 and #708 suites plus a full suite at the new head and correct the verification claims.NEXT_PROMPT:
WHAT_HAPPENED:
Independent reviewer session on the prgs-reviewer namespace reviewed the full base-to-head diff at
126d76adagainst the six #708 acceptance criteria. The attachment detection is wired into real production call paths and the gate behaviour was proven by execution, but two defects block the merge: a deterministic test regression introduced by the final commit, and a false-healthy verdict for required namespaces that never connected.WHY:
The #956 threat-model regression is the single identifier separating the head failing set from the base failing set, and the PR asserts that suite passes at this head, so the verification record is inaccurate as well as red. The false-healthy verdict contradicts #708 AC4 and reuses the Connected-but-unattached wording for a not-connected namespace, which collapses this condition into #672 and #584 exactly as the issue forbids.
ISSUE:
#708
HEAD_SHA:
126d76ad28REVIEW_STATUS:
request_changes
MERGE_READY:
no
BLOCKERS:
B1 — tests/test_issue_956_threat_model.py::ThreatModelStructureTests::test_records_the_commit_it_was_generated_against fails at head
126d76adand passes at base 17ba1ff; docs/remote-mcp/threat-model.md:8 citesa143cd065bwhile the anchors fixture generated_against_commit ise3fa3b263d. B2 — assess_connected_namespace_attachment returns attachment_healthy True, discovery_status namespaces_attached, missing_namespaces empty and error_type None when a required namespace was never connected, and the gate reason misattributes that state to a Connected host.VALIDATION:
Focused #708 suites 24 passed at head. Namespace/session/discoverability/reconnect/registration sweep 108 passed at head. Review/merge/runtime gate sweep 264 passed at head. tests/test_issue_956_threat_model.py 17 passed at base
17ba1ffand failing at head126d76ad. Full suite head126d76ad29 failed / 5887 passed / 6 skipped / 1047 subtests versus base17ba1ff28 failed / 5864 passed / 6 skipped / 1047 subtests; failing identifier sets compared directly, head equals base plus exactly the #956 identifier, with nothing failing only at base. All 58 threat-model anchors resolve at head with no duplicate file-line locations. Tool registration proven by mcp.list_tools() returning 125 tools including gitea_assess_mcp_namespace_attachment. Gate behaviour proven by execution against the head module. tests/test_mirror_refs.py::TestDryRunBanner::test_dry_run_banner_shown_by_default passed three consecutive isolated runs at both head and base and appears in neither full-suite failing set.LAST_UPDATED_BY:
prgs-reviewer (sysadmin), native runtime at
17ba1ff035, daemon pid 30511, session reviewer-967-126d76ad.repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #967
issue: #708
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: reviewer-967-126d76ad
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr967-126d76ad
phase: released
candidate_head:
126d76ad28target_branch: master
target_branch_sha:
17ba1ff035last_activity: 2026-07-28T21:47:53Z
expires_at: 2026-07-28T21:57:53Z
blocker: manual-release
repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #967
issue: none
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: reviewer-967-58bd8521
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr967-58bd8521
phase: claimed
candidate_head:
58bd852188target_branch: master
target_branch_sha: none
last_activity: 2026-07-28T23:23:05Z
expires_at: 2026-07-28T23:33:05Z
blocker: none
APPROVE — PR #967 (Issue #708) at head
58bd8521880fe464d5d816b11d1001ffaf7cbdfaFresh independent review after review 637 (REQUEST_CHANGES at
126d76ad, preserved stale). Reviewersysadmin/prgs-reviewer/ namespacegitea-reviewer, distinct from authorjcwalker3. Base17ba1ff035ee→ head58bd8521.Preflight (live)
prgs-author,prgs-reviewer,prgs-reconciler,prgs-merger,prgs-controller.GITEA_CLIENT_MANAGED=1; launch command usesvenv/bin/python3(Homebrew-resolved binary is expected).gitea-toolsalias; no duplicate / foreign cohort.sysadmin, profileprgs-reviewer, rolereviewer;gitea_resolve_task_capability(review_pr)ready,stop_required=false.17ba1ff035ee;mutation_safe=true.58bd8521880fe464d5d816b11d1001ffaf7cbdfa.126d76ad(stale).Lease: acquired
reviewer-967-58bd8521on worktreebranches/review-pr967-58bd8521.B1 — threat-model generation metadata (
58bd8521) — verified fixeddocs/remote-mcp/threat-model-anchors.jsongenerated_against_commit=ca5f078d8a575ea3e2991771f8b4ea85e3dcaaa0.docs/remote-mcp/threat-model.mdcites the same full SHA; metadata agree.(file, line)pairs.tests/test_issue_956_threat_model.py: 17 passed at this head (the deterministic B1 failure at126d76adis gone).B2 — disconnected vs connected-but-unattached (
ca5f078d) — verified fixedIndependent execution against the head module:
Also confirmed:
attachment_healthy=True,namespaces_attachedmcp_connected_namespaces_missing, not healthymcp_required_namespaces_not_connected, not healthydiscovery_status=disconnected, not healthycontradictory_namespacessetProduction wiring re-confirmed at head:
_namespace_attachment_gate("review_pr")in live review path (gitea_mcp_server.py:7654)._namespace_attachment_gate("merge_pr")Gate 0c (gitea_mcp_server.py:11218)._record_live_namespace_attachmentconsumes per-namespacenamespace_conditionsrather than pinning session-wideerror_type.Focused tests (serial, impact-aware only)
tests/test_issue_708_not_connected_classification.pytests/test_issue_956_threat_model.pyDid not re-run full head/base suites; author already recorded identical serial failure-ID sets with zero head-only failures.
Non-blocking (unchanged from 637; not merge-blocking)
ATTACHMENT_GATED_TASKSmappings remain without production call sites; inert because unassessed namespaces do not gate.Scope
Diff is limited to #708 attachment gating, docs, threat-model restamp, and focused tests. No unrelated behavior expansion observed.
Canonical PR State
STATE:
approved
WHO_IS_NEXT:
merger
NEXT_ACTION:
Merger role: pre-merge recheck at exact head
58bd8521880fe464d5d816b11d1001ffaf7cbdfa, then merge only with explicit operator authorization. Do not merge from the reviewer session.NEXT_PROMPT:
WHAT_HAPPENED:
Fresh independent reviewer on gitea-reviewer / prgs-reviewer / sysadmin re-verified both review-637 blockers at head
58bd8521and approved.WHY:
B1 metadata agreement and 58/58 anchor resolution hold; B2 false-healthy path is closed with distinct typing and fail-closed review/merge gates.
ISSUE:
#708
HEAD_SHA:
58bd852188REVIEW_STATUS:
approved
MERGE_READY:
true
BLOCKERS:
none
VALIDATION:
Independent module probes for B2 cases; 58/58 anchors at gen commit and head; focused pytest classification 22, three #708 files 46, #956 17, related namespace 35; review 637 preserved stale against 126d76ad; no pre-existing approval at head.
NATIVE_REVIEW_PROOF: transport=native_mcp; entrypoint=mcp_server; token_fingerprint=cf2c64a09acba71c; pid=36688; profile=prgs-reviewer; identity=sysadmin; session_id=reviewer-967-58bd8521
LAST_UPDATED_BY:
prgs-reviewer (sysadmin), native runtime parity
17ba1ff035, session reviewer-967-58bd8521.repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #967
issue: none
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: reviewer-967-58bd8521
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr967-58bd8521
phase: released
candidate_head:
58bd852188target_branch: master
target_branch_sha: none
last_activity: 2026-07-28T23:26:08Z
expires_at: 2026-07-28T23:36:08Z
blocker: manual-release
repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #967
issue: #708
reviewer_identity: sysadmin
profile: prgs-merger
session_id: merger-967-58bd8521
worktree: branches/review-pr967-58bd8521
phase: claimed
candidate_head:
58bd852188target_branch: master
target_branch_sha:
17ba1ff035last_activity: 2026-07-28T23:30:46Z
expires_at: 2026-07-28T23:40:46Z
blocker: none
Stale #332 review-decision lock cleanup (#594)
Status: APPLIED
sysadminprgs-merger2026-07-28T23:31:18.308986+00:00approveon PR feat(mcp): gate Connected-but-unattached MCP namespaces (Closes #708) (#967)closed(merged=True)8eada1fbe45289d6b92d291b3d5e5e56ae64ac5d2prgs-reviewerManual deletion of session-state files is not the workflow.
This path only clears a lock when the referenced PR is merged/closed.
repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #967
issue: #708
reviewer_identity: sysadmin
profile: prgs-merger
session_id: merger-967-58bd8521
worktree: branches/review-pr967-58bd8521
phase: released
candidate_head:
58bd852188target_branch: master
target_branch_sha:
17ba1ff035last_activity: 2026-07-28T23:40:15Z
expires_at: 2026-07-28T23:50:15Z
blocker: post-merge-moot