Repository navigation
docs(mcp): record the MCP server design and what the spike got wrong - #785
Conversation
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Advanced Run ID: Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@coderabbitai review Docs-only PR, but the prose makes specific, checkable claims about third-party SDK behaviour and about this repo's invariants. Those claims are the deliverable, so please weight correctness over style:
|
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo '== Changed documentation files =='
git diff --name-only "$(git merge-base HEAD origin/main)" HEAD 2>/dev/null || git diff --name-only HEAD~1 HEAD
echo
echo '== Candidate design documents =='
fd -t f -a '02-guards\.md|04-verified-findings\.md|AGENTS\.md' . \
-E .git -E node_modules
echo
echo '== Relevant repository symbols =='
rg -n -C 5 --glob '*.cs' \
'class\s+FlockScope\b|record\s+FlockScope\b|struct\s+FlockScope\b|IsUnrestricted|IsResolved|class\s+RecordDailyEntryHandler\b|RecordDailyEntryHandler\s*\(|class\s+IdempotencyMiddleware\b|IdempotencyMiddleware\s*\(' \
. -g '!**/bin/**' -g '!**/obj/**' || true
echo
echo '== Package references for ModelContextProtocol =='
rg -n -C 2 'ModelContextProtocol' --glob '*.*proj' --glob 'Directory.Packages.props' --glob 'packages.lock.json' . || true
echo
echo '== SDK v2.2.0: transport registrations =='
curl -fsSL --retry 2 \
'https://raw.githubusercontent.com/modelcontextprotocol/csharp-sdk/v2.2.0/src/ModelContextProtocol.AspNetCore/Extensions/McpServerBuilderExtensions.cs' \
| grep -n -A35 -B10 'WithHttpTransport' || true
echo
echo '== SDK v2.2.0: CreateSessionAsync stateless behavior =='
curl -fsSL --retry 2 \
'https://raw.githubusercontent.com/modelcontextprotocol/csharp-sdk/v2.2.0/src/ModelContextProtocol.AspNetCore/Handlers/StreamableHttpHandler.cs' \
| grep -n -A35 -B10 'CreateSessionAsync' || true
echo
echo '== SDK v2.2.0: idle tracker start behavior =='
curl -fsSL --retry 2 \
'https://raw.githubusercontent.com/modelcontextprotocol/csharp-sdk/v2.2.0/src/ModelContextProtocol.AspNetCore/Handlers/IdleTrackingBackgroundService.cs' \
| grep -n -A45 -B10 'StartAsync' || trueLength of output: 187344 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo '== Design documents under review =='
for f in \
docs/plans/770-mcp-server/00-README.md \
docs/plans/770-mcp-server/01-design.md \
docs/plans/770-mcp-server/02-guards.md \
docs/plans/770-mcp-server/03-arena-synthesis.md \
docs/plans/770-mcp-server/04-verified-findings.md \
docs/plans/README.md; do
echo
echo "----- $f -----"
cat -n "$f"
done
echo
echo '== Guard-dependent implementation ranges =='
sed -n '1,120p' src/Cluckwork.Infrastructure/Persistence/FlockScope.cs
sed -n '45,190p' src/Cluckwork.Api/Middleware/IdempotencyMiddleware.cs
sed -n '1,260p' src/Cluckwork.Application/Features/DailyEntries/RecordDailyEntry/RecordDailyEntryHandler.cs
echo
echo '== Idempotency exemptions and pipeline ordering =='
rg -n -C 12 'Exempt|excluded|Path|IdempotencyMiddleware|UseMiddleware<.*Idempotency|UseCluckwork.*Idempotency|MapMcp|Map.*Mcp' \
src/Cluckwork.Api --glob '*.cs'
echo
echo '== Relevant idempotency and daily-entry test assertions =='
rg -n -C 8 'Idempotency|Idempotency-Key|RecordDailyEntry|DailyEntr|AuditEvent|AuditActions' \
tests --glob '*.cs' -g '!**/bin/**' -g '!**/obj/**'
echo
echo '== csharp-sdk v2.2.0 candidate source paths =='
gh api 'repos/modelcontextprotocol/csharp-sdk/git/trees/v2.2.0?recursive=1' --paginate \
--jq '.tree[] | select(.path | test("(McpServerBuilderExtensions|StreamableHttpHandler|IdleTrackingBackgroundService)\\.cs$")) | .path'
echo
echo '== csharp-sdk v2.2.0 package target frameworks =='
gh api 'repos/modelcontextprotocol/csharp-sdk/git/trees/v2.2.0?recursive=1' --paginate \
--jq '.tree[] | select(.path | test("ModelContextProtocol\\.AspNetCore\\.csproj$")) | .path'Length of output: 202096 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo '== 04 verified findings =='
cat -n docs/plans/770-mcp-server/04-verified-findings.md
echo
echo '== 02 guard headings and guard sections =='
rg -n '^#{1,3} |FlockScope|IHostedService|daily-entry|DailyEntry|replay|Idempotency' \
docs/plans/770-mcp-server/02-guards.md
cat -n docs/plans/770-mcp-server/02-guards.md
echo
echo '== 01 design headings and MCP transport/idempotency sections =='
rg -n '^#{1,3} |SessionMode|ScopeRequests|Idempotency|/mcp|IIdempotentUnit|BodyReading|PerSession|IHostedService' \
docs/plans/770-mcp-server/01-design.md
cat -n docs/plans/770-mcp-server/01-design.md
echo
echo '== Exact csharp-sdk v2.2.0 evidence =='
for path in \
src/ModelContextProtocol.AspNetCore/HttpMcpServerBuilderExtensions.cs \
src/ModelContextProtocol.AspNetCore/StreamableHttpHandler.cs \
src/ModelContextProtocol.AspNetCore/IdleTrackingBackgroundService.cs \
src/ModelContextProtocol.AspNetCore/ModelContextProtocol.AspNetCore.csproj; do
echo
echo "----- $path -----"
gh api -H 'Accept: application/vnd.github.raw+json' \
"repos/modelcontextprotocol/csharp-sdk/contents/$path?ref=v2.2.0" \
| grep -n -E -A40 -B12 \
'WithHttpTransport|CreateSessionAsync|serveStatelessly|ScopeRequests|StartAsync|SessionMode|TargetFramework|TargetFrameworks|PerSessionExecutionContext'
done
echo
echo '== Exact Cluckwork evidence for MCP blockers and guards =='
sed -n '48,180p' src/Cluckwork.Api/Middleware/IdempotencyMiddleware.cs
sed -n '190,250p' src/Cluckwork.Api/Endpoints/DailyEntries/DailyEntryEndpoints.cs
sed -n '1,240p' src/Cluckwork.Application/Features/DailyEntries/RecordDailyEntry/RecordDailyEntryHandler.cs
sed -n '300,345p' tests/Cluckwork.Api.IntegrationTests/SubmitDailyEntryTests.cs
rg -n -C 8 'BodyReadingEndpointTests|ReadsRequestBodyAttribute|IAcceptsMetadata' tests src/Cluckwork.Api --glob '*.cs'Length of output: 130093 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo '== Remaining design documents =='
cat -n docs/plans/770-mcp-server/00-README.md
cat -n docs/plans/770-mcp-server/03-arena-synthesis.md
cat -n docs/plans/README.md
echo
echo '== AGENTS.md rules and current PR changes to AGENTS.md =='
rg -n -C 8 '`#271`|AddSingleton|AddHostedService|twice derived|two misses|blocker' AGENTS.md
git diff --unified=40 "$(git merge-base HEAD origin/main)" HEAD -- AGENTS.md
echo
echo '== Exact SDK claims used in the design =='
for path in \
src/ModelContextProtocol.AspNetCore/HttpMcpServerBuilderExtensions.cs \
src/ModelContextProtocol.AspNetCore/McpEndpointRouteBuilderExtensions.cs \
src/ModelContextProtocol.AspNetCore/HttpServerTransportOptions.cs \
src/ModelContextProtocol/McpServerOptions.cs \
src/ModelContextProtocol.AspNetCore/AuthorizationFilterSetup.cs; do
echo
echo "----- $path -----"
gh api -H 'Accept: application/vnd.github.raw+json' \
"repos/modelcontextprotocol/csharp-sdk/contents/$path?ref=v2.2.0" 2>/dev/null \
| grep -n -E -A18 -B8 \
'MapMcp|HandlePostRequestAsync|ScopeRequests|AddAuthorizationFilters|RemoveAt|McpProtocolException|ConfigureSessionOptions|PerSessionExecutionContext|DefaultValue' \
|| true
done
echo
echo '== Package and compiler warning configuration =='
rg -n -C 3 'TreatWarningsAsErrors|ModelContextProtocol.AspNetCore|Version=' \
Directory.Build.props Directory.Packages.props --glob '*' 2>/dev/null || true
echo
echo '== FlockScopeGuard fail-open implementation =='
fd -t f -a '*FlockScopeGuard*.cs' src
rg -n -C 12 'class\s+FlockScopeGuard\b|CheckAsync\s*\(' src --glob '*.cs'
echo
echo '== Diff statistics =='
git diff --check "$(git merge-base HEAD origin/main)" HEAD
git diff --stat "$(git merge-base HEAD origin/main)" HEADLength of output: 52356
Change the sentence to say that the wording needs amendment in The requested technical checks otherwise pass:
You are interacting with an AI system. |
…tion CodeRabbit review of #785 found that 01-design.md claimed the AGENTS.md wording 'needs amending in this PR' while the PR ships no AGENTS.md change, contradicting 00-README.md which correctly says it is raised separately. Corrected to point at #786. Three more instances of the same ambiguity, found by grepping for the phrase rather than waiting for a second round: two uses of 'this PR' meaning the future implementing PR, now explicit; and the open question asking whether to file the FlockScopeGuard issue, which #787 answered.
|
@coderabbitai Fixed in 7522f4f, and thank you — that was a real inconsistency, not a nit.
Rather than fix only the line you pointed at, I grepped the whole plan directory for the same ambiguity and found three more, all now corrected in the same commit:
Verification: Noting for the record that your seven technical confirmations line up with what I verified independently against the SDK source at the No further changes planned from this round. |
|
Tip For best results, initiate chat on the files or code changes.
The revised wording now distinguishes this documentation PR from the future implementation PR. Resolving the 🐇 ✓ If you found this review helpful, would you consider giving us a shout-out on X? Thank you for using CodeRabbit! You are interacting with an AI system. |
|
Pushed b0a5954 — an independent adversarial review of the synthesized design, folded in. New file: An independent reviewer on a different model family was handed the design and told to find what is wrong, explicitly barred from re-checking the six facts in 1. The identity bridge screened parameters, not the dependency graph. 2. The extracted idempotency port had no success/failure contract. Commit eligibility today is an HTTP 2xx ( It also proposed a cheaper shape the design never considered — route the write through the existing HTTP endpoint over loopback with the caller's bearer and a tool-namespaced 3. Name-addressed writes had no ambiguity contract. Duplicate flock names are legal — 4. No bounded-result contract, and I conflated two mechanisms. 5. Guard rows 2 and 19 could not go red on their stated mutations. Row 2: deleting the It also refuted something I had suggested investigating: there is no JSON-RPC batch logging defect against #214, because MCP removed batching in protocol revision 2025-06-18. Recorded so nobody reopens it. Guards went from 28 to 32. @coderabbitai review — this commit landed after your approval, so the approval no longer covers the head SHA. The new content makes falsifiable claims about |
|
Your plan includes PR reviews subject to rate limits. More reviews will be available in 15 minutes. |
#770 was a feasibility spike that recorded a read of the work without writing code. This is the design that came out of it, produced by running three independent design candidates in parallel and synthesizing them. Four of the spike's premises did not survive verification against the SDK source and the repo, and each is recorded with its evidence: - The SDK already sets `mcpServerServices = context.RequestServices` and `ScopeRequests = false` under `SessionMode.Stateless`. The setting worth guarding is `SessionMode`; a guard on `ScopeRequests` reddens on a mutation that leaves the product safe. - MCP does not function at all until `/mcp` is exempted from `IdempotencyMiddleware` — every MCP request is a POST, so an unexempted pipeline 400s `initialize` and `tools/list`. Blocking even for the read-only demo the issue sized in days. - #271 is touched: `WithHttpTransport` registers three singletons and a hosted service inside the package, unconditionally. The blocker list is not extended (the service is inert under Stateless), but AGENTS.md's instruction to re-derive it by walking `src/` cannot see package registrations. - `ModelContextProtocol.AspNetCore` 2.2.0 ships a net10.0 target, so the issue's open ".NET 10 compat" decision is answered. Design only. No code written and nothing shipped.
…tion CodeRabbit review of #785 found that 01-design.md claimed the AGENTS.md wording 'needs amending in this PR' while the PR ships no AGENTS.md change, contradicting 00-README.md which correctly says it is raised separately. Corrected to point at #786. Three more instances of the same ambiguity, found by grepping for the phrase rather than waiting for a second round: two uses of 'this PR' meaning the future implementing PR, now explicit; and the open question asking whether to file the FlockScopeGuard issue, which #787 answered.
An independent reviewer on a different model family was given the synthesized design and told to find what is wrong. It found four major defects and one minor. All five were checked against source before being accepted; all five hold. - The identity bridge screened tool PARAMETERS and called that the boundary. The boundary is the scope: a tool can inject a helper that opens a second scope via IServiceScopeFactory, copy only AccountId, and read every flock, because the new FlockScope is unresolved and therefore unrestricted. Both original guards passed. - The extracted idempotency port specified key identity but not OUTCOME. Commit eligibility today is an HTTP 2xx; a tool call has no HTTP status, and the SDK turns exceptions into CallToolResult.IsError inside its own dispatch. An implementation publishing on "returned normally" caches a domain failure permanently. - Name-addressed writes had no ambiguity contract, though duplicate flock names are legal and FlockRepository's tie-break exists because of it. - No bounded-result contract, and ReadsRequestBodyAttribute was conflated with the #309 body cap, which reads different metadata. /mcp had no body cap and no rate limit. - Guard rows 2 and 19 could not go red on their stated mutations. Also records a cheaper shape the design never considered: route the write through the existing HTTP endpoint over loopback, avoiding the #307 extraction entirely. That is the design's self-declared largest risk, so it should be priced before anyone starts slice 1. And refutes one thing I suggested investigating: there is no JSON-RPC batch logging defect, because MCP removed batching in revision 2025-06-18.
PR #790 merged the AGENTS.md walk-scope amendment, so three places in this record that described it as raised-but-open are now wrong in the same way CodeRabbit caught last round: a document asserting a follow-up is pending when it has shipped. Rebased onto main so the claim is checkable against the merged wording.
b0a5954 to
d3434db
Compare
|
@coderabbitai review Re-requesting on head Two commits to look at since that approval:
Also worth a look: Docs-only throughout; no source changes. |
🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
base="$(git merge-base HEAD origin/main 2>/dev/null || git merge-base HEAD main)"
echo '== Changed files since main =='
git diff --name-only "$base" HEAD
echo
echo '== Documentation changes under review =='
git diff --unified=35 "$base" HEAD -- docs/plans/770-mcp-server
echo
echo '== Maps of relevant implementation files =='
for file in \
src/Cluckwork.Api/Middleware/IdempotencyMiddleware.cs \
src/Cluckwork.Infrastructure/Persistence/PaymentRepository.cs \
src/Cluckwork.Api/Endpoints/Sales/SaleEndpoints.cs; do
if test -f "$file"; then
echo "----- $file -----"
ast-grep outline "$file" --items all || true
fi
done
echo
echo '== Idempotency middleware: status eligibility and claim release =='
rg -n -C 18 --glob '*.cs' \
'StatusCode|StatusCodes|IsSuccessStatusCode|2[0-9]{2}|Commit|Publish|Release|Claim|Idempotency' \
src/Cluckwork.Api/Middleware/IdempotencyMiddleware.cs
echo
echo '== PaymentRepository materialization and sales endpoint limit =='
rg -n -C 14 --glob '*.cs' \
'class\s+PaymentRepository\b|Task<.*Payment|ToListAsync|ToArrayAsync|Take\s*\(|PageSize|limit|Limit|Max' \
src/Cluckwork.Infrastructure/Persistence/PaymentRepository.cs \
src/Cluckwork.Api/Endpoints/Sales/SaleEndpoints.cs
echo
echo '== Request-body metadata attribute versus body-size middleware =='
rg -n -C 14 --glob '*.cs' \
'ReadsRequestBodyAttribute|MaxRequestBodyBytesMetadata|MaxRequestBodyBytes|BodyReadingEndpointTests|IAcceptsMetadata' \
src tests
echo
echo '== Guard rows 2 and 19 and their stated mutations =='
rg -n -A16 -B5 '^\|[[:space:]]*(2|19)[[:space:]]*\||Guard 2|Guard 19|CurrentUserContext\.IsResolved|/mcp|alias' \
docs/plans/770-mcp-server/02-guards.md
echo
echo '== `#786` cross references and main AGENTS.md evidence =='
rg -n -C 4 '`#786`|AGENTS\.md|pending|merged|amend' docs/plans/770-mcp-server AGENTS.md
git show origin/main:AGENTS.md | rg -n -C 5 '`#271`|AddSingleton|AddHostedService|package|registration|blocker' || true
echo
echo '== Documentation consistency checks =='
git diff --check "$base" HEAD
fd -t f -a . docs/plans/770-mcp-server | sortLength of output: 113131 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo '== Locate payment repository and body-limit implementation =='
fd -t f -a -i 'PaymentRepository.cs' .
fd -t f -a -i '*Body*Limit*.cs' src tests
fd -t f -a -i '*RequestBody*.cs' src tests
echo
echo '== Payment repository implementation =='
payment_file="$(fd -t f -a -i 'PaymentRepository.cs' . | head -n 1)"
if test -n "$payment_file"; then
ast-grep outline "$payment_file" --items all || true
cat -n "$payment_file"
fi
echo
echo '== Sales list implementation after pagination calculation =='
sed -n '242,350p' src/Cluckwork.Api/Endpoints/Sales/SaleEndpoints.cs
echo
echo '== Body cap metadata definitions and middleware reads =='
rg -n -C 12 --glob '*.cs' \
'class\s+ReadsRequestBodyAttribute|record\s+ReadsRequestBodyAttribute|class\s+MaxRequestBodyBytesMetadata|record\s+MaxRequestBodyBytesMetadata|WithMaxRequestBodyBytes\s*\(|UseCluckworkRequestBodyLimit|MaxRequestBodyBytesMetadata' \
src tests
echo
echo '== Body-reading test behavior =='
body_test="$(fd -t f -a -i 'BodyReadingEndpointTests.cs' tests | head -n 1)"
if test -n "$body_test"; then
cat -n "$body_test"
fi
echo
echo '== Exact current guard rows and local document references =='
sed -n '7,65p' docs/plans/770-mcp-server/02-guards.md
rg -n -C 3 '`#786`|PR `#790`|pending|amended|merged' docs/plans/770-mcp-server
echo
echo '== Verify issue `#786` and PR `#790` state =='
gh issue view 786 --repo mforce/cluckwork --json number,state,title,url,closedAt
gh pr view 790 --repo mforce/cluckwork --json number,state,mergedAt,title,url
echo
echo '== Diff quality =='
base="$(git merge-base HEAD origin/main 2>/dev/null || git merge-base HEAD main)"
git diff --check "$base" HEADLength of output: 2517 🏁 Script executed: #!/bin/bash
set -u
echo '== Payment repository =='
payment_file="src/Cluckwork.Infrastructure/Repositories/PaymentRepository.cs"
ast-grep outline "$payment_file" --items all || true
cat -n "$payment_file"
echo
echo '== Sales order pagination and repository call =='
sed -n '242,350p' src/Cluckwork.Api/Endpoints/Sales/SaleEndpoints.cs
echo
echo '== Definitions and use of body-reading classification and body-size metadata =='
rg -n -C 12 --glob '*.cs' \
'ReadsRequestBodyAttribute|MaxRequestBodyBytesMetadata|WithMaxRequestBodyBytes\s*\(|UseCluckworkRequestBodyLimit' \
src tests || true
echo
echo '== Body-reading test =='
fd -t f -a -i 'BodyReadingEndpointTests.cs' tests -x cat -n {} \; || true
echo
echo '== Guard rows 2 and 19 =='
sed -n '7,52p' docs/plans/770-mcp-server/02-guards.md
echo
echo '== Current `#786` references =='
rg -n -C 3 '`#786`|PR `#790`|pending|amended|merged' docs/plans/770-mcp-server || true
echo
echo '== GitHub state for follow-up =='
gh issue view 786 --repo mforce/cluckwork --json number,state,title,url,closedAt || true
gh pr view 790 --repo mforce/cluckwork --json number,state,mergedAt,title,url || true
echo
echo '== Whitespace check =='
base="$(git merge-base HEAD origin/main 2>/dev/null || git merge-base HEAD main)"
git diff --check "$base" HEAD || trueLength of output: 87100
Update the table entry to say “32 guards” or remove the fixed count. The requested checks pass:
|
CodeRabbit flagged 00-README.md claiming 28 guards when the table had grown past 32. The suggested fix was to say 32 — but the real count is 34 rows (32 numbered, plus 3b and 7b), so that correction would have shipped a second wrong number. Removed the count entirely. AGENTS.md already records a bare count going stale twice on the #271 list and says to re-run the walk rather than trust the sentence; the same reasoning applies to a guard table that is still growing.
|
@coderabbitai Fixed in 968e5b2 — but not the way you suggested, and the difference is the point. You proposed changing "28 guards" to "32". The table actually has 34 rows: 32 numbered, plus Removed the count instead. Thanks for the other five confirmations; each was a claim I would rather have had contradicted than assumed:
Noting your run reported rate limited, so this round completed as comments rather than a formal review. I am not re-triggering for that reason alone — the findings arrived, which is what matters. An independent adversarial reviewer is separately attacking the fixes themselves right now (round 2, briefed to find what the fixes broke rather than re-review the original). If it finds anything I will push it here before asking for merge. |
|
Tip For best results, initiate chat on the files or code changes.
The table has 34 rows: 32 numbered rows plus sub-rows Thanks for identifying the error in the suggested replacement. ✏️ Learnings added
You are interacting with an AI system. |
Same reviewer, re-briefed to attack the round-1 fixes rather than re-review the design. Two major, two minor, all applied. - Row 7b claimed the dependency walk closed the secondary-scope route. It does not: a helper registered as `sp => new Helper(() => sp.CreateScope())` exposes only a delegate, so a constructor walk sees nothing, and a static locator adds no edge. The design now specifies a closed registration model and STOPS claiming the branch is unreachable — it bounds the hazard, names the residue, and points at #787 as the backstop. A fix that reads as complete and is not is worse than the original gap. - Row 31 said "no tool can materialize an unbounded set" but its mutation only removed the reply cap. Take(cap) over an unbounded query keeps every response bounded while materializing the whole balance book. Split into a bounded-reply row and a bounded-query row asserted on rows consumed from the database. - Row 19's mutation was ambiguous: HTTP stores the SHA-256 digest, so keying on the route text leaves the namespaces distinct. Now stated byte-for-byte. - The loopback alternative over-promised. A tool-prefixed Idempotency-Key is not a server-enforced namespace, and the replay path carries no discriminator, so `replayed: true` cannot be reported honestly. Both priced in the alternative rather than discovered during slice 1. Round 1 found three guards that could not go red. Round 2 found two more in the fixes for round 1. Every one had a mutation adjacent to, but not identical with, its invariant.
|
Pushed 650141e — adversarial review round 2, which attacked the round-1 fixes rather than re-reviewing the design. Two major, two minor, all verified and applied. The headline: two more guards that could not go red — both of them in the fixes for round 1. Row 7b claimed a guarantee its own walk could not establish. My round-1 fix said the dependency walk closes the secondary-scope route. It does not: a helper registered as The design now specifies a closed registration model and — the part that matters — stops claiming the branch is unreachable. It bounds the hazard, names the residue, and points at #787 as the backstop that makes the residue fail closed rather than silent. This is why slice 6 of #789 is blocked on #787 rather than merely related to it. A fix that reads as complete and is not is worse than the gap it replaced. Row 31 tested an output bound while claiming a materialization bound. It said "no tool can materialize an unbounded set", but its mutation only removed the reply cap. An implementation can call Row 19's mutation was ambiguous — HTTP stores the SHA-256 digest, so keying on the route text leaves the namespaces distinct and the test green. Now stated byte-for-byte. The loopback alternative over-promised, and the reviewer refuted its own round-1 suggestion — which is what I asked it to do. A tool-prefixed Cleared by round 2: revised guard 2, and new guards 29, 30, 32. The idempotency outcome contract is correct as written. For loopback: forwarding the caller's bearer preserves tenant and actor resolution, the second credential-epoch read correctly rejects a credential revoked between hops, only the inner request claims idempotency, no rate-limit double charge, and The pattern, recorded because it is the transferable part: round 1 found three guards that could not go red; round 2 found two more, in the fixes for round 1. Every one had a mutation adjacent to but not identical with the invariant it claimed. The rule was already written in this repo — run the mutation, do not reason about it — and it was violated by the person writing it into the same document. Twice. I am not triggering another round on my own initiative. Two consecutive rounds have now yielded documentation defects rather than product defects (there is no product here — nothing has been implemented), which is the repo's stop-and-ask point. Handing that decision to the owner. |
Two major, one minor, all applied. Round 3 attacked round 2's fixes and found two claims added while fixing. - Round 2 said #787 makes the secondary-scope residue "safe rather than silent". True for writes, false for reads: a read escape calls FlockRepository.ListAsync, which goes through the EF query filter reading FlockScope directly, and never calls FlockScopeGuard at all. This contradicted the author's own scoping of #787 written the same day on that issue. The residual read risk is now recorded as open. - Three operative statements still said the branch was "unreachable from MCP" while the new honesty note said it is not. An implementer reading any of them would omit protection the safety argument depends on. All three now carry the bounded claim. - Row 31b pointed at SqlCaptureInterceptor and said the mechanism needs no invention. It records (Sql, Parameters) only, so a page-fetch loop that accumulates the whole book emits paged SQL and passes. Now requires counting rows consumed via the DbDataReader. Also drops a factory-registration count: two independent counts disagreed (9 vs 10) depending on lambda parameter naming. Walk ServiceDescriptors, do not grep, do not trust a count in prose.
|
Pushed e20cb0a — adversarial review round 3, attacking round 2's fixes. Two major, one minor, all applied. This is the final round; the loop stops here by decision. The finding worth reading: I contradicted myself across two artifacts on the same day. Round 2's fix claimed #787 makes the secondary-scope residue "safe rather than silent". That is true for writes and false for reads — a read escape calls What makes it sharp: this contradicts my own scoping of #787, written hours earlier on that very issue — " Three operative statements still said "unreachable". Round 2 added an honesty note saying the branch is not unreachable and left three statements elsewhere — Alternatives considered, Open questions, Deliberately not guarded — still asserting it is. An implementer reading any of those would omit protection the new argument depends on. All three now carry the bounded claim; the superseded wording survives in the review history rather than the operative text. Row 31b named a mechanism that cannot measure what it requires. My round-2 fix pointed at Two counts corrected, and one deliberately removed. I added "9 factory registrations" while fixing round 2; the reviewer counted 10. The discrepancy turns on which lambda parameter names a grep matches — so the number is now absent by design, with instructions to walk Cleared by round 3: row 7b is implementable; row 19's byte-for-byte mutation is correct (the registered pattern is Three rounds
Every round found defects in the previous round's fixes, and the yield narrowed without reaching zero — round 3's findings are about claim precision rather than design shape. Stopping is a decision, not an exhaustion. The transferable lesson now has six instances behind it: a guard's stated mutation must be run, not reasoned about — and a fix written confidently is exactly as likely to need that as the thing it fixed. What remains useful is not more review of this document; it is running these mutations for real when the guards are written. Ready to merge once checks settle. |
🤖 I have created a release *beep* *boop* --- ## [0.1.1](v0.1.0...v0.1.1) (2026-09-12) ### Bug fixes * **dashboard:** give recent sales real columns and make both charts readable ([#781](#781)) ([7193ebe](7193ebe)) * **dashboard:** tell a day with no entry from a day that laid no eggs ([#791](#791)) ([48c10e5](48c10e5)) ### Documentation * **agents:** walk into package registrations when re-deriving [#271](#271) ([#790](#790)) ([5fab974](5fab974)) * **mcp:** record the MCP server design and what the spike got wrong ([#785](#785)) ([3fe5a7f](3fe5a7f)), closes [#770](#770) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: cluckwork-lockfix[bot] <309265648+cluckwork-lockfix[bot]@users.noreply.github.com>
Records the design for MCP server support. Design only — no code, nothing shipped.
#770 was a feasibility spike. This is the design that came out of it, produced by running three independent design candidates in parallel (one per model), cross-judging them on a rubric, and synthesizing a base plus grafts. Lands under
docs/plans/rather thandocs/decisions/, because a decision record is the relocated rationale for a rule that also lives compressed inAGENTS.md— this is a design for unshipped work, which is whatdocs/plans/is defined to hold.What the spike got wrong
Four premises did not survive verification against the SDK source at the
v2.2.0tag and against the repo. Each is recorded with evidence in04-verified-findings.md.1.
ScopeRequestsis not the load-bearing setting. UnderSessionMode.Statelessthe SDK sets it itself:So the realistic regression is not someone deleting our
ScopeRequests = falseline — it is someone changingSessionMode, after which tools silently revert to a fresh root scope. UnderStatefulForInitializeClientsthat is per-request mixed. Two of the three candidates built their whole safety argument on the redundant line, and all three proposed a guard on it that would redden on a mutation leaving the product safe.2. MCP does not function at all until
/mcpis exempted fromIdempotencyMiddleware. Not a key collision — a hard 400 on every request includinginitializeandtools/list, because MCP Streamable HTTP sends everything as POST,/mcpis not in the exempt list, an authenticated call resolves a tenant, and no MCP client knows Cluckwork'sIdempotency-Keyheader contract. This is phase-zero and blocking even for a read-only demo, which the issue sized at 1–3 days without accounting for it.3. #271 is touched.
WithHttpTransportregisters, unconditionally and regardless of session mode:The blocker list is not extended —
IdleTrackingBackgroundService.StartAsyncreturns early under Stateless, so the service is registered but inert. ButAGENTS.mdtells you to re-derive that list by "walking everyAddSingleton/AddHostedServiceundersrc/", and that walk is structurally blind to a package registration.AGENTS.mdalready records the list as "twice derived wrongly"; by its own two misses of the same shape mean the METHOD is wrong rule, the walk's scope is the defect. Filed separately.4. .NET 10 compat is confirmed.
ModelContextProtocol.AspNetCore2.2.0 ships a nativelib/net10.0, answering the issue's open decision 5.The design in one paragraph
Stateless HTTP transport on
/mcpinside the existing middleware pipeline, so one tool call is one authenticated request andCredentialEpochMiddlewarekeeps its per-call read (#364). A scopedMcpCallContextis the only way a tool learns who is calling; its factory throws unless anHttpContextis present, the injectedTenantContextis reference-equal to the request's, tenant + actor + flock scope are allIsResolved, andUserId != Guid.Empty. An assembly walk forbids a tool from injectingTenantContext/CurrentUserContext/FlockScope/AppDbContext/IServiceProvider/HttpContextat all — so the next tool someone adds inherits the guarantee, includingRecordWaterUsage, which audits nothing and genuinely reachesFlockScopeGuard's fail-open branch. #307's claim/execute/publish protocol is extracted intoIIdempotentUnitkeyed by an explicit operation key, with the middleware as the first adapter (byte-identical key) and the write tool as the second (mcp:tool:<name>), which dissolves the single-route collision. Eight domain-question tools over thirteen routes: the write takes a flock name, derivesFarmId/HouseId, and money renders at the boundary rather than crossing as minor units.Reviewer notes
02-guards.mdis the part worth reading closely. 28 guards, each with the mutation that makes it red, plus a section on what is deliberately not guarded and why. Three encode traps the candidates fell into: assertingFlockScope.IsUnrestrictedinstead ofIsResolved(defaults totrue, so the guard passes while the hole stays open); asserting zeroIHostedServicedescriptors (red on the correct configuration); and asserting the daily-entry row count on replay (the handler upserts, so it passes with idempotency removed — assert the absence of the second audit row).IIdempotentUnitextraction is the largest risk in the whole issue, not the tool code.AtomicIdempotencyProtocolTestsand the other Make API write idempotency atomic across replicas #307 suites must pass unedited; an edit to one is a stop-and-review.PerSessionExecutionContextis[Obsolete]with aDiagnosticIdat 2.2.0 andDirectory.Build.propssetsTreatWarningsAsErrors=true. It is only read in the stateful branch, so the fix is deletion.BodyReadingEndpointTestsstarts applying the moment/mcpis mapped —MapMcpmapsHandlePostRequestAsync(HttpContext)with noIAcceptsMetadata.RequestType, so it needsReadsRequestBodyAttribute.Spin-offs filed
Three findings outlive #770 and are filed separately rather than bundled here:
AGENTS.md; no current blocker is missing.FlockScopeGuard's fail-open branch. Its own comment has been requesting this since fix(seed): seeded audit events carry "(unresolved)" as the actor, now visible on record History columns #500. This design makes it unreachable from MCP rather than fixing it, deliberately.Closes #770