Make MCP runtime generation ownership client/session-aware so multiple LLMs can work concurrently #948

Closed
opened 2026-07-27 03:44:40 -05:00 by jcwalker3 · 2 comments
Owner

Problem

The control plane currently treats legitimate MCP generations launched by different active LLM clients as globally conflicting duplicates. Claude, Codex, Gemini, Grok, Antigravity, or other clients cannot simultaneously use the control plane, even when they are working on different issues.

The global “exactly one MCP generation” mutation gate is the defect. Concurrency ownership should be scoped to authenticated client/session/generation identity, while issue ownership should be enforced independently through atomic leases, heartbeats, expiration, and fencing.

Required behavior

  • Current behavior: a second legitimate client generation triggers a fleet-wide stale-runtime mutation wall.
  • Desired behavior: multiple active LLM clients can use MCP concurrently.
  • Different clients may author different issues simultaneously.
  • Each issue is atomically locked to one author workflow without manual assignment.
  • Lock heartbeats renew ownership automatically.
  • When heartbeats cease, the lock expires after the configured timeout.
  • Takeover creates a new fencing epoch/token.
  • An expired or superseded session cannot mutate if it resumes.
  • The Gitea assignee field is visibility metadata, not ownership authority.
  • Reviewer identity remains independent from the author.
  • Merge still requires current-head approval and no unresolved change request.
  • Restart, reconnect, and stale-runtime diagnostics must be scoped to the affected client generation instead of globally blocking unrelated healthy clients.

Do not prematurely require either a shared fleet or one fleet per client. The implementation may choose the repository-appropriate architecture, but it must satisfy the behavioral contract.

Acceptance criteria

  1. Every client connection has a stable, non-LLM-forgeable client_instance_id.
  2. Every client session has a distinct session_id.
  3. Every daemon launch has a generation_id or boot epoch.
  4. Runtime ownership records include client, session, generation, profile/namespace, process, start time, liveness, and applicable repository binding.
  5. Multiple healthy generations owned by different authenticated client sessions do not trigger a global mutation wall.
  6. An unintended duplicate within the same ownership scope remains blocked or is resolved through an explicit fenced replacement protocol.
  7. Abandoned generations are detected through authoritative liveness evidence, not PID comparison alone.
  8. Stale or superseded generations cannot mutate after a replacement generation becomes authoritative.
  9. work_issue atomically acquires an issue lease; no operator assignment step is required.
  10. The lease records repository, issue, claimant identity, client, session, branch, registered worktree, lease epoch/fencing token, and expiration.
  11. Automatic heartbeats renew only the owning lease.
  12. Two clients racing for the same issue produce exactly one winner.
  13. Different clients can mutate different leased issues concurrently.
  14. After expiration, a new author may acquire the issue with a higher fencing epoch.
  15. The prior session remains fenced if it reconnects or resumes.
  16. All issue-scoped mutations validate the current fencing token immediately before mutation.
  17. A stale generation affects only its own ownership scope unless authoritative evidence proves a wider safety problem.
  18. Reviewer independence is checked by identity, not merely client or session ID.
  19. Approval applies only to the exact reviewed PR head.
  20. Any unresolved current-head REQUEST_CHANGES prevents merging.
  21. Valid current-head approval permits the sanctioned LLM merger to complete the merge without returning it to the operator.
  22. Migration from legacy runtime records fails closed without permanently preventing legitimate clients from reconnecting.
  23. Diagnostics name the conflicting owner, affected scope, liveness evidence, recoverability, and exact sanctioned next action.
  24. End-to-end tests run multiple simulated clients and their role namespaces concurrently.
  25. Regression tests cover:
    • four healthy LLM clients working on four different issues;
    • two clients racing for one issue;
    • heartbeat renewal;
    • lease expiration and takeover;
    • stale-session resurrection;
    • same-owner duplicate generation;
    • healthy generations owned by different clients;
    • one stale generation alongside unrelated healthy generations;
    • author/reviewer independence;
    • stale approval after a head change;
    • blocking change requests;
    • successful sanctioned LLM merge.
  26. worker_identity is independent of role and profile. Neither a role nor a profile is ever used as, or derived into, the unique identity of a worker.
  27. Each LLM instance establishes a collision-resistant worker_identity.
  28. The supported identity format carries the LLM name, a UTC timestamp, and a collision-resistant digest, for example <llm-name>-<UTC-timestamp>-<short-sha>.
  29. The digest incorporates sufficient entropy — session identity, high-resolution time, and a random nonce, for example sha256(llm_name + session_id + timestamp_ns + random_nonce) — or an equivalently collision-resistant construction.
  30. Worker registration is atomic.
  31. Registering a worker_identity that is already active fails closed, without replacing, adopting, merging with, or corrupting the existing worker registration.
  32. After a collision, the new worker generates a different identity and atomically registers that replacement.
  33. A genuinely restarted worker receives a new worker_identity.
  34. A restarted worker cannot silently inherit the previous worker's leases or fencing tokens.
  35. Automated tests deliberately force an identity collision and verify rejection of the colliding registration, safe regeneration, and successful registration of the replacement identity.
  36. worker_identity is unique.
  37. Role is not unique and may be shared concurrently by multiple healthy workers.
  38. Profile is not unique and may be shared concurrently by multiple healthy workers.
  39. Exclusivity applies to an active lease target, never to a role or a profile.
  40. Multiple healthy workers sharing the same role or profile do not trigger a profile-wide duplicate-process block, stale-runtime block, reconnect requirement, or mutation block.
  41. Stale-runtime and reconnect failures are scoped only to the affected worker_identity or generation.
  42. Issue #900 is reconciled: cohort lifecycle supervision detects and reaps abandoned or genuinely superseded instances without blocking or terminating legitimate, independently identified workers that share a profile.
  43. The behavior introduced by issue #686 is reconciled: duplicate detection distinguishes reuse of the same worker_identity from valid concurrent workers holding different identities under the same role/profile.
  44. Terminating all additional processes that share a role or profile is explicitly rejected as a valid implementation of this issue.

Identity invariants

Roles and profiles remain reusable capability definitions, not singleton worker identities:

UNIQUE: worker_identity
NOT UNIQUE: role
NOT UNIQUE: profile
EXCLUSIVE: active lease target

Criteria 26–44 and these invariants are recorded from scope-clarification comment 17651.

Non-goals and prohibited fixes

  • Do not require manual issue assignment.
  • Do not globally serialize all LLM clients.
  • Do not identify ownership using PID alone.
  • Do not trust an LLM-supplied identity without authenticated runtime binding.
  • Do not weaken issue locks, duplicate-work protection, approval rules, or merge gates.
  • Do not special-case Claude, Codex, Gemini, Grok, Antigravity, or particular issue numbers.
  • Do not use prompt-only workarounds.
  • Do not require operators to close unrelated healthy clients.
  • Do not use raw Git or direct API fallbacks.
## Problem The control plane currently treats legitimate MCP generations launched by different active LLM clients as globally conflicting duplicates. Claude, Codex, Gemini, Grok, Antigravity, or other clients cannot simultaneously use the control plane, even when they are working on different issues. The global “exactly one MCP generation” mutation gate is the defect. Concurrency ownership should be scoped to authenticated client/session/generation identity, while issue ownership should be enforced independently through atomic leases, heartbeats, expiration, and fencing. ## Required behavior * Current behavior: a second legitimate client generation triggers a fleet-wide stale-runtime mutation wall. * Desired behavior: multiple active LLM clients can use MCP concurrently. * Different clients may author different issues simultaneously. * Each issue is atomically locked to one author workflow without manual assignment. * Lock heartbeats renew ownership automatically. * When heartbeats cease, the lock expires after the configured timeout. * Takeover creates a new fencing epoch/token. * An expired or superseded session cannot mutate if it resumes. * The Gitea assignee field is visibility metadata, not ownership authority. * Reviewer identity remains independent from the author. * Merge still requires current-head approval and no unresolved change request. * Restart, reconnect, and stale-runtime diagnostics must be scoped to the affected client generation instead of globally blocking unrelated healthy clients. Do not prematurely require either a shared fleet or one fleet per client. The implementation may choose the repository-appropriate architecture, but it must satisfy the behavioral contract. ## Acceptance criteria 1. Every client connection has a stable, non-LLM-forgeable `client_instance_id`. 2. Every client session has a distinct `session_id`. 3. Every daemon launch has a `generation_id` or boot epoch. 4. Runtime ownership records include client, session, generation, profile/namespace, process, start time, liveness, and applicable repository binding. 5. Multiple healthy generations owned by different authenticated client sessions do not trigger a global mutation wall. 6. An unintended duplicate within the same ownership scope remains blocked or is resolved through an explicit fenced replacement protocol. 7. Abandoned generations are detected through authoritative liveness evidence, not PID comparison alone. 8. Stale or superseded generations cannot mutate after a replacement generation becomes authoritative. 9. `work_issue` atomically acquires an issue lease; no operator assignment step is required. 10. The lease records repository, issue, claimant identity, client, session, branch, registered worktree, lease epoch/fencing token, and expiration. 11. Automatic heartbeats renew only the owning lease. 12. Two clients racing for the same issue produce exactly one winner. 13. Different clients can mutate different leased issues concurrently. 14. After expiration, a new author may acquire the issue with a higher fencing epoch. 15. The prior session remains fenced if it reconnects or resumes. 16. All issue-scoped mutations validate the current fencing token immediately before mutation. 17. A stale generation affects only its own ownership scope unless authoritative evidence proves a wider safety problem. 18. Reviewer independence is checked by identity, not merely client or session ID. 19. Approval applies only to the exact reviewed PR head. 20. Any unresolved current-head `REQUEST_CHANGES` prevents merging. 21. Valid current-head approval permits the sanctioned LLM merger to complete the merge without returning it to the operator. 22. Migration from legacy runtime records fails closed without permanently preventing legitimate clients from reconnecting. 23. Diagnostics name the conflicting owner, affected scope, liveness evidence, recoverability, and exact sanctioned next action. 24. End-to-end tests run multiple simulated clients and their role namespaces concurrently. 25. Regression tests cover: * four healthy LLM clients working on four different issues; * two clients racing for one issue; * heartbeat renewal; * lease expiration and takeover; * stale-session resurrection; * same-owner duplicate generation; * healthy generations owned by different clients; * one stale generation alongside unrelated healthy generations; * author/reviewer independence; * stale approval after a head change; * blocking change requests; * successful sanctioned LLM merge. 26. `worker_identity` is independent of role and profile. Neither a role nor a profile is ever used as, or derived into, the unique identity of a worker. 27. Each LLM instance establishes a collision-resistant `worker_identity`. 28. The supported identity format carries the LLM name, a UTC timestamp, and a collision-resistant digest, for example `<llm-name>-<UTC-timestamp>-<short-sha>`. 29. The digest incorporates sufficient entropy — session identity, high-resolution time, and a random nonce, for example `sha256(llm_name + session_id + timestamp_ns + random_nonce)` — or an equivalently collision-resistant construction. 30. Worker registration is atomic. 31. Registering a `worker_identity` that is already active fails closed, without replacing, adopting, merging with, or corrupting the existing worker registration. 32. After a collision, the new worker generates a different identity and atomically registers that replacement. 33. A genuinely restarted worker receives a new `worker_identity`. 34. A restarted worker cannot silently inherit the previous worker's leases or fencing tokens. 35. Automated tests deliberately force an identity collision and verify rejection of the colliding registration, safe regeneration, and successful registration of the replacement identity. 36. `worker_identity` is unique. 37. Role is not unique and may be shared concurrently by multiple healthy workers. 38. Profile is not unique and may be shared concurrently by multiple healthy workers. 39. Exclusivity applies to an active lease target, never to a role or a profile. 40. Multiple healthy workers sharing the same role or profile do not trigger a profile-wide duplicate-process block, stale-runtime block, reconnect requirement, or mutation block. 41. Stale-runtime and reconnect failures are scoped only to the affected `worker_identity` or generation. 42. Issue #900 is reconciled: cohort lifecycle supervision detects and reaps abandoned or genuinely superseded instances without blocking or terminating legitimate, independently identified workers that share a profile. 43. The behavior introduced by issue #686 is reconciled: duplicate detection distinguishes reuse of the same `worker_identity` from valid concurrent workers holding different identities under the same role/profile. 44. Terminating all additional processes that share a role or profile is explicitly rejected as a valid implementation of this issue. ### Identity invariants Roles and profiles remain reusable capability definitions, not singleton worker identities: ```text UNIQUE: worker_identity NOT UNIQUE: role NOT UNIQUE: profile EXCLUSIVE: active lease target ``` Criteria 26–44 and these invariants are recorded from scope-clarification comment 17651. ## Non-goals and prohibited fixes * Do not require manual issue assignment. * Do not globally serialize all LLM clients. * Do not identify ownership using PID alone. * Do not trust an LLM-supplied identity without authenticated runtime binding. * Do not weaken issue locks, duplicate-work protection, approval rules, or merge gates. * Do not special-case Claude, Codex, Gemini, Grok, Antigravity, or particular issue numbers. * Do not use prompt-only workarounds. * Do not require operators to close unrelated healthy clients. * Do not use raw Git or direct API fallbacks.
jcwalker3 added the type:featurestatus:ready labels 2026-07-27 03:44:41 -05:00
Author
Owner

P0 scope clarification: worker identity collision handling

This issue is the top-priority concurrency blocker limiting how many independent LLM workers can operate simultaneously.

Please make the following requirements explicit in the implementation and acceptance criteria:

  1. worker_identity must remain independent of role and profile.

  2. Each LLM instance must establish a collision-resistant identity, for example:

    <llm-name>-<UTC-timestamp>-<short-sha>

  3. The digest should incorporate sufficient entropy, such as:

    sha256(llm_name + session_id + timestamp_ns + random_nonce)

  4. Worker registration must be atomic.

  5. A registration collision must be detected and rejected without replacing, adopting, or corrupting the existing worker.

  6. After a collision, the new worker must generate and atomically register a different identity.

  7. A restarted worker must receive a new identity and must not silently inherit the previous worker's leases or fencing tokens.

  8. Automated tests must deliberately force an identity collision and verify safe regeneration and registration.

  9. Roles and profiles must remain reusable capability definitions:

    • UNIQUE: worker_identity
    • NOT UNIQUE: role
    • NOT UNIQUE: profile
    • EXCLUSIVE: active lease target
  10. Multiple healthy workers sharing the same role/profile must never trigger a profile-wide duplicate-process or stale-runtime block.

Required issue reconciliation

Issue #900 appears to require one eligible runtime cohort per profile. That requirement conflicts with #948 if "cohort" or profile eligibility permits only one LLM worker.

Implementation of #948 must explicitly reconcile #900 so that lifecycle supervision may detect abandoned or superseded instances without terminating or blocking legitimate, independently identified workers sharing the same profile.

Issue #686 should also be revisited because its duplicate-role-server guardrail must distinguish:

  • accidental duplicate registration of the same worker identity; from
  • valid concurrent workers with different identities using the same role/profile.

Killing every additional process using a shared profile is not an acceptable resolution.

Canonical Issue State

STATE: ready
WHO_IS_NEXT: author
NEXT_ACTION: Amend the #948 acceptance criteria to add the ten worker-identity collision requirements above as explicit numbered criteria, add the forced-collision regression test to AC25, and record the #900 and #686 reconciliation decisions in the issue body before implementation starts.
NEXT_PROMPT:

Amend Gitea-Tools issue #948 acceptance criteria per the scope-clarification comment.

Repository: Scaled-Tech-Consulting/Gitea-Tools
Profile: prgs-author
Identity: jcwalker3
Target issue: #948

Add as explicit acceptance criteria:
1. worker_identity is independent of role and profile.
2. Identity format <llm-name>-<UTC-timestamp>-<short-sha>.
3. Digest entropy sha256(llm_name + session_id + timestamp_ns + random_nonce).
4. Worker registration is atomic.
5. A registration collision is detected and rejected without replacing, adopting, or corrupting the existing worker.
6. After a collision the new worker generates and atomically registers a different identity.
7. A restarted worker receives a new identity and inherits no prior leases or fencing tokens.
8. A regression test deliberately forces an identity collision and verifies safe regeneration.
9. UNIQUE worker_identity; NOT UNIQUE role; NOT UNIQUE profile; EXCLUSIVE active lease target.
10. Multiple healthy workers sharing a role/profile never trigger a profile-wide duplicate-process or stale-runtime block.

Also record in the issue body how #900 (one eligible cohort per profile) and #686
(duplicate-role-server guardrail) are reconciled with #948.

Do not implement code, create a branch, or open a PR in this task.

WHAT_HAPPENED: A duplicate-issue scan for a proposed new P0 issue titled "Support concurrent per-LLM workers independently of shared roles and profiles" found #948 already covers that architectural defect. No duplicate issue was created. This comment records the one requirement gap found during that comparison plus two cross-issue conflicts.
WHY: #948 AC1 requires a stable, non-LLM-forgeable client identity but never states what happens when registration collides. Without an explicit collision requirement an implementation may satisfy AC1 by adopting or overwriting an existing registration, which silently transfers ownership and defeats the lease and fencing guarantees in AC10, AC14, and AC16. #900 and #686 are named because both encode the one-worker-per-profile assumption that #948 exists to remove; leaving them unreconciled would let a correct #948 implementation be reverted by the guardrails that remain.
RELATED_PRS: none
BLOCKERS: none
VALIDATION: Live state re-read at comment time. #948 open, labels status:ready and type:feature, comment thread empty before this comment. Native duplicate scan over all open issues and 120 closed issues found no equivalent clarification. Related issues confirmed open: #900, #887, #937, #797. Related issues confirmed closed: #686, #613. Runtime parity verified in agreement at ed9414ebda with live_stale false, mutation_safe true, restart_required false. No code, branch, commit, or pull request was produced by this task.
LAST_UPDATED_BY: jcwalker3 (prgs-author)

## P0 scope clarification: worker identity collision handling This issue is the top-priority concurrency blocker limiting how many independent LLM workers can operate simultaneously. Please make the following requirements explicit in the implementation and acceptance criteria: 1. `worker_identity` must remain independent of role and profile. 2. Each LLM instance must establish a collision-resistant identity, for example: `<llm-name>-<UTC-timestamp>-<short-sha>` 3. The digest should incorporate sufficient entropy, such as: `sha256(llm_name + session_id + timestamp_ns + random_nonce)` 4. Worker registration must be atomic. 5. A registration collision must be detected and rejected without replacing, adopting, or corrupting the existing worker. 6. After a collision, the new worker must generate and atomically register a different identity. 7. A restarted worker must receive a new identity and must not silently inherit the previous worker's leases or fencing tokens. 8. Automated tests must deliberately force an identity collision and verify safe regeneration and registration. 9. Roles and profiles must remain reusable capability definitions: - `UNIQUE: worker_identity` - `NOT UNIQUE: role` - `NOT UNIQUE: profile` - `EXCLUSIVE: active lease target` 10. Multiple healthy workers sharing the same role/profile must never trigger a profile-wide duplicate-process or stale-runtime block. ## Required issue reconciliation Issue #900 appears to require one eligible runtime cohort per profile. That requirement conflicts with #948 if "cohort" or profile eligibility permits only one LLM worker. Implementation of #948 must explicitly reconcile #900 so that lifecycle supervision may detect abandoned or superseded instances without terminating or blocking legitimate, independently identified workers sharing the same profile. Issue #686 should also be revisited because its duplicate-role-server guardrail must distinguish: - accidental duplicate registration of the same worker identity; from - valid concurrent workers with different identities using the same role/profile. Killing every additional process using a shared profile is not an acceptable resolution. ## Canonical Issue State STATE: ready WHO_IS_NEXT: author NEXT_ACTION: Amend the #948 acceptance criteria to add the ten worker-identity collision requirements above as explicit numbered criteria, add the forced-collision regression test to AC25, and record the #900 and #686 reconciliation decisions in the issue body before implementation starts. NEXT_PROMPT: ```text Amend Gitea-Tools issue #948 acceptance criteria per the scope-clarification comment. Repository: Scaled-Tech-Consulting/Gitea-Tools Profile: prgs-author Identity: jcwalker3 Target issue: #948 Add as explicit acceptance criteria: 1. worker_identity is independent of role and profile. 2. Identity format <llm-name>-<UTC-timestamp>-<short-sha>. 3. Digest entropy sha256(llm_name + session_id + timestamp_ns + random_nonce). 4. Worker registration is atomic. 5. A registration collision is detected and rejected without replacing, adopting, or corrupting the existing worker. 6. After a collision the new worker generates and atomically registers a different identity. 7. A restarted worker receives a new identity and inherits no prior leases or fencing tokens. 8. A regression test deliberately forces an identity collision and verifies safe regeneration. 9. UNIQUE worker_identity; NOT UNIQUE role; NOT UNIQUE profile; EXCLUSIVE active lease target. 10. Multiple healthy workers sharing a role/profile never trigger a profile-wide duplicate-process or stale-runtime block. Also record in the issue body how #900 (one eligible cohort per profile) and #686 (duplicate-role-server guardrail) are reconciled with #948. Do not implement code, create a branch, or open a PR in this task. ``` WHAT_HAPPENED: A duplicate-issue scan for a proposed new P0 issue titled "Support concurrent per-LLM workers independently of shared roles and profiles" found #948 already covers that architectural defect. No duplicate issue was created. This comment records the one requirement gap found during that comparison plus two cross-issue conflicts. WHY: #948 AC1 requires a stable, non-LLM-forgeable client identity but never states what happens when registration collides. Without an explicit collision requirement an implementation may satisfy AC1 by adopting or overwriting an existing registration, which silently transfers ownership and defeats the lease and fencing guarantees in AC10, AC14, and AC16. #900 and #686 are named because both encode the one-worker-per-profile assumption that #948 exists to remove; leaving them unreconciled would let a correct #948 implementation be reverted by the guardrails that remain. RELATED_PRS: none BLOCKERS: none VALIDATION: Live state re-read at comment time. #948 open, labels status:ready and type:feature, comment thread empty before this comment. Native duplicate scan over all open issues and 120 closed issues found no equivalent clarification. Related issues confirmed open: #900, #887, #937, #797. Related issues confirmed closed: #686, #613. Runtime parity verified in agreement at ed9414ebda9034ca87b36a9fce1c1ff7f98090f6 with live_stale false, mutation_safe true, restart_required false. No code, branch, commit, or pull request was produced by this task. LAST_UPDATED_BY: jcwalker3 (prgs-author)
jcwalker3 added status:pr-open and removed status:ready labels 2026-07-29 01:55:48 -05:00
Author
Owner

Author handoff: implementation pushed, PR #968 open

Root cause

Two surfaces computed provenance independently and disagreed about one process.

gitea_get_runtime_context read the live os.environ and reported client_managed. mcp_namespace_health.classify_namespace_probe derived the flag from _safe_env_summary(process), whose SAFE_ENV_KEYS allowlist carries only GITEA_MCP_PROFILE, GITEA_PROFILE_NAME, GITEA_SERVICE, GITEA_EXECUTION_ROLE, GITEA_MCP_CONFIG — and none of the three provenance keys it then looked up. Those lookups could only return None, so that surface was structurally incapable of reporting anything but manual_launch for any process. An allowlist for deciding what may be echoed was deciding what may be believed.

Underneath, neither model could name which client or session owned a runtime. Ownership was inferred from a process environment flag the launcher sets once, which says nothing about who owns the process now — so a healthy daemon serving a second client could not be told apart from a duplicate, and the cohort scan walled every process sharing a profile.

Authoritative provenance model

mcp_worker_identity.py is the single authority for runtime context, namespace health, namespace attachment, the fleet/cohort scan, capability resolution, and typed reconnect assessment. It splits two claims the old code ran together:

  • launch provenance — hand-launched from a terminal? Answered from the environment, which is legitimate because the launcher sets it. The #686 wall is unchanged.
  • session ownership — which live client session owns this runtime now? Answered only by a live attachment record; no environment flag establishes it.

is_client_managed stays bound to the launch dimension, so unification changes which code decides, not what gets decided. Invariants: UNIQUE worker_identity, NOT UNIQUE role, NOT UNIQUE profile, EXCLUSIVE active lease target. Identity collision fails closed and never adopts the incumbent; a generation held by a live session cannot be claimed twice; a non-live claimant is superseded with a higher fencing epoch; liveness is heartbeat freshness, not PID comparison. Missing proof reports unproven rather than asserting manual_launch, and both fail closed.

Also closed: reconnect guidance no longer defaults to Codex (DEFAULT_CLIENT becomes generic, client resolved from the attachment record), and resolve_bound_remote keeps a PRGS-bound namespace off the dadeschools library default.

Files changed

mcp_worker_identity.py (new) · gitea_mcp_server.py · mcp_namespace_health.py · mcp_client_reconnect.py · tests/test_issue_948_client_session_provenance.py (new) · tests/test_issue_686_manual_mcp_provenance.py · docs/remote-mcp/threat-model.md · docs/remote-mcp/threat-model-anchors.json

Tests and results

  • pytest tests/test_issue_948_client_session_provenance.py — 43 passed
  • 9 directly affected suites — 181 passed, 18 subtests
  • 34 category suites (capability resolution, runtime context, namespace health, namespace attachment, fleet/provenance, reconnect, profile and repository binding, session state, process generation, SQLite/registry persistence) — 668 passed, 153 subtests
  • Full suite from a branches/ worktree, -p no:randomly: head 28F/5953P/6S vs merge base 8eada1fb 28F/5910P/6S; diff of failing-ID sets is empty, so the 28 are pre-existing by merge-base evidence, not assumption.

Head and deployment

Complete head SHA: fa510dd28dd5f9ca4f81e5b3048ceab584f0b7d7 (local, git ls-remote, and PR #968 agree). Serving daemons run the code from their boot commit, so the new surfaces appear only after the control checkout advances and the operator reconnects the MCP namespaces. No process kill, restart loop, raw API call, fallback transport, or configuration edit is required or was performed.

Scope note

This delivers the provenance model and the surfaces named above. Issue-lease acquisition via work_issue (AC9-AC16) and the merge-gate criteria (AC19-AC21) build on this model and are not in this change. Boundary with #934: that child defines provenance for a remote non-stdio transport and waits on #931/#932; this change governs the local stdio fleet and leaves every stdio guard in force.

[THREAD STATE LEDGER]

What is true now: PR #968 is open against master at head fa510dd28dd5f9ca4f81e5b3048ceab584f0b7d7, mergeable, authored by jcwalker3 under prgs-author. Issue #948 carries status:pr-open. No review verdict exists on PR #968 yet.

What changed: Two commits were pushed to feat/issue-948-client-session-provenance — the implementation plus a restamp of the #956 threat-model anchors onto the commit whose line numbers they resolve at. Gitea server-side state changed: PR #968 was created and the issue label set moved to status:pr-open.

What is blocked: Nothing. Blocker classification: no blocker.

Who/what acts next: An independent reviewer, because the author of this change must not review or merge it.

  • Server-side decision state: no review decision recorded on PR #968.
  • Local verdict/state: implementation complete and pushed; author self-review is forbidden, so no verdict was prepared locally.
  • Next actor: reviewer.
  • Required action: review PR #968 at head fa510dd28dd5f9ca4f81e5b3048ceab584f0b7d7 and post a formal verdict through the reviewer namespace.
  • Mutation ledger: server-side state changed — PR #968 created; issue #948 labels moved to status:pr-open. No merge performed. No MCP configuration, session-state file, credential, or runtime process was altered.

Do not do: Do not review or merge PR #968 from the author namespace — the author of a change must not be its reviewer or merger. Do not re-open a second issue or branch for #948; feat/issue-948-client-session-provenance is the canonical branch and #968 the canonical PR. Do not kill MCP processes, run a restart loop, edit MCP configuration, .env, or session-state files, or reach for a raw Gitea API or fallback transport to work around a gate. Do not classify any of the 28 full-suite failures as introduced by this branch — compare failing IDs against the merge-base run, never counts. Do not treat a process environment flag as proof of session ownership when reviewing the model.

Canonical Issue State

STATE: pr-open
WHO_IS_NEXT: reviewer
NEXT_ACTION: Review PR #968 at head fa510dd28d against the #948 acceptance criteria for the provenance model, and post a formal verdict from the gitea-reviewer namespace.
NEXT_PROMPT:

Review Scaled-Tech-Consulting/Gitea-Tools PR #968 (Issue #948).

Namespace: gitea-reviewer
Profile: prgs-reviewer
Identity: jcwalker3
Remote: prgs
Repository: Scaled-Tech-Consulting/Gitea-Tools
PR head: fa510dd28dd5f9ca4f81e5b3048ceab584f0b7d7
Base: master at 8eada1fbe45289d6b92d291b3d5e5e56ae64ac5d

Focus the review on:
1. Whether mcp_worker_identity.assess_provenance is genuinely the single
   authority — trace each of the six surfaces to it, and confirm no surface
   still derives provenance independently.
2. Whether the launch/ownership split preserves the #686 manual-launch wall:
   confirm is_client_managed returns what it returned before for every input.
3. Whether the cohort scan can still be defeated — processes without identity
   evidence must remain classified as duplicates.
4. Whether any refusal is profile-wide or fleet-wide rather than scoped to a
   worker identity or generation.
5. Whether the registry is atomic across processes and whether a superseded
   session can mutate through any path.
6. The vocabulary change from manual_launch to unproven, and whether any
   consumer branches on the literal string rather than provenance_fail_closed.

Baseline for the full suite: 28F/5910P/6S at merge base 8eada1fb. Run from a
branches/ worktree. Compare failing IDs, never counts.

Do not merge. Post a formal verdict only.

WHAT_HAPPENED: Implemented the client/session-aware provenance model for #948 on branch feat/issue-948-client-session-provenance, pushed two commits, and opened PR #968. The root cause was reproduced from source: SAFE_ENV_KEYS omits every provenance key that classify_namespace_probe looked up in the summary it produces, so that surface could never report client_managed, while gitea_get_runtime_context read the live environment and did. Both surfaces now call one authority. Hardcoded client='codex' remediation and the dadeschools default-remote fallback were also removed.
WHY: #948 AC1-AC8, AC17, AC22-AC23, AC26-AC44 require one non-forgeable client/session/generation ownership model that no environment flag alone can satisfy, with refusals scoped to a worker identity rather than a profile. The two divergent implementations made agreement impossible by construction, so a shared authority had to exist before any of the ownership criteria could hold. AC42/AC43 reconciliation with #900 and #686 is served by proving distinctness from identity evidence instead of assuming duplication from a shared profile.
RELATED_PRS: #968
BLOCKERS: none
VALIDATION: Preflight verified natively before every mutation — gitea_whoami (jcwalker3, prgs-author, role author, remote prgs, host gitea.prgs.cc) then gitea_resolve_task_capability for the exact task key, with nothing between. Runtime parity in agreement at 8eada1fbe4 with live_stale false, mutation_safe true, restart_required false. Author capability resolved with stop_required false and restart_required false, so the provenance gate did not block the author path. Full suite run from a branches/ worktree at head and at merge base with identical failing-ID sets (28 each). Remote head confirmed by git ls-remote and by gitea_view_pr. Worktree clean at handoff.
LAST_UPDATED_BY: jcwalker3 (prgs-author)

## Author handoff: implementation pushed, PR #968 open ### Root cause Two surfaces computed provenance independently and disagreed about one process. `gitea_get_runtime_context` read the live `os.environ` and reported `client_managed`. `mcp_namespace_health.classify_namespace_probe` derived the flag from `_safe_env_summary(process)`, whose `SAFE_ENV_KEYS` allowlist carries only `GITEA_MCP_PROFILE`, `GITEA_PROFILE_NAME`, `GITEA_SERVICE`, `GITEA_EXECUTION_ROLE`, `GITEA_MCP_CONFIG` — and none of the three provenance keys it then looked up. Those lookups could only return `None`, so that surface was structurally incapable of reporting anything but `manual_launch` for any process. An allowlist for deciding what may be *echoed* was deciding what may be *believed*. Underneath, neither model could name which client or session owned a runtime. Ownership was inferred from a process environment flag the launcher sets once, which says nothing about who owns the process now — so a healthy daemon serving a second client could not be told apart from a duplicate, and the cohort scan walled every process sharing a profile. ### Authoritative provenance model `mcp_worker_identity.py` is the single authority for runtime context, namespace health, namespace attachment, the fleet/cohort scan, capability resolution, and typed reconnect assessment. It splits two claims the old code ran together: - **launch provenance** — hand-launched from a terminal? Answered from the environment, which is legitimate because the launcher sets it. The #686 wall is unchanged. - **session ownership** — which live client session owns this runtime now? Answered only by a live attachment record; no environment flag establishes it. `is_client_managed` stays bound to the launch dimension, so unification changes which code decides, not what gets decided. Invariants: `UNIQUE worker_identity`, `NOT UNIQUE role`, `NOT UNIQUE profile`, `EXCLUSIVE active lease target`. Identity collision fails closed and never adopts the incumbent; a generation held by a live session cannot be claimed twice; a non-live claimant is superseded with a higher fencing epoch; liveness is heartbeat freshness, not PID comparison. Missing proof reports `unproven` rather than asserting `manual_launch`, and both fail closed. Also closed: reconnect guidance no longer defaults to Codex (`DEFAULT_CLIENT` becomes `generic`, client resolved from the attachment record), and `resolve_bound_remote` keeps a PRGS-bound namespace off the `dadeschools` library default. ### Files changed `mcp_worker_identity.py` (new) · `gitea_mcp_server.py` · `mcp_namespace_health.py` · `mcp_client_reconnect.py` · `tests/test_issue_948_client_session_provenance.py` (new) · `tests/test_issue_686_manual_mcp_provenance.py` · `docs/remote-mcp/threat-model.md` · `docs/remote-mcp/threat-model-anchors.json` ### Tests and results - `pytest tests/test_issue_948_client_session_provenance.py` — 43 passed - 9 directly affected suites — 181 passed, 18 subtests - 34 category suites (capability resolution, runtime context, namespace health, namespace attachment, fleet/provenance, reconnect, profile and repository binding, session state, process generation, SQLite/registry persistence) — 668 passed, 153 subtests - Full suite from a `branches/` worktree, `-p no:randomly`: head **28F/5953P/6S** vs merge base `8eada1fb` **28F/5910P/6S**; `diff` of failing-ID sets is empty, so the 28 are pre-existing by merge-base evidence, not assumption. ### Head and deployment Complete head SHA: `fa510dd28dd5f9ca4f81e5b3048ceab584f0b7d7` (local, `git ls-remote`, and PR #968 agree). Serving daemons run the code from their boot commit, so the new surfaces appear only after the control checkout advances and the operator reconnects the MCP namespaces. No process kill, restart loop, raw API call, fallback transport, or configuration edit is required or was performed. ### Scope note This delivers the provenance model and the surfaces named above. Issue-lease acquisition via `work_issue` (AC9-AC16) and the merge-gate criteria (AC19-AC21) build on this model and are not in this change. Boundary with #934: that child defines provenance for a remote non-stdio transport and waits on #931/#932; this change governs the local stdio fleet and leaves every stdio guard in force. [THREAD STATE LEDGER] **What is true now:** PR #968 is open against master at head `fa510dd28dd5f9ca4f81e5b3048ceab584f0b7d7`, mergeable, authored by jcwalker3 under prgs-author. Issue #948 carries `status:pr-open`. No review verdict exists on PR #968 yet. **What changed:** Two commits were pushed to `feat/issue-948-client-session-provenance` — the implementation plus a restamp of the #956 threat-model anchors onto the commit whose line numbers they resolve at. Gitea server-side state changed: PR #968 was created and the issue label set moved to `status:pr-open`. **What is blocked:** Nothing. Blocker classification: no blocker. **Who/what acts next:** An independent reviewer, because the author of this change must not review or merge it. - Server-side decision state: no review decision recorded on PR #968. - Local verdict/state: implementation complete and pushed; author self-review is forbidden, so no verdict was prepared locally. - Next actor: reviewer. - Required action: review PR #968 at head `fa510dd28dd5f9ca4f81e5b3048ceab584f0b7d7` and post a formal verdict through the reviewer namespace. - Mutation ledger: server-side state changed — PR #968 created; issue #948 labels moved to `status:pr-open`. No merge performed. No MCP configuration, session-state file, credential, or runtime process was altered. **Do not do:** Do not review or merge PR #968 from the author namespace — the author of a change must not be its reviewer or merger. Do not re-open a second issue or branch for #948; `feat/issue-948-client-session-provenance` is the canonical branch and #968 the canonical PR. Do not kill MCP processes, run a restart loop, edit MCP configuration, `.env`, or session-state files, or reach for a raw Gitea API or fallback transport to work around a gate. Do not classify any of the 28 full-suite failures as introduced by this branch — compare failing IDs against the merge-base run, never counts. Do not treat a process environment flag as proof of session ownership when reviewing the model. ## Canonical Issue State STATE: pr-open WHO_IS_NEXT: reviewer NEXT_ACTION: Review PR #968 at head fa510dd28dd5f9ca4f81e5b3048ceab584f0b7d7 against the #948 acceptance criteria for the provenance model, and post a formal verdict from the gitea-reviewer namespace. NEXT_PROMPT: ```text Review Scaled-Tech-Consulting/Gitea-Tools PR #968 (Issue #948). Namespace: gitea-reviewer Profile: prgs-reviewer Identity: jcwalker3 Remote: prgs Repository: Scaled-Tech-Consulting/Gitea-Tools PR head: fa510dd28dd5f9ca4f81e5b3048ceab584f0b7d7 Base: master at 8eada1fbe45289d6b92d291b3d5e5e56ae64ac5d Focus the review on: 1. Whether mcp_worker_identity.assess_provenance is genuinely the single authority — trace each of the six surfaces to it, and confirm no surface still derives provenance independently. 2. Whether the launch/ownership split preserves the #686 manual-launch wall: confirm is_client_managed returns what it returned before for every input. 3. Whether the cohort scan can still be defeated — processes without identity evidence must remain classified as duplicates. 4. Whether any refusal is profile-wide or fleet-wide rather than scoped to a worker identity or generation. 5. Whether the registry is atomic across processes and whether a superseded session can mutate through any path. 6. The vocabulary change from manual_launch to unproven, and whether any consumer branches on the literal string rather than provenance_fail_closed. Baseline for the full suite: 28F/5910P/6S at merge base 8eada1fb. Run from a branches/ worktree. Compare failing IDs, never counts. Do not merge. Post a formal verdict only. ``` WHAT_HAPPENED: Implemented the client/session-aware provenance model for #948 on branch `feat/issue-948-client-session-provenance`, pushed two commits, and opened PR #968. The root cause was reproduced from source: `SAFE_ENV_KEYS` omits every provenance key that `classify_namespace_probe` looked up in the summary it produces, so that surface could never report `client_managed`, while `gitea_get_runtime_context` read the live environment and did. Both surfaces now call one authority. Hardcoded `client='codex'` remediation and the `dadeschools` default-remote fallback were also removed. WHY: #948 AC1-AC8, AC17, AC22-AC23, AC26-AC44 require one non-forgeable client/session/generation ownership model that no environment flag alone can satisfy, with refusals scoped to a worker identity rather than a profile. The two divergent implementations made agreement impossible by construction, so a shared authority had to exist before any of the ownership criteria could hold. AC42/AC43 reconciliation with #900 and #686 is served by proving distinctness from identity evidence instead of assuming duplication from a shared profile. RELATED_PRS: #968 BLOCKERS: none VALIDATION: Preflight verified natively before every mutation — gitea_whoami (jcwalker3, prgs-author, role author, remote prgs, host gitea.prgs.cc) then gitea_resolve_task_capability for the exact task key, with nothing between. Runtime parity in agreement at 8eada1fbe45289d6b92d291b3d5e5e56ae64ac5d with live_stale false, mutation_safe true, restart_required false. Author capability resolved with stop_required false and restart_required false, so the provenance gate did not block the author path. Full suite run from a branches/ worktree at head and at merge base with identical failing-ID sets (28 each). Remote head confirmed by git ls-remote and by gitea_view_pr. Worktree clean at handoff. LAST_UPDATED_BY: jcwalker3 (prgs-author)
sysadmin removed the status:pr-open label 2026-07-29 03:29:50 -05:00
Sign in to join this conversation.
No labels type:feature
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Scaled-Tech-Consulting/Gitea-Tools#948