Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
97bc190fc2 | ||
|
|
7bf4f12584 |
@@ -0,0 +1,230 @@
|
||||
# Remote-MCP coupling inventory
|
||||
|
||||
Every place the Gitea MCP server depends on being a local, client-spawned, stdio-attached
|
||||
process on the operator's machine.
|
||||
|
||||
- **Issue:** #930 (Remote-MCP 01), child 1 of epic #929.
|
||||
- **Generated against commit:** `7bf4f1258451823a55b36d2157e74f8457165088` (`master`).
|
||||
- **Anchors:** every `file:line` below resolves at the commit above and at the commit that
|
||||
adds this document. This change adds one new file and edits no existing file, so no
|
||||
existing line number shifts between the two.
|
||||
- **Scope:** documentation only. No server behavior changes in this child.
|
||||
|
||||
## How to read an entry
|
||||
|
||||
| Field | Meaning |
|
||||
| ----- | ------- |
|
||||
| **Anchor** | `file:line` at the commit under review. |
|
||||
| **Assumes today** | What the code takes for granted while running as a local stdio process. |
|
||||
| **Observes remotely** | What the same code would actually see on a shared remote host. |
|
||||
| **Class** | One of: *portable as written*, *needs a seam*, *needs a replacement*, *cannot be remote*. |
|
||||
| **Owner** | Exactly one epic child (#931–#939) responsible for the fix. |
|
||||
|
||||
Classification meanings:
|
||||
|
||||
- **portable as written** — the code is already transport-, host-, and principal-neutral; it
|
||||
moves unchanged once its inputs are supplied by a remote-aware caller.
|
||||
- **needs a seam** — the logic is correct but is wired to a hard-coded local source. It needs
|
||||
an injection point, not new semantics.
|
||||
- **needs a replacement** — the semantics themselves are local-only. A remote deployment
|
||||
needs a differently-defined mechanism, not the same mechanism relocated.
|
||||
- **cannot be remote** — the operation is inherently about the operator's own machine
|
||||
(its process table, its keychain, its checkout). It must either stay local behind an
|
||||
explicit boundary or be deleted from the remote surface.
|
||||
|
||||
---
|
||||
|
||||
## 1. Transport bind
|
||||
|
||||
The transport is bound literally, once, at process start, and the bound value is the root of
|
||||
the mutation-authorization chain.
|
||||
|
||||
| ID | Anchor | Assumes today | Observes remotely | Class | Owner |
|
||||
| -- | ------ | ------------- | ----------------- | ----- | ----- |
|
||||
| T1 | `gitea_mcp_server.py:23750` | The single production bind call passes the literal `transport="stdio"` immediately before the server loop. | The literal is wrong for any non-stdio deployment; there is no parameter to change it. | needs a seam | #931 |
|
||||
| T2 | `mcp_daemon_guard.py:45` | `_PRODUCTION_TRANSPORTS = frozenset({"stdio"})` is the closed allowlist of production transports. | A remote transport name is rejected by the allowlist before any other check runs. | needs a seam | #931 |
|
||||
| T3 | `mcp_daemon_guard.py:174` | `bind_native_mcp_transport` raises `UnsanctionedRuntimeError` for any transport outside `_PRODUCTION_TRANSPORTS` (raise at `mcp_daemon_guard.py:187`). | The remote server fails to start rather than degrading; the failure is correct, but the allowlist is the only thing that must change. | needs a seam | #931 |
|
||||
| T4 | `mcp_daemon_guard.py:328` | `is_native_mcp_transport()` asserts a process-local runtime record whose `pid` matches `os.getpid()` and whose phase is `transport_bound`. The predicate itself names no transport. | Unchanged semantics: one server process that bound one transport. It stays true on a remote host. | portable as written | #931 |
|
||||
| T5 | `mcp_daemon_guard.py:349` | `is_production_native_mcp_transport()` adds only a `mode == production` check on top of T4. | Unchanged. | portable as written | #931 |
|
||||
| T6 | `irrecoverable_provenance.py:497` | `assess_transport_for_auth_mint()` requires production native transport before minting non-forgeable recovery authorization (#709 F1). | The gate is transport-agnostic in form, but its guarantee — "an ordinary Python process cannot reach this" — is currently underwritten by the stdio bind. Under a remote transport the guarantee must be re-derived from the authenticated session, not from the bind. | needs a seam | #931 |
|
||||
| T7 | `gitea_mcp_server.py:8375` | Consumer: refuses to proceed unless `assess_transport_for_auth_mint()` allows. | Unchanged given a corrected T6. | portable as written | #931 |
|
||||
| T8 | `gitea_mcp_server.py:8624` | Second consumer of the same gate on the confirmation path. | Unchanged given a corrected T6. | portable as written | #931 |
|
||||
| T9 | `mcp_server.py:4` | Module docstring asserts "Runs over stdio." as a property of the server. | The stated contract becomes false on the remote deployment and is load-bearing documentation for operators. | needs a replacement | #931 |
|
||||
|
||||
## 2. Launch provenance
|
||||
|
||||
Mutations fail closed unless the process can prove a client launched it with real stdio pipes
|
||||
and `GITEA_CLIENT_MANAGED` provenance. Every proof in this section is a statement about the
|
||||
local operating system.
|
||||
|
||||
| ID | Anchor | Assumes today | Observes remotely | Class | Owner |
|
||||
| -- | ------ | ------------- | ----------------- | ----- | ----- |
|
||||
| P1 | `gitea_mcp_server.py:14588` | `_is_client_managed_process()` derives provenance from `GITEA_CLIENT_MANAGED` / `GITEA_MCP_CLIENT_MANAGED` / `GITEA_SERVER_PROVENANCE` / `GITEA_FORCE_CLIENT_MANAGED` on this process's own environment. | A long-lived remote process has one environment for all callers, so a per-process env var can no longer say anything about the caller that issued a request. | needs a replacement | #934 |
|
||||
| P2 | `gitea_mcp_server.py:14606` | Falls back to `sys.stdin.isatty()`: an active TTY on stdin means a human launched it from a terminal, so refuse. | A remote server has no meaningful stdin. The signal is absent, not merely different. | cannot be remote | #934 |
|
||||
| P3 | `gitea_mcp_server.py:14618` | `_provenance_mutation_block()` emits `blocker_kind: "unsupported_manual_launch"` and a "reconnect the IDE/client-managed MCP namespace" remediation. | The block shape is reusable; its predicate and its remediation text are both stdio-specific. | needs a seam | #934 |
|
||||
| P4 | `gitea_mcp_server.py:20599` | `_check_mcp_runtimes_diagnostics()` shells `ps -o pid,lstart,command -ax` and greps for `mcp_server.py` to find peer role servers. | On a shared host the process table lists unrelated tenants' processes, or none at all under a container. Peer discovery by `ps` has no remote meaning. | cannot be remote | #934 |
|
||||
| P5 | `gitea_mcp_server.py:20702` | More than one process per `GITEA_MCP_PROFILE` in the local process table is reported as a duplicate-launch fault. | A remote endpoint is expected to serve many concurrent sessions per role. "Two processes for one role" becomes the normal case, so the check inverts from a safety net into a false wall. | cannot be remote | #934 |
|
||||
| P6 | `gitea_mcp_server.py:20715` | Processes lacking client-managed provenance are ignored for runtime freshness and reported as manual launches. | Same defect as P5: correctness depends on enumerating local peers. | cannot be remote | #934 |
|
||||
| P7 | `gitea_config.py:1172` | `RECOGNIZED_GITEA_ENV_KEYS` is the allowlist of `GITEA_*` env vars a legitimately launched server may carry; anything else is contamination. | Configuration on a remote host arrives from deployment tooling, not from a client-authored env block. The allowlist keeps working mechanically but stops proving anything about provenance. | needs a replacement | #934 |
|
||||
| P8 | `gitea_mcp_server.py:20683` | The unsupported-env scan applies `RECOGNIZED_GITEA_ENV_KEYS` to *other* processes' environments harvested via `ps eww <pid>`. | Reading another process's environment is unavailable or prohibited across tenants, and is not exposed in this form outside macOS/BSD `ps`. | cannot be remote | #934 |
|
||||
| P9 | `mcp_daemon_guard.py:126` | `mark_sanctioned_daemon()` requires the claiming stack frame's resolved absolute path to be the canonical `mcp_server.py` / `gitea_mcp_server.py` next to the guard module; basename spoofing is rejected. | Entrypoint-path identity still exists on a remote host, but it authenticates the *deployment*, not the *caller*. It must be kept and demoted from "authorizes mutations" to "authorizes the process". | needs a seam | #934 |
|
||||
| P10 | `gitea_config.py:1233` | The client-config generator emits `"GITEA_CLIENT_MANAGED": "1"` into each generated MCP client entry, alongside `GITEA_MCP_CONFIG` / `GITEA_MCP_PROFILE`. | A remote endpoint is addressed by URL and credential, not by a spawn command with an env block. This generator produces the wrong artifact entirely. | needs a replacement | #938 |
|
||||
| P11 | `mcp_namespace_health.py:232` | Namespace health classifies a namespace as `client_managed` or `manual_launch` from the reported env summary. | During dual-run, local and remote namespaces coexist and must both be classifiable; a two-valued local/manual axis cannot express "remote endpoint, authenticated session". | needs a replacement | #939 |
|
||||
| P12 | `gitea_mcp_server.py:18161` | The diagnostics payload reports `server_provenance` as exactly `"client_managed"` or `"manual_launch"`. | This is the field a cutover operator reads to confirm which deployment served a call. It must gain a remote value before dual-run parity can be validated. | needs a replacement | #939 |
|
||||
|
||||
## 3. Role binding
|
||||
|
||||
Role separation is currently enforced by *which process a call reaches*. The process is pinned
|
||||
to one role for its lifetime by an environment variable.
|
||||
|
||||
| ID | Anchor | Assumes today | Observes remotely | Class | Owner |
|
||||
| -- | ------ | ------------- | ----------------- | ----- | ----- |
|
||||
| R1 | `gitea_config.py:54` | `ENV_PROFILE = "GITEA_MCP_PROFILE"` is the single source of the active profile, read from the process environment. | One shared process serves several principals; a process-wide profile cannot answer "who is calling now". This is the root of the coupling. | needs a replacement | #932 |
|
||||
| R2 | `review_workflow_load.py:95` | Reads `GITEA_MCP_PROFILE` directly to decide the reviewer workflow binding. | Reads the deployment's profile, not the caller's, silently granting or denying the wrong role. | needs a replacement | #932 |
|
||||
| R3 | `mcp_discoverability.py:152` | Reads `GITEA_MCP_PROFILE` to describe the namespace to the client. | Correct logic, wrong input source; it needs the request principal injected. | needs a seam | #932 |
|
||||
| R4 | `webui/deployment_boundary.py:115` | Reads `GITEA_MCP_PROFILE` to classify the deployment boundary for the console. | Same as R3. | needs a seam | #932 |
|
||||
| R5 | `gitea_mcp_server.py:21106` | Remediation text instructs the operator to "Relaunch the server with `GITEA_MCP_PROFILE` set to a profile that has the required permission". | Relaunching a shared remote endpoint to change one caller's role is not a valid instruction; it would re-role every other session. | needs a replacement | #932 |
|
||||
| R6 | `native_mcp_preference.py:93` | Detects shell commands that override `GITEA_MCP_PROFILE` away from the session (`native_mcp_preference.py:223`) and flags them as CLI auth divergence. | The divergence check is genuinely useful and survives, but its notion of "the session's profile" must come from the request principal. | needs a seam | #932 |
|
||||
| R7 | `gitea_mcp_server.py:20671` | Recovers a peer server's role by regexing `GITEA_MCP_PROFILE=` out of that process's environment. | Depends on P4/P8 process-table access; role discovery by peer-env scraping has no remote analogue. | cannot be remote | #932 |
|
||||
|
||||
## 4. Credentials
|
||||
|
||||
Every token resolves, directly or indirectly, from one human's macOS keychain.
|
||||
|
||||
| ID | Anchor | Assumes today | Observes remotely | Class | Owner |
|
||||
| -- | ------ | ------------- | ----------------- | ----- | ----- |
|
||||
| C1 | `gitea_config.py:956` | `_keychain_token()` shells `security find-generic-password -s <item> -w`. | `security(1)` is a macOS binary reading the calling user's login keychain. It does not exist on a Linux host and would be the wrong identity even on a shared Mac. | cannot be remote | #933 |
|
||||
| C2 | `gitea_config.py:974` | `resolve_token(profile, keychain_lookup=_keychain_token)` dispatches on `auth.type` of `env` or `keychain`, defaulting the lookup to C1. | The injectable `keychain_lookup` parameter is the existing seam; a remote credential provider plugs in here without changing the dispatch. | needs a seam | #933 |
|
||||
| C3 | `gitea_config.py:1015` | `keychain_auth(item_id)` constructs the `{"type": "keychain", "id": ...}` reference stored in profiles. | The reference type itself encodes "macOS keychain" into persisted config. A remote provider needs a new auth reference type, not a new value of this one. | needs a replacement | #933 |
|
||||
| C4 | `mcp_daemon_guard.py:440` | `assert_keychain_access_allowed()` fails closed for git-credential keychain fill outside a sanctioned daemon, with an operator opt-out env var. | The gate protects a mechanism that will not exist remotely. Its replacement must gate the *credential provider* call, not the keychain call, or the protection silently lapses. | needs a replacement | #933 |
|
||||
| C5 | `sentry_incident_bridge.py:190` | `resolve_token(env)` resolves the Sentry token from an injected env mapping with no keychain path. | Already host-neutral; it is the shape the Gitea credential path should converge on. | portable as written | #933 |
|
||||
| C6 | `gitea_mcp_server.py:18469` | The profile-audit tool calls `gitea_config.resolve_token(p)` for every configured profile to report "credentials present" without networking. | On a remote host this would materialize every principal's credential inside one process — an audit surface that becomes a credential-aggregation risk. | needs a seam | #933 |
|
||||
|
||||
## 5. Runtime freshness
|
||||
|
||||
The mutation gate is defined as "the commit this process started at matches the checkout on
|
||||
this disk, and both match live master". Two of those three terms are local-disk facts.
|
||||
|
||||
| ID | Anchor | Assumes today | Observes remotely | Class | Owner |
|
||||
| -- | ------ | ------------- | ----------------- | ----- | ----- |
|
||||
| F1 | `master_parity_gate.py:168` | `capture_startup_parity(root)` reads git `HEAD` from the server's own root once at startup and returns it as the baseline. | A remote host carries a deployed artifact, not the operator's checkout. Its `HEAD` says nothing about the operator's working tree, which is the thing the gate exists to protect. | cannot be remote | #935 |
|
||||
| F2 | `master_parity_gate.py:255` | `mutation_safe = determinable and in_parity and live_known and not live_stale` — a conjunction of two local-HEAD comparisons and one live-remote comparison. | Two of the three conjuncts lose meaning, so the whole verdict does. A remote deployment needs a redefined, testable freshness predicate rather than this one relocated. | needs a replacement | #935 |
|
||||
| F3 | `master_parity_gate.py:164` | The live-remote head is probed and cached per `(root, remote, branch)`, keyed on the local root. | The live-remote probe is the one conjunct that survives; it needs a key that is not the operator's filesystem path. | needs a seam | #935 |
|
||||
| F4 | `gitea_mcp_server.py:18262` | `gitea_assess_master_parity` publishes `startup_head` / `local_head` / `live_remote_head` / `mutation_safe` as the authoritative mutation-safety verdict. | The tool's contract is consumed by every mutation caller and by the operator; it must keep its shape while its semantics are redefined, or every consumer breaks at once. | needs a replacement | #935 |
|
||||
| F5 | `gitea_mcp_server.py:23054` | Falls back to `_process_boot_head_sha` — the commit this process booted at — when the parity payload has no `startup_head`. | Same defect as F1, in a fallback path that is easy to miss when F1 is fixed. | needs a seam | #935 |
|
||||
| F6 | `gitea_mcp_server.py:20615` | Staleness is also inferred from `os.path.getmtime()` of `gitea_mcp_server.py` under `PROJECT_ROOT` (`gitea_mcp_server.py:20611`), compared against peer process start times. | File mtime on a deployed artifact tracks the deploy, not the operator's edits, and the peer start times it is compared against come from the unavailable process table (P4). | cannot be remote | #935 |
|
||||
|
||||
## 6. Local filesystem
|
||||
|
||||
Author and reviewer tools act directly on the operator's checkout.
|
||||
|
||||
| ID | Anchor | Assumes today | Observes remotely | Class | Owner |
|
||||
| -- | ------ | ------------- | ----------------- | ----- | ----- |
|
||||
| L1 | `gitea_mcp_server.py:10122` | `gitea_bootstrap_author_issue_worktree` creates and binds a git worktree on the server's own disk. | The remote host has no operator checkout to add a worktree to. Executing this remotely would act on the wrong disk while reporting success. | cannot be remote | #936 |
|
||||
| L2 | `gitea_mcp_server.py:190` | `ACTIVE_WORKTREE_ENV = "GITEA_ACTIVE_WORKTREE"` and `AUTHOR_WORKTREE_ENV` (`gitea_mcp_server.py:191`) carry the active workspace as process-wide environment. | Process-wide workspace state cannot represent per-session workspaces on a shared endpoint. | needs a replacement | #936 |
|
||||
| L3 | `gitea_mcp_server.py:9801` | Binding a worktree writes `os.environ["GITEA_AUTHOR_WORKTREE"]` and `os.environ["GITEA_ACTIVE_WORKTREE"]` (`gitea_mcp_server.py:9802`), mutating global process state. | One session's bind would silently retarget every other concurrent session in the same process. This is a correctness bug the moment concurrency is real. | needs a replacement | #936 |
|
||||
| L4 | `reviewer_inventory_worktree.py:48` | `_BRANCHES_WORKTREE_RE = re.compile(r"\bbranches/", re.I)` requires review worktree paths to sit under `branches/`. | A path convention on the operator's machine, asserted as a validation rule. It needs to become a property of a declared workspace, not a substring test. | needs a seam | #936 |
|
||||
| L5 | `stable_control_runtime.py:54` | `DEV_WORKTREE_SEGMENT = "branches"` classifies a process root as a development worktree by path segment. | Same class of assumption as L4, on the runtime-classification side. | needs a seam | #936 |
|
||||
| L6 | `mcp_server.py:42` | `check_conflict_markers()` runs at import and `os.walk`s the install directory for unresolved conflict markers, `sys.exit(1)` on a hit. | On a remote host it scans a deployed artifact, which by construction never has conflict markers — so the guard passes trivially and stops protecting the thing it was written to protect. | needs a replacement | #936 |
|
||||
| L7 | `role_session_router.py:487` | `check_mid_merge()` reports infra-stop from `.git/MERGE_HEAD`, `rebase-merge`, `rebase-apply` and a source conflict scan under the server's project root. | Same inversion as L6: it would report the deployment's git state, not the operator's. | needs a replacement | #936 |
|
||||
| L8 | `author_issue_bootstrap.py:996` | Enumerates worktrees with `git -C <root> worktree list --porcelain`. | Requires a real local clone with real worktrees; there is nothing equivalent to enumerate remotely. | cannot be remote | #936 |
|
||||
| L9 | `mcp_server.py:10` | Redirects `sys.stderr` to the fixed path `/tmp/mcp_server_stderr.log` outside pytest. | A single fixed `/tmp` path is shared by every concurrent server on a host and is not a deployment's logging surface. | needs a replacement | #938 |
|
||||
| L10 | `gitea_mcp_server.py:2314` | `ISSUE_LOCK_FILE = "/tmp/gitea_issue_lock.json"` — the legacy single global lock slot. | One global `/tmp` slot per host cannot represent concurrent remote sessions and is world-visible on a shared machine. | needs a replacement | #937 |
|
||||
| L11 | `issue_lock_provenance.py:14` | `ISSUE_LOCK_FILE = os.environ.get("GITEA_ISSUE_LOCK_FILE", "/tmp/gitea_issue_lock.json")` keeps the same `/tmp` default in the provenance path. | Same as L10; the env override is a local escape hatch, not a remote design. | needs a replacement | #937 |
|
||||
|
||||
## 7. Durable state
|
||||
|
||||
Locks, leases, session state, and the control-plane database live in the operator's home
|
||||
directory and are keyed on local PIDs.
|
||||
|
||||
| ID | Anchor | Assumes today | Observes remotely | Class | Owner |
|
||||
| -- | ------ | ------------- | ----------------- | ----- | ----- |
|
||||
| S1 | `issue_lock_store.py:26` | `DEFAULT_LOCK_DIR = ~/.cache/gitea-tools/issue-locks` — per-issue lock files under one user's home. | A shared endpoint has no single operator home; per-user paths make locks invisible across sessions and hosts. | needs a replacement | #937 |
|
||||
| S2 | `issue_lock_store.py:83` | `session_pointer_path()` names the session pointer file `session-<os.getpid()>.json`. | Many sessions share one PID on a remote server, so the pointer collapses to a single slot and sessions overwrite each other. | cannot be remote | #937 |
|
||||
| S3 | `issue_lock_store.py:98` | `is_process_alive(pid)` decides lock liveness by probing the local process table. | A PID recorded by one host is meaningless on another, and may coincidentally match a live unrelated process. | cannot be remote | #937 |
|
||||
| S4 | `issue_lock_store.py:213` | Lock records stamp `session_pid` and `pid` from `os.getpid()`. | The recorded identity no longer distinguishes sessions; ownership checks silently pass for the wrong caller. | needs a replacement | #937 |
|
||||
| S5 | `mcp_session_state.py:27` | `DEFAULT_STATE_DIR = ~/.cache/gitea-tools/session-state`, mode `0o700`. | Same home-directory coupling as S1, for review decision locks and workflow proofs. | needs a replacement | #937 |
|
||||
| S6 | `mcp_session_state.py:559` | Session bodies stamp `session_pid` and `writer_pid` from `os.getpid()` (`mcp_session_state.py:560`). | Writer attribution collapses across concurrent sessions in one process. | needs a replacement | #937 |
|
||||
| S7 | `control_plane_db.py:47` | `DEFAULT_DB_PATH = ~/.cache/gitea-tools/control-plane/control_plane.sqlite3`. | A per-user SQLite file is not reachable by, or safe for, multiple remote sessions or multiple hosts. | needs a replacement | #937 |
|
||||
| S8 | `control_plane_db.py:386` | `sqlite3.connect(self.db_path, timeout=30)` — single-writer file locking tuned for one local process. | SQLite's write lock does not extend across hosts and degrades sharply under real concurrency; the store needs a concurrency-safe backend. | needs a replacement | #937 |
|
||||
| S9 | `control_plane_db.py:1145` | Lease rows record `owner_pid` defaulting to `os.getpid()` (also `control_plane_db.py:2039`). | PID-keyed lease ownership is unusable across hosts and ambiguous within one shared process. | cannot be remote | #937 |
|
||||
| S10 | `mcp_daemon_guard.py:53` | `_DEFAULT_SESSION_STATE_DIR` is pinned once at transport bind so a later `GITEA_MCP_SESSION_STATE_DIR` change cannot manufacture a second authority domain (#695 AC2). | The single-authority-domain invariant is exactly right and must be preserved; only its backing location needs to move. | needs a seam | #937 |
|
||||
| S11 | `gitea_mcp_server.py:11875` | Reviewer-lease reclaim reads `owner_pid_alive` from the lease freshness record to decide whether an owner is dead. | Consumes S3/S9; a false "owner alive" or "owner dead" here reclaims or refuses a live lease. This is the highest-consequence consumer of PID liveness. | cannot be remote | #937 |
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
### Entries per category
|
||||
|
||||
| Category | Entries |
|
||||
| -------- | ------: |
|
||||
| 1. Transport bind | 9 |
|
||||
| 2. Launch provenance | 12 |
|
||||
| 3. Role binding | 7 |
|
||||
| 4. Credentials | 6 |
|
||||
| 5. Runtime freshness | 6 |
|
||||
| 6. Local filesystem | 11 |
|
||||
| 7. Durable state | 11 |
|
||||
| **Total** | **62** |
|
||||
|
||||
No category is empty, so no "this category has no coupling" justification is required.
|
||||
|
||||
### Entries per classification
|
||||
|
||||
| Classification | Entries |
|
||||
| -------------- | ------: |
|
||||
| portable as written | 5 |
|
||||
| needs a seam | 16 |
|
||||
| needs a replacement | 26 |
|
||||
| cannot be remote | 15 |
|
||||
| **Total** | **62** |
|
||||
|
||||
### Category × classification
|
||||
|
||||
| Category | portable | seam | replacement | cannot | Total |
|
||||
| -------- | -------: | ---: | ----------: | -----: | ----: |
|
||||
| 1. Transport bind | 4 | 4 | 1 | 0 | 9 |
|
||||
| 2. Launch provenance | 0 | 2 | 5 | 5 | 12 |
|
||||
| 3. Role binding | 0 | 3 | 3 | 1 | 7 |
|
||||
| 4. Credentials | 1 | 2 | 2 | 1 | 6 |
|
||||
| 5. Runtime freshness | 0 | 2 | 2 | 2 | 6 |
|
||||
| 6. Local filesystem | 0 | 2 | 7 | 2 | 11 |
|
||||
| 7. Durable state | 0 | 1 | 6 | 4 | 11 |
|
||||
| **Total** | **5** | **16** | **26** | **15** | **62** |
|
||||
|
||||
### Entries per epic child
|
||||
|
||||
Every child from 2 through 10 is named by at least one entry, and every entry names exactly
|
||||
one child.
|
||||
|
||||
| Child | Issue | Title | Entries | IDs |
|
||||
| ----: | ----- | ----- | ------: | --- |
|
||||
| 2 | #931 | Transport-neutral bind seam | 9 | T1–T9 |
|
||||
| 3 | #932 | Per-request principal resolution | 7 | R1–R7 |
|
||||
| 4 | #933 | Server-side credential provider | 6 | C1–C6 |
|
||||
| 5 | #934 | Remote-session provenance | 9 | P1–P9 |
|
||||
| 6 | #935 | Redefined master-parity gate | 6 | F1–F6 |
|
||||
| 7 | #936 | Local-filesystem vs remotable tool split | 8 | L1–L8 |
|
||||
| 8 | #937 | Concurrency-safe session, lock, and lease state | 13 | L10, L11, S1–S11 |
|
||||
| 9 | #938 | Authenticated remote MCP endpoint | 2 | P10, L9 |
|
||||
| 10 | #939 | Dual-run cutover and rollback | 2 | P11, P12 |
|
||||
| | | **Total** | **62** | |
|
||||
|
||||
## Notes for downstream children
|
||||
|
||||
- **The three highest-risk entries are P5, F2, and S11.** Each is a guard that does not
|
||||
merely stop working remotely — it inverts. P5 turns concurrency into a reported fault,
|
||||
F2 returns a verdict computed from terms that no longer mean anything, and S11 reclaims
|
||||
or refuses leases on a PID-liveness answer that is wrong rather than unknown. A gate that
|
||||
fails open while still reporting green is worse than one that fails to start.
|
||||
- **T4, T5, T7, T8, and C5 are the portable core.** They show the target shape: predicates
|
||||
over injected inputs, with no reference to the host, the process table, or the operator's
|
||||
disk.
|
||||
- **The keychain seam already exists** at C2 (`resolve_token`'s injectable `keychain_lookup`).
|
||||
#933 should widen that seam rather than introduce a parallel path, and must remember C4 —
|
||||
the guard protecting the old mechanism has to be re-pointed, or the protection lapses
|
||||
silently when the mechanism is replaced.
|
||||
- **`branches/` appears as a validation rule in at least two independent places** (L4, L5).
|
||||
Path-substring conventions tend to have more copies than expected; #936 should re-grep
|
||||
rather than trust this list to be exhaustive for that specific pattern.
|
||||
Reference in New Issue
Block a user