fix(scope): legacy and internal cache entries refused for every caller, kind-first authorization, monotone child provenance (Spec 105 PR B, FR-001/002) - #1282
Merged
Conversation
Deploying mcpproxy-docs with
|
| Latest commit: |
65bc97d
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://6d32642b.mcpproxy-docs.pages.dev |
| Branch Preview URL: | https://105-b-cache.mcpproxy-docs.pages.dev |
|
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
Contributor
📦 Build ArtifactsWorkflow Run: View Run Available Artifacts
How to DownloadOption 1: GitHub Web UI (easiest)
Option 2: GitHub CLI gh run download 35117731746 --repo smart-mcp-proxy/mcpproxy-go
|
Dumbris
added a commit
that referenced
this pull request
Sep 16, 2026
A controller that reports success without a server config (the test doubles do) was dereferenced in handleAddFromRegistry; chi's recoverer turned the fault into a bare 500. Under Go 1.26 on windows/amd64 the recovered hardware exception corrupts the Go heap (golang/go#81238), so the internal/httpapi test binary died later with "found pointer to free object" / "unexpected fault address" on unrelated PRs (#1282, #1285). Return a JSON 500 instead, the way redactedRegistrySummary already tolerates a nil entry, and pin it with a test. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> (cherry picked from commit b9312b4)
Dumbris
added a commit
that referenced
this pull request
Sep 16, 2026
A controller that reports success without a server config (the test doubles do) was dereferenced in handleAddFromRegistry; chi's recoverer turned the fault into a bare 500. Under Go 1.26 on windows/amd64 the recovered hardware exception corrupts the Go heap (golang/go#81238), so the internal/httpapi test binary died later with "found pointer to free object" / "unexpected fault address" on unrelated PRs (#1282, #1285). Return a JSON 500 instead, the way redactedRegistrySummary already tolerates a nil entry, and pin it with a test. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> (cherry picked from commit b9312b4)
Dumbris
added a commit
that referenced
this pull request
Sep 16, 2026
A controller that reports success without a server config (the test doubles do) was dereferenced in handleAddFromRegistry; chi's recoverer turned the fault into a bare 500. Under Go 1.26 on windows/amd64 the recovered hardware exception corrupts the Go heap (golang/go#81238), so the internal/httpapi test binary died later with "found pointer to free object" / "unexpected fault address" on unrelated PRs (#1282, #1285). Return a JSON 500 instead, the way redactedRegistrySummary already tolerates a nil entry, and pin it with a test. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> (cherry picked from commit b9312b4)
Dumbris
added a commit
that referenced
this pull request
Sep 16, 2026
A controller that reports success without a server config (the test doubles do) was dereferenced in handleAddFromRegistry; chi's recoverer turned the fault into a bare 500. Under Go 1.26 on windows/amd64 the recovered hardware exception corrupts the Go heap (golang/go#81238), so the internal/httpapi test binary died later with "found pointer to free object" / "unexpected fault address" on unrelated PRs (#1282, #1285). Return a JSON 500 instead, the way redactedRegistrySummary already tolerates a nil entry, and pin it with a test. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> (cherry picked from commit b9312b4)
Dumbris
added a commit
that referenced
this pull request
Sep 16, 2026
…emp-dir race (#1288) * fix(httpapi): stop the recovered nil-config panic in add-from-registry A controller that reports success without a server config (the test doubles do) was dereferenced in handleAddFromRegistry; chi's recoverer turned the fault into a bare 500. Under Go 1.26 on windows/amd64 the recovered hardware exception corrupts the Go heap (golang/go#81238), so the internal/httpapi test binary died later with "found pointer to free object" / "unexpected fault address" on unrelated PRs (#1282, #1285). Return a JSON 500 instead, the way redactedRegistrySummary already tolerates a nil entry, and pin it with a test. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(scanner): wait for the Pass-2 job before tearing down temp dirs TestServiceStartScanDeepOnRunsSourceResolutionAndPass2 failed on ubuntu CI with "TempDir RemoveAll cleanup: directory not empty": the Pass-2 goroutine writes tools.json into the server working dir after ResolveFullSource returns and before it registers its engine job, so an idle engine can mean "Pass 1 cleared, Pass 2 not started yet" and the test's teardown raced the write. Wait for the Pass-2 job to reach a terminal status in storage (its last write on every exit path) first. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
… kind-first gate, monotone child provenance, non-disclosing read_cache (T021–T027) Failing tests for FR-001/FR-002 gaps FR001-G1..G7, all asserting against HEAD (bac8f6b) behaviour: - G1 internal/cache/manager_legacy_test.go (new): nil-producer / version-0 / unknown-version / stamped-but-unversioned records refused for every caller kind (admin, admin_user, anonymous, agent, user), evicted on first refused redemption, absent after bbolt close+reopen, stats consistent with the bucket; per-key invalidation. Handler-level admin/anonymous/user refusal in internal/server/mcp_read_cache_scope_test.go (new). - G2 internal/cache/manager_internal_test.go (new): "internal"-stamped registry/npm entries refused for every read_cache caller WITHOUT eviction (Peek/Get still serve). Writer stamps pinned in internal/runtime/registry_cache_stamp_test.go (real SearchRegistryServers against a loopback registry) and internal/experiments/guesser_cache_stamp_test.go (cacheInfo). Handler-level admin refusal + agent/user live≡absent parity + no eviction. - G3 TestExpiredRecords extended: expired entry absent after the expiring Get, in-memory and persisted stats agree with the bucket (rollback bug). - G4 recursive child carries the PARENT producer (MCP + REST): producing agent reads the child an administrator minted. - G5 agent refusal collapses unauthorized/not-found/expired into one body ("cache key not found") on handleReadCache and CallToolDirect. - G6 TestAuthorization_CallerKindFirst (D5): profile-bound / empty-profile / deleted-profile administrators redeem any snapshot; agents never redeem an administrator snapshot; deny-all guard applies to agent readers only. - G7 internal/server/scope_cache_fixtures_test.go (new): upgrade fixture with a real restart on the same data dir (SC-004), fresh internal entry, recursive child on REST, spec's named profiled-admin → pinned-agent child fixture, pinned-token REST dispatch-vs-redemption body parity, and the held-call narrowing fixture (regression pin — passes on HEAD). Not touched (T031, implementation phase): the pre-D5 cells in TestAuthorization_CouldHaveProduced, TestGetRecordsAs_LegacyEntryWithoutProducer, and the "not readable with this credential" assertions in mcp_read_cache_authz_test.go / mcp_call_tool_direct_test.go still pin the pre-feature behaviour and must be inverted alongside the implementation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…t gate, monotone child provenance, non-disclosing read_cache (T028–T032) FR-001/FR-002 implementation turning the T021–T027 red tests green: - internal/cache: Record.Version (RecordVersion=1, stamped by every Store); HasCurrentProvenance; CallerKindInternal; ErrKeyNotFound/ErrKeyExpired/ ErrLegacyProvenance/ErrInternalEntry (the last two errors.Is ErrUnauthorizedRead). getGuarded commits every path that deletes or records a stat (miss, expiry, legacy invalidation) by returning nil from the bbolt Update closure and surfacing the verdict outside, so the delete is durable and m.stats agrees with the bucket (G1, G3). On the gated door an entry with no producer, no version or an unrecognised version is refused for EVERY caller kind and evicted; an internal entry is refused without eviction (G2). ReadCacheResponse.Producer (json:"-") carries the paged entry's snapshot. - CouldHaveProduced is ordered caller kind first (D5, G6): an administrator reader qualifies for any snapshot regardless of its own profile binding; a non-administrator never for an administrator snapshot; the deny-all guard, profile-set, pin, server-set and permission checks apply between agent snapshots only; internal producers match nobody. Unrestricted() renamed IsAdministrator(). - Writers: runtime registry search and the repository guesser stamp CallerKindInternal (G2, T029). - handleReadCache: a re-truncated page is stamped with the PARENT entry's snapshot (childPageProducer), never the redeemer's (G4); readCacheRefusal collapses unauthorized/expired/internal/legacy into the not-found body for non-administrator callers (G5) while the activity record keeps the real reason and administrators get the reason; storage faults stay distinct. CallToolDirect inherits both through the shared handler. - T031 inversions (never deleted): the four pre-D5 admin cells in TestAuthorization_CouldHaveProduced, TestGetRecordsAs_LegacyEntryWithoutProducer (every caller refused + invalidated), the "not readable with this credential" assertions in mcp_read_cache_authz_test.go (now body parity with the nonexistent-key answer plus an administrator liveness control), and TestCallToolDirect_ReadCachePagesStoredRecords (seeds a stamped record). - Docs: agent-tokens.md and routing-modes.md describe kind-first ordering, monotone child provenance, the non-disclosing body, legacy and internal entries. tasks.md T021–T032 ticked; ROADMAP.md regenerated. Verified: go test -race ./internal/cache/... ./internal/runtime/... ./internal/experiments/... ./internal/httpapi/...; full ./internal/server with the CI skip regex; -tags server builds and serveredition suites; goldens untouched; gofmt clean on touched files. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ng class), unknown kinds are legacy, stats never drift, expired internal entries kept Critique 1 (security): - (1) MUST-FIX: a scoped read_cache refusal returned its error from the bbolt Update closure, so the transaction rolled back with no disk write (~5 us) while a miss committed a stats write (~13 ms): a ~3000x timing oracle that told a narrow token whether a live entry (a broader token's page, or a guessable registry/guesser key) sat behind the key. Every refusal now takes the committing branch as a miss; verdicts leave the closure through `verdict`, and the closure errors only on storage faults. Pinned structurally via bbolt TxStats.Write parity with a miss. - (2) "unrecognised provenance" was decided on Version only; an entry stamped with a caller kind this binary does not know was served to admin and anonymous readers. HasCurrentProvenance now requires a known kind. - (3) stats were decremented before saveStats ran, so a failed write left the in-memory counters disagreeing with the rolled-back bucket. Manager.update snapshots and restores the stats on a failed closure; saveStats errors instead of nil-dereferencing a missing stats bucket. - (6, with critique 2 #6) an undecodable record (e.g. a version that overflows uint8 after a downgrade) is unrecognised provenance on the gated door: refused for every caller with the legacy sentinel and invalidated, as cleanup already treats it. The ungated Get is unchanged. - (4) documented: pre-upgrade registry/guesser entries carry no stamp and are invalidated once by the first probe; (5) anonymous body is spec-conformant (spec.md:37) and is now pinned as the SC-005 parity control. Critique 2 (parity/tests): - (4) the gated door evicted an EXPIRED internal entry (expiry ran before the internal check) although the registry reader serves it as Stale until cleanup; provenance class is now decided before expiry. - (1) the FR-001 named fixture pins parent-stamping (ProfileScoped/Profile), (2) administrator reason bodies and the anonymous parity body are asserted (mutation M2 now bites), plus ErrLegacyProvenance/ErrInternalEntry sentinels at the cache level, (3) the upgrade fixture re-seeds before every leg, (5) REST legs assert the not-found substring, (7) appctx CacheManagerAdapter.Set stamps internal instead of writing a legacy entry, (8) handler comment reworded for kind-first, (9)+docs: committed expiry eviction, one-time invalidation of entries from any earlier release. Verified: -race on internal/cache, runtime, experiments, httpapi, appctx, internal/server (with the standard skip list) and the -tags server suites; goldens byte-identical. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…che redemption Codex round 1 (both chunks, one defect): an agent reader whose token carries no AllowedServers is deny-all on every dispatch gate (auth.CanAccessServer, serverInScope), but cache.CouldHaveProduced let coversServers([], []) succeed, so an empty-grant agent could redeem an agent-produced entry whose snapshot also carried an empty grant — on the MCP read_cache tool and on the /api/v1/tools/call read_cache branch alike. An empty grant is reachable: token creation normalises it to ["*"], but a server-edition rotation that narrows the grant persists nil. The agent branch of the gated predicate now opens with the empty-grant deny-all guard, alongside the existing empty-profile one: such a reader could have produced no entry, its own identically-stamped one included. Refusal body and timing class are the not-found ones already in place. Tests: nine new cells in TestAuthorization_CallerKindFirst (nil and [] grants, own entry, another empty-grant agent's, unscoped agent, admin; broad agent and administrators still read the empty-grant entry), and fixture (f) TestScopeCacheFixture_EmptyGrantAgentIsDenyAllOnRedemption: CanAccessServer + REST direct dispatch refused as premise, administrator control reads the record, MCP and REST live key ≡ absent key with the not-found body, no eviction. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…failure stats, header-only refusals Codex round 2 on Spec 105 PR B (FR-001/FR-002), three cache findings and one server finding, all confirmed by probe before fixing: 1. A current-version agent PRODUCER snapshot with an empty server grant, or bounded to an empty effective profile, could have authorized no tool, yet containment against the empty set let any broader agent redeem it. CouldHaveProduced now rejects such producer snapshots for agent readers before containment; administrators still qualify for any snapshot. 2. Manager.update restored the in-memory stats only when the closure failed. A commit failure after a successful closure (disk full at fsync) left the record on disk with the counters already decremented. The snapshot is now taken before db.Update and restored on any error, under a mutex so it is taken under the same exclusion the transaction runs under; a dbUpdate seam lets a test fail the commit after the closure. 3. Refusals were payload-proportional: every existing key was fully JSON-decoded (multi-MB FullContent) before provenance, expiry or the guard ran, while a miss was not — a timing class the spec's non-disclosing refusal forbids. MarshalBinary now writes a small frame header (version, producer, expiry, size) in front of the record; the gated door decides every verdict short of admission on that header alone and decodes the record only after admission. UnmarshalBinary accepts framed and pre-frame bare-JSON values. Expired entries are no longer evicted by the gated read — refused like a miss and left to the cleanup sweep — so only the FR-002 invalidating refusals delete, and that delete is pinned as payload-independent. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ader bound, pre-frame size accounting Three confirmed findings on the framed cache record (Spec 105 FR-001/FR-002): - UnmarshalBinary now requires the frame header to agree exactly with the body it fronts (Version, ExpiresAt, TotalSize, deep Producer). The gated read admits on the header alone, so a crafted a-only header in front of an administrator/broader/legacy body was served to the a-only reader, and a future-expiry header over an expired body was decoded and evicted after admission. A disagreeing value is undecodable: refused for every caller, invalidated (folding out the header's size), never served. - maxRecordHeaderLen raised 64 KiB -> 1 MiB, sized so an authorization naming several thousand servers in both grant and profile fits, and MarshalBinary refuses a header over the bound BEFORE persisting. Previously a legitimate large snapshot was stored, then refused and deleted on its producer's own first redemption; now StoreAs returns errRecordHeaderOversize and the truncator serves the payload uncached. - Invalidating a pre-frame bare-JSON record folded 0 into TotalSizeBytes; it now folds out the value's length (known without decoding, an upper bound on the content) and evict clamps the total at zero. Red tests in manager_frame_integrity_test.go; verdict table appended to .review-tmp/critique-r1.md under "Codex round 3". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…snapshots, exact pre-frame accounting Codex round 4 (specs/105, PR B), three confirmed findings: 1. User-kind snapshots kept only the user id and redemption compared only the principal, so a server-edition user narrowed since producing an entry (grant, permission tier or profile) still redeemed it. Users are bounded by the same dispatch gates as agent tokens, so the snapshot now carries AllowedServers/Permissions/ProfilePin and the read gate applies the same containment on top of identity equality (necessary, never sufficient). Shared rule: IsScoped/DenyAll/kindVerdict/containedBy. 2. The 1 MiB frame-header bound refused legitimate snapshots (configuration bounds neither server count nor name length), and a live-key refusal JSON-decoded the whole header on every probe — a fleet-sized timing oracle a miss did not share. The frame header is now FIXED-SIZE (52 bytes: version, kind code, deny-all bit, expiry, size, snapshot SHA-256); each distinct producer snapshot is stored once in a content-addressed `cache_snapshots` bucket, written in the same transaction as the record and pruned by cleanup; the gate decides kind, version, expiry and deny-all on the header and loads the snapshot — through a small LRU, O(1) after the first probe — only for same-kind containment. No size bound remains: a 5,000-server snapshot round-trips and is redeemable by its producer, warm and after a restart. 3. Pre-frame invalidation folded out the raw record length (escaped JSON) and clamped the aggregate at zero, understating unrelated live entries. The legacy path — one-shot, every caller — now decodes the bare-JSON value and folds out exactly len(FullContent); the clamp is gone. Every finding was reproduced by a probe and pinned by a red test proven against pre-fix HEAD. Docs: agent-tokens.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…al-cache fixture The loopback-registry fixture sets AllowPrivateRegistryFetch through the runtime config, which flips the process-wide allow-policy; under the shuffle lane TestBuildRegistrySourceEntry_RejectsSSRFLiteralIP then ran after it and accepted a literal IP. Same LIFO cleanup as #1222. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…), no legacy decode Codex round 5 (specs/105, PR B), one root behind both findings: any refusal that must LOAD the producer snapshot (round 4's content-addressed snapshot was O(1) only while its 128-entry LRU was warm — a restart or 129 distinct snapshots made the first same-kind refusal read and JSON- decode a fleet-sized snapshot) or DECODE the legacy value (round 4's one-shot preFramePayloadSize) does work proportional to hidden state, which the spec's non-disclosing refusal forbids in the timing class. Measured before the fix: cold scope refusal against a 5,000-server producer 1,657,864 B vs a 58,104 B miss; legacy 4 MB refusal 8,500,992 B. Structural answer, recorded as research.md D16: the gated door decides in O(1) from the fixed 52-byte header alone — it admits (a) an administrator reader (FR-001 kind first), (b) an unrestricted agent (wildcard grant, no pin, no profile) whose tier set covers the producer's tier bits, now carried in the header's formerly reserved byte, or (c) a reader whose effective-authorization DIGEST (SHA-256 of the canonical snapshot: kind, principal, sorted AllowedServers, sorted Permissions, pin, ProfileScoped, sorted ProfileServers — the profile name excluded) equals the producer digest in the header; every other reader is refused without loading anything. Reader facts are computed once before the transaction so a miss and a refusal do the same work; CouldHaveProduced is the same `admits` over the same facts. A strictly wider but bounded reader is now refused: FR-001 obliges refusing non-supersets, not admitting supersets, so the cells that pinned its admission are inverted with names stating the new contract (three agent cells, three user cells, one server-level user cell). The snapshots bucket stays, keyed by the digest, for administrator diagnostics only; the LRU, loadSnapshot and producerView are gone. Frame magic \x02 -> \x03 (never in a release). Legacy (pre-frame / corrupt-frame) entries are refused and deleted WITHOUT decoding: the entry count is folded out exactly, the size is left to the existing cleanup sweep, which now recomputes TotalEntries and TotalSizeBytes from the bucket it already walks, so the statistics are eventually consistent while the refusal stays O(1). Tests (red first): the allocation matrix probes every refusal through a reopened manager and gains the cold 5,000-server producer and the pre-frame 4 MB legacy cells (after: wide = small = miss = 25,112 B); digest-equal, reordered-list, renamed-profile and unrestricted readers admitted cold; strictly-wider readers refused; the snapshots bucket proven diagnostics-only; the sweep restores exact stats after a legacy invalidation and reconciles arbitrary drift. Docs: agent-tokens.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ed provenance; admitted readers never get the refusal shape Codex round 6, cache finding 1: decodeHeaderBytes accepted flag bits outside recordFlagDenyAll and tier bits outside the emitted mask. With the digest preserved, an unknown tier bit was admitted on digest equality and caught only by the header/body agreement check after a payload-sized decode — the timing class round 5 removed — and an unknown flag bit was invisible to both the decoder and agreesWith, so the record was served. The decoder now rejects any reserved bit as errRecordFrameCorrupt, so the gate classifies such a frame as unrecognised provenance on the existing O(1) refuse-and-invalidate path before admission. permBitKnown names the emitted union. Server finding 1: an admitted (digest-equal / wildcard / admin) reader whose body proved undecodable or header-disagreeing received the not-found refusal shape after the body decode. By contract the non-disclosing definition covers callers the gate refuses, and this reader was admitted on the header — but so that every response wearing the refusal shape is one the header decided, the branch now yields the admitted-class ErrEntryUnreadable (not an ErrUnauthorizedRead, not a miss), still invalidated, rendered for every caller kind as "cache entry is unreadable … and has been invalidated". contracts/refusals.md states the rule. Tests: TestGetRecordsAs_UnknownHeaderBitsAreUnrecognisedProvenance (a 4 MB entry with one header byte flipped, probed cold by its producer and an administrator: legacy verdict, invalidated, within the miss allocation budget; exhaustive decoder sweep), the scoped-reader malformed-body case beside the administrator one in manager_legacy_test.go, the admitted-body fixtures retargeted to ErrEntryUnreadable, and TestReadCache_AdmittedReaderOfUnreadableEntryGetsDistinctBody at the handler. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Dumbris
force-pushed
the
105-b-cache
branch
from
September 16, 2026 15:47
7d3fa72 to
65bc97d
Compare
Dumbris
marked this pull request as ready for review
September 16, 2026 15:50
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Spec 105 (agent-token scope hardening), planning artifacts and PR A merged in #1279. This PR closes FR001-G1..G7 · roadmap
scope-cache-legacy-invalidation· research D2, D5 and the new D16 (header-only read gate). Gap ids, evidence and test sketches:specs/105-agent-scope-hardening/gap-map.md§1; the refusal shapes it must satisfy:contracts/refusals.md.What changed
magic | fixed header (version, caller kind, flags, tier bits, expiry, size, producer authorization digest) | JSON body. Pre-frame values are legacy provenance: refused for every caller and invalidated on first redemption, without decoding (FR-002).ErrEntryUnreadable(entry invalidated) — the refusal shape is decided on the header only (rule recorded incontracts/refusals.md).TotalEntries/TotalSizeBytes) are recomputed exactly by the cleanup sweep, so legacy invalidation never has to size the payload.CallerKindInternalentries are refused for every caller and kept.Tests
Red-first per gap id (each confirmed failing on
origin/mainby assertion), pinned pre-105 tests inverted (never deleted), admin controls kept. Notable:manager_payload_independent_test.go(allocation/bytes-read constant across miss / scope-refused 4 MB entry with a 5k-server producer / cold cache / legacy 4 MB),manager_refusal_commit_test.go,manager_frame_integrity_test.go(exhaustive flag/tier byte sweep),manager_legacy_test.go(admin and scoped-reader malformed bodies),mcp_read_cache_scope_test.go.Verification
go buildboth editions ·go test -raceon the touched packages,internal/server(CI skip regex),-tags serverserveredition ·-count=3 -shuffle=onon the PR packages · goldens byte-identical to main · gofmt/vet incl.GOOS=linux/GOOS=windows·./scripts/test-api-e2e.sh65/65.golangci-lintv2 could not run locally (binary built with go1.25 vs the go 1.26 target) — CI covers it.gpt-5.6-sol, rounds 5–7 opencodegithub-copilot/gpt-5.6-sol; every finding verified by probe before fixing (verdict tables in the worktree's.review-tmp/critique-r1.md). The recurring theme was the timing class of refusals (work proportional to hidden snapshot/payload size), answered structurally by D16.Follow-ups / Spec 105 gaps
See
gap-map.md§8 (REST replay has no tool gate; UI active profile bounds no MCP session). Remaining Spec 105 PRs: D (#1283), E (#1284), H0 (#1285) in this batch, then C → F → G → H1.🤖 Generated with Claude Code