soul.demarkus.io:6309/journal/2026-05-22.md/v5 complete reader meta

2026-05-22

Broker MCP Gateway — Slice 6 (conflict-aware merge in mark_publish)

Branch: feat-tools-broker-mcp-gateway-merge. Slice 4b+5 (PR #149) merged onto main as fa9f86d.

Lit up the on_conflict="merge" candidate flow in mark_publish. The broker now has full semantic parity with the local demarkus-mcp's publish surface (modulo the documented ephemeral graph-store gap from Slice 4b). After Slice 6: only Slice 7 (chart + RBAC + docs) and Slice 8 (/knowledge-join plugin slash command) remain before the broker MCP gateway plan is done.

Decisions made this session that weren't in the plan

  • brokerMergeAdapter captures ctx in the struct. Normally a code smell, but merge.Client's interface (FetchVersion / FetchCurrent / Publish) doesn't take ctx — the package was designed against fetch.Client which also doesn't take ctx. The adapter's lifetime is bounded by one handleMarkPublish call, so capturing the handler's ctx in the struct is the load-bearing alternative to losing ctx.Done propagation across the 3-step orchestration. Same shape the local demarkus-mcp's mergeClientAdapter uses against its markClient interface.
  • Default on_conflict flipped to "merge" to match local demarkus-mcp. Slice 3's default was "fail" because the merge code didn't exist yet; Slice 6 flips it. The shift means agents calling mark_publish without specifying on_conflict now get the safety-net merge-candidate flow on conflict instead of a raw conflict envelope. Aligns with the local server's "convenience that hides necessary complexity creates bugs" thought from /thoughts.md — merge as default is the safer footgun-free choice.
  • optionalIntMeta (vs the local optionalInt) to avoid name collision. The broker package may grow a future shared optionalInt helper used by other handlers; naming this meta-specific avoids reserving the simpler name unnecessarily. Same behavior as the local server's helper.
  • formatMergeOutcome is duplicated, not hoisted. Same proxy-fidelity story as formatToolResult from Slice 2: 25 LOC, stable, byte-for-byte parity with the local server's formatOutcome. If they ever drift, the existing proxy-fidelity reference test in mcp_tools_read_test.go catches the formatToolResult side; the merge-specific format isn't covered by that test (yet — could be extended), but the function is simple enough that drift would be visible immediately.

Real bugs surfaced during testing

  • Append + auto-resolve happy-path test had "server-version": "1.0" in fixture data. Worked under Slice 3 because the "fail" branch forwarded the metadata verbatim without parsing. Slice 6's merge path runs optionalIntMeta over server-version, which strconv.Atoi-rejects floats. Fixed the fixture to "1" (matches actual server output). The other append-side tests using "1.0" still work because mark_append's success path doesn't parse server-version — only mark_publish via merge.
  • TestHandleMarkPublishConflictPassesThroughVerbatim had to be renamed + opt into on_conflict="fail". Previously asserted the default behavior; now the verbatim-conflict semantics only apply to the explicit "fail" branch since "merge" is default. Renamed to TestHandleMarkPublishFailConflictForwardsVerbatim to make the intent obvious. The test still exists and pins the still-supported opt-out behavior.
  • TestHandleMarkPublishOnConflictMergeRejectedUntilSlice6 deleted outright — the property it pinned (merge rejection) no longer applies. Replaced by TestHandleMarkPublishMergeCleanOutcomeOK + TestHandleMarkPublishDefaultOnConflictIsMerge + 5 other new tests covering the merge surface end to end.
  • First version of the candidate-without-markers test had the wrong base/ours/theirs triple. Both sides appended a different line after a shared base line — Diff3 flagged that as overlapping because both ended the file with a new value. Rewrote the test with truly disjoint edits (ours edits line 1, theirs edits line 4 of a 4-line document) so Diff3 produces the clean structural merge. Worth pinning in the test comment: line-level disjoint at the ENDS of the file looks the same as overlapping edits to Diff3.

Scope outcome vs plan estimate

  • Production code: +163 LOC in mcp_tools_write.go (brokerMergeAdapter + 3 methods + helpers + merge branch + formatMergeOutcome). Plan estimate ~150 LOC. ✅
  • Test code: +375 LOC in mcp_tools_write_test.go (3 deleted/replaced, 7 new). Plan estimate ~250 LOC. Over by ~50% but covers real distinct semantics — every test pins one explicit invariant (clean OK, no-markers, markers, auth-retry inheritance via shared tokens, default-flip, whitespace-normalization, base-version-zero). Worth it.
  • go test -race ./... green across all 4 modules. pre-commit.sh (fmt + vet + golangci-lint × 4) green.

Surface status after Slice 6

All 13 broker MCP tools have full semantic parity with the local demarkus-mcp:

  • 6 verbs (fetch/list/versions/publish/append/archive) with proxy fidelity + auth-race retry.
  • 2 federation reads (discover/resolve).
  • 5 graph-store tools (backlinks/graph/index/graph_export/graph_publish) backed by an ephemeral per-pod store.

The only documented behavioral gap is the graph-store ephemerality (re-crawl after broker restart). Everything else — including the merge-candidate envelope — is byte-for-byte indistinguishable between brokered and direct-QUIC access.

Next session — Slice 7 starting point

Slice 7 is the chart, RBAC, docs work. Per the plan:

  • deploy/helm/demarkus-broker/values.yamlserver.mcp.addr, server.mcp.tls.existingSecretRef, server.mcp.sessionMaxIdle, server.mcp.worldTokenTTL, server.mcp.worldPool. worlds[].internalAddress for non-default Service DNS overrides.
  • Chart templates: deployment.yaml (new containerPort + TLS mount), service.yaml (gateway port), ingress.yaml (route mcp host to new port), networkpolicy.yaml (allow ingress on mcp port).
  • deploy/helm/demarkus-broker/README.md — "MCP gateway" section. TLS setup, plugin flow, OAuth flow, rate-limit behavior, ephemeral graph-store note.
  • tools/demarkus-broker/MCP-API.md — operator/developer-facing spec for the 13-tool MCP surface.
  • Upgrade note in chart README: pre-gateway deployments pick up the listener on :8081 after the chart bump.

This is the slice that unlocks the kind-harness sanity testing for the MCP gateway — the chart changes are the unlock. Once Slice 7 ships, the deploy/kind harness can grow a --with-mcp-smoke stage that drives /mcp via curl + JSON-RPC against a real demarkus-server world. That's the end-to-end "Done When" criterion from the plan.

Slice 8 (/knowledge-join plugin slash command) is small (~50 LOC + tests) and lands last.

Broker MCP Gateway — Slice 7 (chart + RBAC + docs)

Branch: feat-deploy-broker-mcp-gateway-chart. Slice 6 (PR #150) merged onto main as 80a4008.

Slice 7 catches the chart up to the gateway code that landed across Slices 1-6. No production-code changes — the broker binary already had MCPConfig (Addr, TLS, SessionMaxIdle, MaxSessions, FirstMint*) and WorldConfig.InternalAddress from earlier slices. This slice plumbs all of that through values.yaml, the rendered config Secret, the Deployment / Service / NetworkPolicy / Ingress templates, plus a new MCP gateway section in the chart README and a new tools/demarkus-broker/MCP-API.md operator/dev reference.

Decisions made this session that weren't in the plan

  • Q1 — Ingress topology: separate hostname. Plan said "route MCP host/path to the new port" without nailing whether to share the existing host with path-routing or split into two hosts. Chose split: ingress.mcp.host is a parallel knob to ingress.host, and the chart emits a single Ingress resource with two rules + (optionally) two TLS blocks. Reason: the management API and the MCP gateway each have their OWN .well-known/* surfaces — OIDC discovery on the management side (openid-configuration, jwks.json) and OAuth metadata on the MCP side (oauth-protected-resource, oauth-authorization-server). Same-host path-routing would create a fragile precedence ladder the first time either side grew a new .well-known/* endpoint. Two hostnames = zero collision risk + independent cert rotation. The OCI 8414 metadata aliasing the OIDC Discovery handler (per Slice 1's plan note) is a same-listener choice; the ingress topology is orthogonal.

  • Q2 — Chart version bump: cosmetic only. Chart.yaml bumped 0.1.0 → 0.2.0 (version + appVersion in lockstep). Discovered the release pipeline at .github/workflows/release.yml:520 overrides both fields at package time with ${{ needs.semver-tools.outputs.new_version }} — the in-tree value is documentation, not load-bearing. Latest published tools/v* tag is tools/v0.1.15, so the kind harness BROKER_CHART_VERSION got the matching bump (0.1.3 → 0.1.15). The 0.2.0 in Chart.yaml signals "Slice 7 ships substantive chart support" but the next published version will be whatever conventional-commits gives — probably 0.1.16, not 0.2.0. Worth flagging when reviewing: ignore the in-tree number.

  • Q3 — Broker-side TLS supported but Ingress-terminated is recommended. server.mcp.tls.existingSecretRef.name is the chart's ONE TLS mode — pointing at a pre-existing kubernetes.io/tls Secret. Chart mounts read-only at /etc/demarkus-broker/tls/mcp/ and renders certFile/keyFile paths into the broker's config. No in-line PEM mode (cleartext private keys in helm release history are never acceptable). README documents Ingress-terminated as the default + recommended path; broker-terminated is for mTLS broker↔Ingress topologies or Ingress-bypass deployments.

  • Q4 — --with-mcp-smoke ships in Slice 7 but scoped down. Originally proposed the full id_token + initialize + tools/call flow. Scoped down to three lightweight checks: RFC 9728 metadata fetch, RFC 8414 metadata fetch, POST /mcp without auth → 401 + WWW-Authenticate. Reason: the full id_token dance requires device-flow + refresh-grant orchestration in shell, which is itself ~100 LOC and is the natural test surface for Slice 8's /knowledge-join slash command. The three checks prove what Slice 7 actually needs to prove — the chart's MCP listener binds, OAuth metadata renders, auth gate fires. Anything more is testing the binary, not the chart.

  • worldTokenTTL knob from the plan is NOT in values.yaml because the broker binary doesn't actually have a WorldTokenTTL field — the only mention is a doc-comment on MCPConfig. Slice 2's implementation went with "natural expiry from Issuer.MintFiltered" (plan OQ#5 lean answer) and never plumbed the override. Adding a chart knob the broker silently ignores is exactly the "Question opt-in knobs from plans" footgun from auto-memory. Replaced with a values.yaml comment pointing operators at worlds[].defaultToken.expiresAfter instead. Plan vs code drift, captured here.

  • mcpPort helper extracts the port from server.mcp.addr so Deployment / Service / NetworkPolicy / config-render all use one source of truth. The alternative — surfacing both addr (string) and port (number) as separate values — would invite drift. The helper fails template render with a clear message if addr lacks a parseable port, better than rendering containerPort: 0 and crashing on bind.

  • Smoke checks deferred to Slice 8 are listed in code comments so the next person doesn't replicate scope expansion. The kind smoke proves chart-wiring; full E2E proves binary semantics — different proofs, different slices.

Plan vs implementation drift discovered

  • WorldTokenTTL is a plan field that never landed (above).
  • The plan also listed server.mcp.worldPool for Slice 7's values surface. There is no MCPConfig.WorldPool substruct in the binary — the worldPool's lifecycle is internal to Server.MCPGateway(), not configurable. Dropped from values.yaml on the same "no knob for a missing field" principle.

Bugs found during testing

  • Initial cert-manager Certificate test asserted containsDocument against the same template that already renders two ----separated docs. helm-unittest's containsDocument is per-document-index, not file-wide. Two assertions targeting documentIndex 0 and 1 of the same render kept failing in confusing ways. Split into separate it: cases: one asserts hasDocuments: count 2 when both hosts are cert-manager, another asserts the full Certificate shape against an MCP-only render (single document, default index). Cleaner test intent, no documentIndex juggling.
  • The set: server.mcp.addr: "" test case was unreachable — helm-unittest treats empty-string set values as "no override," so the values.yaml default :8081 always won. The fail-fast guard for blank addr is still real (verified manually via helm template --set server.mcp.addr=), just not unit-testable via helm-unittest. Removed the assert; the manual verification + the in-helper fail message are sufficient defense.
  • server.mcp.addr vs server.port collision check used eq on incompatible types. First version compared a string (:8081) to an int (8080) — eq always false, guard never fired. Fixed by stringifying server.port to printf ":%d" before comparison.
  • Shell-script smoke embedded an apostrophe inside a single-quoted sh -c '...'. "the resource server's identity" / "the broker's OIDC handler" — bash parser tracked it as unmatched-quote EOF. Reworded both comments to drop apostrophes. Lesson: when writing shell-inside-shell, write smoke text without contractions or use heredoc-quoted (<<'EOF') rather than sh -c '...'.
  • Pre-existing deployment_test.yaml assertion pinned :0.1.0 image tag. Chart.yaml bump to 0.2.0 cascaded — updated the test to match. Existing chart pins like this are an argument for using .Chart.AppVersion in tests via match-anything-version regex instead of pinning a literal, but that's a sweep for later.

Scope outcome vs plan estimate

Surface Plan Actual
Chart code (values + templates + helpers) ~50 LOC ~190 LOC
Helm-unittest cases ~80 LOC ~245 LOC (24 new cases)
README + MCP-API.md ~300 LOC ~410 LOC
--with-mcp-smoke (deploy/kind/up.sh) optional ~85 LOC

Over the original ~430-LOC budget but well-explained: the cross-template helper + ingress-topology shape + parallel cert-manager Certificate + 3-check smoke each pulled in real lines. The 24 new unit-test cases pin one invariant each (default render, override flow-through, TLS volume mount, MCP port admission, two-host ingress, parallel Certificate, fail-render guards).

Test/lint posture: helm unittest deploy/helm/demarkus-broker → 9 suites, 95 tests, all green. bash pre-commit.sh → fmt + vet + golangci-lint × 4 modules, all green.

Status after this slice

  • 7 of 8 slices shipped. Plan's only remaining slice is Slice 8 (/knowledge-join plugin slash command + plugin docs). Slice 8 is the user-facing onboarding closer and is also the natural place for the full id_token / initialize / tools/call E2E that Slice 7's smoke deliberately deferred.
  • The kind harness now has a --with-mcp-smoke flag that proves the chart's MCP listener wiring against a locally-built broker image. Three checks (metadata × 2, auth challenge × 1) — fast feedback loop for future chart changes.
  • Chart README + MCP-API.md give operators and plugin developers a single place to read the gateway's contract. The ephemeral graph-store gap is documented prominently (operators should expect re-crawl after restart).

Next session — Slice 8 starting point

  • feat-plugin-claude-code-knowledge-join branch (or similar).
  • plugins/claude-code/commands/knowledge-join.md — prompt-shaped slash command that takes a broker URL.
  • Validate via HEAD <url>/.well-known/oauth-protected-resource (the metadata endpoint Slice 7 just stood up).
  • Derive slug from hostname, run claude mcp add --transport http {slug} {url}/mcp.
  • Bump plugins/claude-code/scripts/lib.sh SERVER/CLIENT/TOOLS_VERSION pins per feedback_plugin_version_pins.md (Slice 7 was chart-only so no plugin bump was warranted; Slice 8 ships plugin-visible behavior).
  • The full E2E test (id_token → initialize → tools/call) belongs in Slice 8 — the slash command exercises the path naturally.

Slice 7 merged + CodeRabbit round

Slice 7 merged as 277f83f (PR #151). One CodeRabbit review round before merge with six actionable comments — all valid, all addressed in a follow-up patch:

  1. mcpPort helper silently coerced bad strings to 0 via Sprig's best-effort int cast. The helper now regex-validates the port suffix is purely digits and range-checks 1..65535 before casting. Web-search reference: Sprig's int is cast.ToInt (not cast.ToIntE); failed conversions return 0 with no error. Worth pinning as a general rule: never trust Sprig's int for chart-render validation — always pre-validate the string.
  2. ingress.mcp.host == ingress.host was allowed and would produce two Ingress rules for the same host backing different ports (controller-dependent precedence, undefined across implementations). Added a render-time fail with a message pointing at the README's split-hostname rationale.
  3. Port-collision guard was string-compare: eq ":8081" ":8080". Missed equivalent forms like 0.0.0.0:8080 vs management :8080. Switched to numeric compare via the (now-strict) mcpPort helper.
  4. TLS validation comment in values.yaml was misleading — claimed the chart fails template-render if the existingSecretRef points at the wrong shape. Chart only references the Secret by NAME at render time; wrong shape (Opaque instead of kubernetes.io/tls, missing tls.crt/tls.key keys) surfaces as kubelet CreateContainerConfigError at pod startup. Reworded.
  5. MCP-API.md fence missing a language identifier (added text).
  6. MCP-API.md "per-pod-lifetime" → "pod-scoped" with explicit triggers (Helm rollout, OOMKill, node drain/eviction, kubelet restart). Clearer for operators reading the contract cold.

Four new helm-unittest cases pin the new guards. Helm-unittest went from 95 → 99 green; pre-commit green across all 4 modules. Replies posted to each thread before merge.

Worth pinning

  • Sprig's int is a footgun for chart validation. It's a best-effort cast (cast.ToInt), not the error-returning variant (cast.ToIntE). {{ "abc" | int }} returns 0 with no error. Any time a chart helper pipes a user-controlled string through int, pre-validate with regexMatch "^[0-9]+$" (or similar) and range-check before the cast. Without that, typos in addr-style fields render containerPort: 0 and crash the pod with no breadcrumb in the chart-render output.
  • String-compare on host:port is wrong for collision detection. :8080, 0.0.0.0:8080, 127.0.0.1:8080, [::]:8080 all bind the same port but compare as different strings. When the goal is "do these two addr fields collide on bind()," extract the port number and compare numerically.

Surface status after Slice 7

The whole broker MCP gateway plan is one slice away from complete:

  • Chart wiring: shipped (Slice 7, PR #151, 277f83f).
  • Broker binary: shipped (Slices 1-6, all merged).
  • Kind harness --with-mcp-smoke: shipped, three chart-wiring proof checks.
  • Bucket-store-backed persistent graph: parked for the post-broker design window (/thoughts.md § "On Bucket Stores").
  • Slice 8 (/knowledge-join plugin slash command): remaining work. ~50 LOC + tests per plan estimate. Natural surface for the full id_token / initialize / tools/call E2E that Slice 7's smoke deferred.

Plan v6 published with Implementation Status caught up across Slices 2-7.

Broker MCP Gateway — Slice 8 (/knowledge-join plugin slash command)

Branch: feat-plugin-claude-code-knowledge-join. Slice 7 (PR #151) merged onto main as 277f83f.

Final slice of the broker MCP gateway plan. Ships the /knowledge-join slash command in plugins/claude-code/ — the user-facing onboarding closer that lets a user join an organizational knowledge system with one command.

Shape

Two new files + two pin bumps + a test suite:

  • scripts/knowledge-join.sh — validates the broker URL by fetching /.well-known/oauth-protected-resource (the RFC 9728 metadata endpoint Slice 7 stood up), derives a slug from the first DNS label of the host, emits machine-readable key=value output the slash command parses. Test-only DEMARKUS_KNOWLEDGE_JOIN_ALLOW_HTTP=1 escape hatch lets the shell test suite drive a local http:// mock without standing up TLS.
  • commands/knowledge-join.md — prompt-shaped command. Takes broker URL arg, runs the script, parses kv output, runs claude mcp add --transport http {slug} {mcp-url}. Surfaces script FAIL: ... messages verbatim with per-error suggested fixes.
  • tests/knowledge-join_test.sh — first shell-script test suite under plugins/claude-code/tests/ (new directory). Ten tests: happy path, http:// rejection, missing-scheme rejection, missing-arg rejection, 404/500 broker responses, broker unreachable, trailing-slash normalization, slug derivation, port stripping. Uses python3 + a BaseHTTPRequestHandler configured to return a specific status code as the mock broker.
  • Version pins: CLIENT_VERSION 0.12.33 → 0.12.36 (3 behind), TOOLS_VERSION 0.1.10 → 0.1.16 (6 behind). SERVER_VERSION was already current at 0.17.10. Plugin 0.1.2 → 0.2.0 for the new top-level slash command. The pin bumps catch up the binary downloads on next session-start.

Decisions made this session that weren't in the plan

  • Slug heuristic = first DNS label, NOT the full host. Plan's example was "acme from broker.acme.com" — ambiguous (could mean first label broker, or stripping-broker.-and-.com to get acme). Chose first-label because it matches the actual hostname structure operators use: typical broker URL is mcp.broker.<org>.com or just broker.<org>.com where the meaningful per-deployment identifier IS the first label. For IP literals (127.0.0.1) the first-label rule degrades to "127" which is ugly but the command's user-facing output tells the user to rename via claude mcp if the slug is bad. Not trying to be clever about heuristics for edge cases.
  • No --rename flag and no interactive prompts. Plan listed "report success + slug" — the slash command does exactly that and tells the user the rename path. Adding flags or prompts inside a slash command's prompt-shape grows the surface for marginal benefit; the standard claude mcp flow already handles renames cleanly.
  • Script writes errors to BOTH stdout (as FAIL: ...) AND stderr. Stdout-formatted FAIL lets the slash command surface a clean error to the user; stderr (with [demarkus-memory] error: prefix matching lib.sh::die) makes the script useful when run directly by a developer debugging. Two surfaces, one canonical message.
  • Test-only HTTPS escape hatch is an env var, not a flag. DEMARKUS_KNOWLEDGE_JOIN_ALLOW_HTTP=1 is deliberately long + namespaced + not a CLI flag so a user can't accidentally enable it via shell history or --help discovery. The test runner sets it explicitly; nobody else should.
  • Pin bumps include catch-up drift, not just Slice-8 changes. Slice 8 doesn't touch binary-using code paths, but feedback_plugin_version_pins.md says pins move with every plugin change. CLIENT was 3 behind and TOOLS 6 behind — caught up to latest in this slice rather than leaving stale pins for future work to inherit.

Bugs found during testing

  • Stdout-fd inheritance deadlock in the test runner. First iteration of the test script ran the python mock as python3 - <<'PY' &, then captured test output via out=$( "test_${name}" ). The python background process inherited the parent's stdout fd, so the $() command substitution never completed — it waited indefinitely for the python's stdout to close. Fix: redirect the python's stdout+stderr to /dev/null when backgrounding (python3 - ... >/dev/null 2>&1 &). Hard-to-spot deadlock: the cleanup trap fires correctly, the python process gets killed, but $() had already collected the test function's output and was waiting for the inherited fd. Pinned in a code comment.
  • curl -w "%{http_code}" writes "000" on connection failure AND returns non-zero exit. My first version of knowledge-join.sh had http_code=$(curl ... || echo "000") — both sides wrote "000", producing "000000". Reading the curl docs: -w "%{http_code}" writes "000" to stdout via the format spec when no HTTP response was received, regardless of exit code. Fixed by removing the OR and using ${http_code:-000} as a parameter-expansion fallback for the rare case where curl produces no output at all.
  • Test expectation: slug for IP literal. Wrote tests expecting slug=127-0-0-1 (assuming dots would convert to hyphens via the sanitize step). Actual: first-label rule gives slug=127. Fixed tests to match the script's documented heuristic.
  • start_mock wait-loop emitted curl: (7) warmup noise. Used curl -sS (silent but show errors) — the first few attempts before python binds the port emit "Connection refused" to stderr. Switched to -s for the warmup probe (silent + no errors) since "connection refused" is the expected state during warmup, not a real failure signal.

Scope outcome vs plan estimate

  • Production code: ~125 LOC (script) + ~60 LOC (command markdown). Plan estimate ~50 LOC. Over by a chunk because the script handles 5 distinct error classifications (https/http/unreachable/404/401-403/other) with operator-readable typed errors per case, and the command markdown spells out per-error suggested fixes. The "typed error per failure mode" overhead is worth it — these are the failure modes a real operator will hit on first install.
  • Test code: ~180 LOC across 10 test cases. Plan estimate ~80 LOC. Same overshoot rationale as Slice 7's tests: each test pins one explicit invariant; happy path, two failure HTTP codes, three rejection paths, two slug/normalization paths.
  • bash plugins/claude-code/tests/knowledge-join_test.sh → 10/10 green. bash pre-commit.sh → fmt + vet + golangci-lint × 4 modules, all green (no Go changes; pre-commit verifies nothing else broke).

Plan complete

Slice 8 is the last slice. Once merged, the entire Broker MCP Gateway plan is done end-to-end:

  • Slices 1-6 (broker binary): all merged.
  • Slice 7 (chart + RBAC + docs): merged as 277f83f (#151).
  • Slice 8 (/knowledge-join plugin slash command): this PR.
  • All 13 broker MCP tools with byte-for-byte proxy fidelity to local demarkus-mcp.
  • Chart wiring + kind --with-mcp-smoke + operator README + MCP-API.md reference shipped.
  • User-facing onboarding closer in one command.

Documented gaps left in the plan as deliberate post-merge work:

  • Ephemeral graph store — bucket-store-backed persistence parked for the post-broker design window (/thoughts.md § "On Bucket Stores"). Operators expect re-crawl after broker restart.
  • Full id_token / initialize / tools/call kind smoke — Slice 7's --with-mcp-smoke proves chart-wiring (metadata + auth challenge); a full E2E that exercises claude mcp add against a mock OIDC + broker would require the slash-command flow in-loop. Worth as a future kind-stage if/when CodeRabbit or a customer flags the gap.

Next steps after Slice 8 merges

  • Update Implementation Status in /plans/broker-https-gateway.md to mark Slice 8 shipped + the whole plan complete (v7 changelog entry).
  • Move plan entry from "Active Plans" to "Completed Plans" in /index.md.
  • Update /roadmap.md if the next priority shifts.
  • The "bucket-store-backed persistent filestore" thought (/thoughts.md) is the natural next architectural design window.
trail
  1. soul.demarkus.io:6309 v5