From dd053ff3adda20ea43629ccc2a78b5ea2ebf9191 Mon Sep 17 00:00:00 2001 From: Tobias Macey Date: Wed, 19 Aug 2026 10:55:59 -0400 Subject: [PATCH 1/2] docs(witan): give the two colliding ADRs their own numbers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Numbers 0004 and 0006 were each used by two different decisions: 0004 Keycloak JWT per-user actor mapping ← keeps 0004 0004 Optional task `phase` field → 0008 0006 Code-graph branch ownership + reaping ← keeps 0006 0006 Stateless MCP protocol era → 0009 ADRs are cross-referenced by number from code comments, other ADRs, and changelog entries ("see ADR-0005", "Related: ADR-0002"), so a duplicated number makes those references ambiguous — a reader following "ADR-0006" cannot tell which document is meant. Which of each pair kept its number was decided by reference count, so the renumber moves the fewest citations: keycloak carries 10 by-filename references (and every bare `ADR-0004` in the tree is about identity — oidc.py, identity.py, omnigraph.py, config.py) against 1 for the phase tag; branch ownership carries 7 against 4 for the stateless era. This lands before the documentation site rather than after it deliberately. Once the site publishes, `/explanation/decisions/0006-stateless-.../` is a URL people link to and search engines index, and renumbering then would break those. Doing it first also means the site ships correct numbers from its first build instead of carrying a note explaining the collision. Two references needed judgement beyond a path rewrite: - `witan/config.py` had a bare `ADR-0006` meaning the stateless era. Left alone it would have silently started pointing at branch ownership. - The changelog entry for the stateless work named both the number and the path. A changelog is a historical record, so the entry now reads "ADR-0009 (…, numbered 0006 at the time)" rather than being quietly rewritten to look as though it always said 0009. Verified: no reference to either old filename remains anywhere in the tree, no ADR number is used twice, and witan-council's 839 tests pass. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017oVnpzjRJQVWq9hJGE2Rs7 --- mcp/servers/witan/CHANGELOG.md | 3 ++- .../docs/adr/0004-keycloak-jwt-per-user-actor-mapping.md | 2 +- .../docs/adr/0005-secure-cli-path-into-deployed-witan.md | 4 ++-- ...onal-task-phase-tag.md => 0008-optional-task-phase-tag.md} | 2 +- ...mcp-protocol-era.md => 0009-stateless-mcp-protocol-era.md} | 2 +- mcp/servers/witan/witan/config.py | 2 +- 6 files changed, 8 insertions(+), 7 deletions(-) rename mcp/servers/witan/docs/adr/{0004-optional-task-phase-tag.md => 0008-optional-task-phase-tag.md} (99%) rename mcp/servers/witan/docs/adr/{0006-stateless-mcp-protocol-era.md => 0009-stateless-mcp-protocol-era.md} (99%) diff --git a/mcp/servers/witan/CHANGELOG.md b/mcp/servers/witan/CHANGELOG.md index 8bab006e..400335e7 100644 --- a/mcp/servers/witan/CHANGELOG.md +++ b/mcp/servers/witan/CHANGELOG.md @@ -1221,7 +1221,8 @@ unchanged; this is what has to be true before it can move. ### Added -- **ADR-0006** (`docs/adr/0006-stateless-mcp-protocol-era.md`) records the move +- **ADR-0009** (`docs/adr/0009-stateless-mcp-protocol-era.md`, numbered 0006 at + the time) records the move to the stateless 2026-07-28 era: what it unlocks (multi-replica behind a plain round-robin LB, no session affinity), the two pieces of state that are still per-replica, and why the fastmcp 3.4.x/4.x straddle stays until 4.0 GA. diff --git a/mcp/servers/witan/docs/adr/0004-keycloak-jwt-per-user-actor-mapping.md b/mcp/servers/witan/docs/adr/0004-keycloak-jwt-per-user-actor-mapping.md index 7fb31378..36cdd325 100644 --- a/mcp/servers/witan/docs/adr/0004-keycloak-jwt-per-user-actor-mapping.md +++ b/mcp/servers/witan/docs/adr/0004-keycloak-jwt-per-user-actor-mapping.md @@ -9,7 +9,7 @@ `witan-users` group); ol-infrastructure `docs/adr/0009-deploy-witan-as-shared-multi-tenant-mcp-service.md`; `tk-ol-infrastructure-toolhive-witan-pulumi-stack-e843b3`; - `docs/adr/0006-stateless-mcp-protocol-era.md` (the JWT→actor mapping below is + `docs/adr/0009-stateless-mcp-protocol-era.md` (the JWT→actor mapping below is unchanged by the stateless era — it reads the token on each request and never depended on session state — but the `streamable-http` connection it describes no longer carries a handshake or a session id) diff --git a/mcp/servers/witan/docs/adr/0005-secure-cli-path-into-deployed-witan.md b/mcp/servers/witan/docs/adr/0005-secure-cli-path-into-deployed-witan.md index 9018cdd1..6e4c50e9 100644 --- a/mcp/servers/witan/docs/adr/0005-secure-cli-path-into-deployed-witan.md +++ b/mcp/servers/witan/docs/adr/0005-secure-cli-path-into-deployed-witan.md @@ -9,7 +9,7 @@ server-side JWT→actor→token mapping this reuses); ol-infrastructure `docs/adr/0009-deploy-witan-as-shared-multi-tenant-mcp-service.md` (ClusterIP-only omnigraph-server, the `svc-witan-admin` sketch); - `docs/adr/0006-stateless-mcp-protocol-era.md` (the 2026-07-28 era this path + `docs/adr/0009-stateless-mcp-protocol-era.md` (the 2026-07-28 era this path now runs on — read it alongside every `streamable-http` reference below, which describes the handshake-era shape) @@ -247,6 +247,6 @@ already actor-resolving. - **Amendment (2026-07-30):** largely moot against a 2026-07-28 deployment. That era has no `initialize` handshake and no session id, so a fresh connection per call costs a connection, not a negotiation — see - `docs/adr/0006-stateless-mcp-protocol-era.md`. The proxy also gained an + `docs/adr/0009-stateless-mcp-protocol-era.md`. The proxy also gained an elicitation handler, so a prompt the deployment raises now reaches the human at the terminal instead of degrading to the tool's default. diff --git a/mcp/servers/witan/docs/adr/0004-optional-task-phase-tag.md b/mcp/servers/witan/docs/adr/0008-optional-task-phase-tag.md similarity index 99% rename from mcp/servers/witan/docs/adr/0004-optional-task-phase-tag.md rename to mcp/servers/witan/docs/adr/0008-optional-task-phase-tag.md index 55dd3f6e..77dc0718 100644 --- a/mcp/servers/witan/docs/adr/0004-optional-task-phase-tag.md +++ b/mcp/servers/witan/docs/adr/0008-optional-task-phase-tag.md @@ -1,4 +1,4 @@ -# 4. Optional task `phase` field + per-phase ready-work rollup +# 8. Optional task `phase` field + per-phase ready-work rollup > "Phase tag" is used loosely in the tracking task title; the decision below is a > typed, optional `phase` **enum field** on `Task` (not a free-form tag) — see diff --git a/mcp/servers/witan/docs/adr/0006-stateless-mcp-protocol-era.md b/mcp/servers/witan/docs/adr/0009-stateless-mcp-protocol-era.md similarity index 99% rename from mcp/servers/witan/docs/adr/0006-stateless-mcp-protocol-era.md rename to mcp/servers/witan/docs/adr/0009-stateless-mcp-protocol-era.md index e7165e44..7efb0152 100644 --- a/mcp/servers/witan/docs/adr/0006-stateless-mcp-protocol-era.md +++ b/mcp/servers/witan/docs/adr/0009-stateless-mcp-protocol-era.md @@ -1,4 +1,4 @@ -# 6. Serving the stateless MCP protocol era (2026-07-28) +# 9. Serving the stateless MCP protocol era (2026-07-28) - Status: Accepted - Date: 2026-07-30 diff --git a/mcp/servers/witan/witan/config.py b/mcp/servers/witan/witan/config.py index c2be7f46..00c17f9a 100644 --- a/mcp/servers/witan/witan/config.py +++ b/mcp/servers/witan/witan/config.py @@ -143,7 +143,7 @@ class IdentityConfig(BaseModel): Sourced entirely from ``WITAN_OIDC_*``/``WITAN_ACTOR_TOKENS_FILE`` env vars — this is deployment/ops config for the shared ``streamable-http`` service, not something an individual local user sets in config.toml. - Unaffected by the stateless 2026-07-28 era (ADR-0006): the mapping reads + Unaffected by the stateless 2026-07-28 era (ADR-0009): the mapping reads the JWT on every request and never depended on session state. ``oidc_issuer`` unset means the deployed-auth path is disabled entirely (local ``stdio`` usage never sets it). From 49cd5691cb79b7e75e0979a39847bedb2b4620ba Mon Sep 17 00:00:00 2001 From: Tobias Macey Date: Wed, 19 Aug 2026 12:47:41 -0400 Subject: [PATCH 2/2] fix(witan): renumber four more stateless-era ADR references MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review feedback on #253 (Copilot). The renumber missed four citations that say ADR-0006 and mean the stateless 2026-07-28 protocol era — which now resolves to the branch-ownership decision instead, i.e. exactly the ambiguity the renumber existed to remove, just pointing the other way: packages/witan-core/witan_core/remote/config.py:47 packages/witan-core/witan_core/remote/__init__.py:7 mcp/servers/witan/witan/remote/__init__.py:9 docker/witan.Dockerfile:152 All four are unambiguous in context — each sits in a sentence about there being no handshake and no session id — so all four become ADR-0009. My earlier sweep searched by *filename* and caught only the one bare `ADR-0006` in `witan/config.py`. Bare numeric citations are the whole problem a duplicated number causes, so searching for the filename was the wrong instrument. Re-swept for every remaining `ADR-0004`/`ADR-0006` mention in the tree. All of them are correct as they stand: each surviving `ADR-0004` is the Keycloak JWT→actor mapping and each `ADR-0006` is code-graph branch ownership, and both of those kept their original numbers. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017oVnpzjRJQVWq9hJGE2Rs7 --- docker/witan.Dockerfile | 2 +- mcp/servers/witan/witan/remote/__init__.py | 2 +- packages/witan-core/witan_core/remote/__init__.py | 2 +- packages/witan-core/witan_core/remote/config.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docker/witan.Dockerfile b/docker/witan.Dockerfile index bebf1a0b..c53358d3 100644 --- a/docker/witan.Dockerfile +++ b/docker/witan.Dockerfile @@ -149,5 +149,5 @@ ENTRYPOINT ["witan"] # deployed invocation and keeps the image runnable standalone. The transport # serves both protocol eras: a 2026-07-28 client is answered statelessly (no # handshake, no Mcp-Session-Id, so replicas need no session affinity), an older -# one still gets the handshake. See mcp/servers/witan/docs/adr/0006. +# one still gets the handshake. See mcp/servers/witan/docs/adr/0009. CMD ["serve", "--transport", "streamable-http", "--host", "0.0.0.0", "--port", "8000"] diff --git a/mcp/servers/witan/witan/remote/__init__.py b/mcp/servers/witan/witan/remote/__init__.py index acd908aa..f19c30bb 100644 --- a/mcp/servers/witan/witan/remote/__init__.py +++ b/mcp/servers/witan/witan/remote/__init__.py @@ -6,7 +6,7 @@ instead of the in-process static-token fallback that only works locally. Against a 2026-07-28 deployment each call is a self-contained request: no handshake, no session id, and any session context the server needs travels as a -tool argument (ADR-0006). +tool argument (ADR-0009). - :mod:`witan.remote.oidc` — OIDC device-authorization-grant login + token cache. - :mod:`witan.remote.proxy` — :class:`RemoteServerProxy`, a drop-in stand-in diff --git a/packages/witan-core/witan_core/remote/__init__.py b/packages/witan-core/witan_core/remote/__init__.py index 67860b8d..bd00c44b 100644 --- a/packages/witan-core/witan_core/remote/__init__.py +++ b/packages/witan-core/witan_core/remote/__init__.py @@ -4,7 +4,7 @@ self over ``streamable-http`` with a per-user OIDC identity (ADR-0005, path a), factored out of witan-council so a second server (e.g. a deployed witan-code) can reuse it instead of copy-pasting. On MCP 2026-07-28 that transport carries -no handshake and no session id, so every call stands alone (witan ADR-0006): +no handshake and no session id, so every call stands alone (witan ADR-0009): - :mod:`witan_core.remote.config` — :class:`~witan_core.remote.config.RemoteConfig`, which deployment to talk to and how to authenticate to it, resolved from the diff --git a/packages/witan-core/witan_core/remote/config.py b/packages/witan-core/witan_core/remote/config.py index 3ff70101..ca5626ac 100644 --- a/packages/witan-core/witan_core/remote/config.py +++ b/packages/witan-core/witan_core/remote/config.py @@ -44,7 +44,7 @@ class RemoteConfig: through the deployment over ``streamable-http``, authenticated with a per-user Keycloak JWT (device-code flow, :mod:`witan_core.remote.oidc`). Against a 2026-07-28 deployment that connection is stateless — no - handshake, no session id (witan ADR-0006). + handshake, no session id (witan ADR-0009). These name the *client's* view of the deployment and are deliberately separate from the server-side identity config (``WITAN_ACTOR_TOKENS_FILE``