feat(plugins/db/postgres): PostgreSQL 16 provider#2
Merged
Conversation
…es-flake
Mirror of the bough-plugin-mysql shape, swapped for postgresql_16:
- Up: nix run --impure '.#postgres' -- up --tui=false, detached via
Setsid so the WorktreeCreate hook returns before postgres is ready
- ReadyCheck: pg_isready on PATH preferred, raw TCP DialTimeout
fallback so the host does not require psql installed
- Down: graceful nix run … down then lsof + SIGTERM/SIGKILL fallback
+ stray process-compose supervisor cleanup
- Cleanup: rm -rf <datadir>
- PortRange default 50000-52999 (out of mysql 42000-44999, prior
bash-hook 33000-41999)
- EnvVars: BOUGH_POSTGRES_HOST / _PORT / _SOCKET_DIR
The embedded flake.nix wraps services-flake's services.postgres
module, takes BOUGH_POSTGRES_PORT / _SOCKET_DIR / _DATADIR via
builtins.getEnv, and pins postgresql_16 against silent major bumps
from a nix flake update. socketDir set so the operator can dodge
macOS's 104-char sun_path limit.
cmd/bough-plugin-postgres stays four lines of plugin.Serve. The
real lifecycle lives in plugins/db/postgres.
.goreleaser.yaml gains a third build target + the archive id list
is extended so the v0.1.0+ tarballs ship bough + bough-plugin-mysql
+ bough-plugin-postgres in one archive per (os, arch).
Tests cover PortRangeDefault (default + override), EnvVars including
socket-dir default, deployFlake asset extraction (every fragment
the host integration relies on), Cleanup happy + empty-datadir
guard.
4 tasks
ikeikeikeike
added a commit
that referenced
this pull request
Jun 21, 2026
…em0 namespace Round 4 review #23 surfaced six findings that block v0.6.0 ship. This commit clears all six in-place; remaining medium / low items land in follow-up commits. #1 internal/cli/capability.go: runCompile passed `currentScope(nil, ...)`, but currentScope dereferences cfg.Repositories on the default path → nil pointer panic on every `bough capability compile/preview`. Use an explicit Scope{Level: worktree, WorktreeID: "default"} literal until --scope flags land in v0.6.x. #2 + #3 cmd/bough-mcp-server/main.go + internal/mcp/server.go: WatchStdin and Server.Run both Read from os.Stdin concurrently, so the watchdog stole bytes the JSON-RPC scanner needed. The ZOMBIE guard also only triggered on io.EOF, so wrapped errors left the goroutine spinning. Both go away by deleting WatchStdin entirely and letting Server.Run's bufio.Scanner detect EOF — main.go's `defer server.Shutdown()` handles the subprocess teardown. #4 + #5 internal/pluginsign/verifier.go + internal/cli/plugin_verify.go: verifyCosign defaulted SigPath to ".bundle" but .goreleaser.yaml writes ".sig", and the command was missing the --certificate-identity / --certificate-oidc-issuer flags cosign 2.x requires for keyless verify. Default SigPath now prefers .sig (with .bundle fallback for legacy artifacts), CertPath defaults to the GoReleaser .pem companion, and the Request grows CertIdentity / CertIdentityRegexp / CertOIDCIssuer fields the CLI exposes as --cert-identity / --cert-identity-regexp / --cert-issuer. PreRunE now validates --pubkey is set for minisign and --cert-identity (or regexp) is set for cosign BEFORE shelling out so the operator sees an Args error rather than a phantom verify failure. #6 internal/mcp/server.go: handleResourcesRead built Scope{Level: level} with no RepoName/WorktreeID, then asked the backend for a count. sqlite's scopeID returns "/" for that shape, so the filter never matched a real worktree row and the resource reported `worktree: 0` even when worktree rows existed. The honest fix is to stop returning a faithful count the host cannot guarantee: readScopesResource now lists the three scope tiers with descriptions and points clients at memory.query for counts (v0.6.x will add an explicit count API once Server learns about cfg.Repositories). #7 (review #23) plugins/memory/mem0/namespace.go: scopeToMem0 worktree case derived rootHash from `RepoName + "/" + WorktreeID`, so two worktrees of the same repo got different user_ids and the documented user-tier sharing did not hold. Use hashShort(RepoName) for both segments; branch identity continues to live entirely in session_id. Build clean, all unit + conformance tests pass, lint 0 issues.
ikeikeikeike
added a commit
that referenced
this pull request
Jun 21, 2026
…view #23 #2/#3) The existing conformance/mcp suite wires Server.Run through io.Pipe, which CI-greened the pre-fix WatchStdin design where a second goroutine on os.Stdin would have raced the bufio.Scanner inside Run. Review #23 #2/#3 surfaced that gap; the WatchStdin function itself is gone, but the in-process suite still cannot regress-test the production stdio path. This test spawns the real bough-mcp-server binary (and its bough-plugin-memory-sqlite dependency, built into a shared temp dir so the server's discoverSQLite finds it via PATH) and drives the MCP initialize handshake through actual stdin/stdout pipes: 1. Build both binaries into t.TempDir(). 2. Start cmd/bough-mcp-server with PATH=tempdir + sqlite db path. 3. Write one JSON-RPC initialize frame; read exactly one response. A WatchStdin-style second reader would steal the bytes and ReadBytes would time out. 4. Close stdin → Server.Run's bufio.Scanner returns false → main returns → defer server.Shutdown() reaps the SQLite plugin. If the SQLite subprocess outlives the server, cmd.Wait blocks past 10s and the test fires. The build cost (~10s for two binaries) is paid once per test run. The test runs without tags so the CI memory job already covers it; operators running `go test ./...` locally get coverage without configuring anything extra. Verified locally: go test -run TestSubprocessLifecycle ./conformance/mcp/... PASS: TestSubprocessLifecycle (1.65s)
ikeikeikeike
added a commit
that referenced
this pull request
Jun 21, 2026
…nd 4 priority A12) Round 4 external review (AI #2) proposed widening the protocol-level feature-negotiation surface so the host coordinator can route Query / Store / fallback decisions per backend without hard-coding plugin identity. v0.5 shipped 5 capability flags; v0.6 adds 11 more — TemporalQuery / MetadataFilter / NamespaceIsolation / SoftDelete / BulkImport / DedupeKey / SourceEventID / TTL / EventualConsistency plus int32 MaxBatchSize and MaxQueryTokens limits. The expansion is additive — v0.5 plugin binaries continue to advertise only the original 5 flags (proto field numbers 1-6); the new fields default to the proto zero values, which the host treats as "feature not supported". No wire break. SQLite reference-fallback now advertises its honest values: SoftDelete + BulkImport + DedupeKey + SourceEventID = true, since v0.5.1 made each of those real (Forget flips state, Import restores rows, Store consults DedupeKey + SourceEventID for idempotency). MaxQueryTokens = 4000 mirrors the host validator default. mem0 / Graphiti will light up the SemanticQuery / VectorSearch / NamespaceIsolation / TTL / EventualConsistency cluster when their plugins land in Ν-1.1 / Ν-1.3. Touched: memory.proto + types.go + server.go + client.go + sqlite.go. Regenerated memory.pb.go + memory_grpc.pb.go via `make proto`.
ikeikeikeike
added a commit
that referenced
this pull request
Jun 21, 2026
…s + 4-line plugin.Serve)
v0.6 introduces the official mem0 adapter. This commit lays down
the skeleton:
- plugins/memory/mem0/mem0.go: Provider struct, Capabilities
(mem0's correct advertise — SemanticQuery / VectorSearch /
NamespaceIsolation / TTL / EventualConsistency / MetadataFilter
/ BulkExport / BulkImport all true; DedupeKey / SourceEventID
false because the host computes idempotency tokens), Health
(static), and stubbed Store / Query / Forget / Export / Import
that return errNotYetImplemented until Ν-1.1c (namespace
mapping) and Ν-1.1d (HTTP client adapter) wire the real REST
calls.
- plugins/memory/mem0/CONTRACT.md: pins the contract that makes
the plugin swappable.
- Scope translation: repo → user_id, worktree → session_id
(round 4 AI #2).
- Fallback policy: Query Read fallback only, Store fail-fast
(round 4 AI #1 + #2 split-brain Blocker 1 mitigation).
- Cache: 30 s TTL, LRU 512, Query-only, invalidated on Store /
Forget / Import (round 4 AI #1 + #2).
- Capabilities matrix mapping each advertised flag to its
rationale.
- cmd/bough-plugin-memory-mem0/main.go: the four-line plugin.Serve
entry the host spawns when .bough.yaml memory_backends declares
kind: mem0. Reads endpoint / api_key / namespace / timeout from
the BOUGH_MEMORY_MEM0_* env block.
Build clean and golangci-lint 0 issues across the new package and
cmd entry.
ikeikeikeike
added a commit
that referenced
this pull request
Jun 21, 2026
…ession_id) Round 4 AI #2's mem0-layered namespace lands here. bough's three- tier Scope translates into mem0's namespace pair: global → user_id = global/<user@host> repo → user_id = repo/<repo_hash>/<root_hash> worktree → user_id = repo/<repo_hash>/<root_hash> session_id = worktree/<worktree_id> The repo / worktree split puts the long-lived identity on user_id and the short-lived per-branch identity on session_id, matching mem0's memory-types layering (user / session / agent / run). Two worktrees of the same repo share the user namespace but stay queryable per branch through session_id. v0.6 limitation: the wire Scope carries only RepoName + WorktreeID, so both hashes derive from RepoName (and WorktreeID for the worktree root hash). v0.6.x will extend memapi.Scope with optional Remote Fingerprint + RootFingerprint so the hashes match docs/NAMESPACE_ MAPPING.md exactly (sha256(remote URL) + sha256(absolute path)). The current derivation still cleanly isolates two repos with different names. BOUGH_MEMORY_MEM0_NAMESPACE multi-tenant prefix is honored for every scope level. namespace_test.go covers global / repo / worktree user_id shape, shared repo prefix across repo + worktree of the same RepoName, disjoint user_ids for two different repos, multi-tenant prefix application, machineID stability, and hashShort idempotency. 8 tests pass; lint 0 issues.
ikeikeikeike
added a commit
that referenced
this pull request
Jun 21, 2026
…Export / Import + helpers) Round 4 AI #1 + #2 split-brain Blocker 1 lands as policy in this commit. The mem0 plugin's six stub methods now route through the real mem0 v1 REST API while keeping the host's fallback decision read-only: - doJSON (http.go) is the single HTTP entry every per-RPC method uses. No transparent retry — round 4 AI #1 flagged retry on writes as risky without native idempotency. The host computes DedupeKey + SourceEventID, but mem0 does not honor them, so the plugin advertises false for both and Store fails fast. - Store packs every Instinct field into mem0 metadata so an Export -> Import cycle round-trips even though mem0's primary surface is just a memory string. The mem0 event tag (ADD / UPDATE / NONE) drives the WasUpsert response. - Query applies MinConfidence and MaxResults host-side so the budget aggregator never sees rows below the cap. - Forget url.PathEscapes the memory id so user-supplied ids do not break the path. - Export emits the same YAML / JSONL shape as the sqlite reference-fallback so Import is backend-agnostic. - Import reuses an inlined copy of sqlite's parser; v0.6.x will lift the parsers into pkg/memorywire/ once a second plugin needs them. - Health stays static — the host's first real RPC after Health surfaces a misconfigured endpoint, so we don't duplicate the probe. http_test.go (httptest.Server, 9 cases) covers the wire shape: authentication headers, content-type, error decoding with upstream body, Store metadata round-trip, mem0 event semantics, Query min- confidence filter, Forget url-escape, and an end-to-end Export -> Import YAML round trip. Full conformance e2e arrives in Ν-1.2. 17 tests pass (8 namespace + 9 http); lint 0 issues.
ikeikeikeike
added a commit
that referenced
this pull request
Jun 21, 2026
…U 512 + scope invalidate) Round 4 AI #1 + #2 both flagged a cache as essential for absorbing duplicate Query traffic across one bough invocation, with two guard rails: it must be Query-only (Store / Forget / Import never read or write entries) and Store / Forget / Import must invalidate every cached entry for the touched scope so a follow-up Query never returns a row mem0 no longer holds. cache.go implements both rules: - queryCache holds entries keyed by (scope, term, max_results, max_tokens, min_confidence) so distinct queries never alias. - TTL is 30 s — short enough that mem0 updates from another bough process land within one query round, long enough to amortise duplicate calls inside a single CLI invocation. - LRU caps at 512 entries; oldest evicts when full. - clock is injectable so TTL behaviour can be unit-tested. - invalidateScope drops every key targeting the given Scope. mem0.go hooks the cache without breaking the existing tests: - Provider gains a cache field; New() initialises it. - Query consults cache.get first, falls through to mem0 search on miss, cache.put on the way out. - Store and Forget call cache.invalidateScope after a successful upstream call. Import re-uses Store so invalidation propagates automatically. cache_test.go (6 cases) covers get/put hit+miss, TTL expiry via the injectable clock, LRU eviction at cap+1, invalidateScope dropping target entries while siblings survive, plus an end-to-end TestProviderQuery_CacheHit (server hit once across two identical Query calls) and TestProviderStore_InvalidatesCache (Store flips the next Query back to the live wire path). 23 mem0 tests pass (8 namespace + 9 http + 6 cache); lint 0 issues.
ikeikeikeike
added a commit
that referenced
this pull request
Jun 21, 2026
…brain mitigation) v0.5.1 plumbed an instinct.fallback_on_error flag into the coordinator's Query path but always passed nil for the fallback backend (round 4 AI #1 + #2 Blocker 1: split-brain). v0.6's mem0 plugin gives us a real "non-SQLite primary" to test against, so this commit lights up the second-backend slot. internal/cli/instinct_memory_helpers.go: - discoverFallbackSQLite spawns a separate bough-plugin-memory- sqlite subprocess regardless of cfg.Instinct.DefaultMemoryBackend. Reads the SQLite path from cfg.MemoryBackends[kind=sqlite].path so an operator who wants a dedicated fallback file can point it at .bough/memory/fallback.db while the primary mem0 keeps writing to mem0. - loadInstinctCoordinator now spawns the fallback when fallback_on_error is true AND the primary kind != "sqlite". A SQLite primary remains its own fallback (= no second process, no shared DB file). The close func disposes both subprocesses so the host's lifecycle code stays one-call clean. The coordinator's Query path (v0.5.1) does the actual fallback; nothing here changes the policy. AI #1 + #2 mandated that Store / Forget / Import never fall back to SQLite (that would split-brain the dataset), and mem0's plugin advertises DedupeKey / SourceEventID = false so the coordinator never retries the write — Store fails loud, as the CONTRACT.md prescribes. Build clean across the repo, all unit tests pass, lint 0 issues.
ikeikeikeike
added a commit
that referenced
this pull request
Jun 21, 2026
…deferral) Round 4 AI #2 deferred the Graphiti binary to v0.6.x: the sidecar lifecycle (graph DB + Graphiti server + LLM extraction) does not fit cleanly into v0.6.0 alongside mem0 + CapabilityCompiler + MCP server + signing. v0.6.0 ships the skeleton instead: - examples/memory-plugin-graphiti-skeleton/README.md: adapter guide covering the v0.6.x shape (Provider struct + Capabilities cluster Graphiti will advertise + namespace mapping using Graphiti's group_id concept). - examples/memory-plugin-graphiti-skeleton/docker-compose.yml: Neo4j 5.13 + getzep/graphiti:latest with healthchecks so developers can stand up a local Graphiti stack with one `docker compose up -d` (requires OPENAI_API_KEY for Graphiti's entity extraction). docs/EXTERNAL_MEMORY_BACKENDS.md gets a status matrix bumped to v0.5.x / v0.6.0 / v0.6.x columns, the mem0 section now documents the cache + namespace mapping + split-brain fallback policy that shipped in Ν-1.1, and the Graphiti section explains why the binary lives in v0.6.x as a separate GoReleaser archive. No Go code changes; docs + examples only.
ikeikeikeike
added a commit
that referenced
this pull request
Jun 21, 2026
… claude-skill + mcp) Round 4 priority A2 + A6 land here: agent-skill is the default target (= bough is host-neutral OSS, gh skill is the cross-runtime distribution surface), claude-skill produces SKILL.md when the operator opts into a Claude-specific layout, and the mcp emitter ships read-only first per round 4 AI #2's MCP scope discipline. internal/export/agent_skill.go: - AgentSkillEmitter renders gh skill style markdown with full frontmatter (name / description / version / kind / host / source_ref / tree_sha / generated_by / checksum / state_changing). Steps / Constraints / Evidence / Prerequisites sections follow the gh skill body convention. internal/export/claude_skill.go: - ClaudeSkillEmitter renders an Anthropic Agent Skills SKILL.md with minimal frontmatter (the matcher only reads name + description). Provenance + checksum still ride along so the same supply-chain story applies. internal/export/mcp.go: - MCPEmitter dispatches on Target.MCPKind: tool / resource / prompt. tools default state_changing=false (read-only first); the CLI flips to true only when --allow-write is passed. Spec version pinned to 2025-11-25 in every payload as the protocol-version advertise that round 4 AI #2 mandated. internal/export/registry.go: - DefaultRegistry returns a populated *capability.Registry holding the three builtin emitters. CLI bootstrap (Ν-1.6) calls this. internal/export/emitter_test.go (6 cases): frontmatter shape, filename suffix, MCP kind dispatch (tool / resource / prompt), provenance round-trip, $schema pin, and DefaultRegistry composition. All tests pass; lint 0 issues.
ikeikeikeike
added a commit
that referenced
this pull request
Jun 21, 2026
…0, MCP 2025-11-25) Round 4 AI #2 + AI #1: v0.6.0 ships an MCP server companion binary that exposes bough's memory subsystem to MCP clients (Claude Desktop, Cursor, etc.) as a read-only surface. State-changing methods refuse with codeWriteForbidden until v0.6.x adds the --allow-write CLI flag. cmd/bough-mcp-server/main.go (~100 lines): - discoverSQLite spawns bough-plugin-memory-sqlite as the v0.6 memory source (mem0 / Graphiti backends land with --backend in v0.6.x). - WatchStdin starts the round 4 AI #1 zombie guard: stdin EOF fires Shutdown which kills the SQLite subprocess, releasing the DB file lock before Claude Desktop can spawn the next instance. - Run loops on stdio JSON-RPC; errors trigger Shutdown so we never leave the backend subprocess running. internal/mcp/types.go (~140 lines): - jsonrpcRequest / jsonrpcResponse / jsonrpcError envelopes. - Error code constants including codeWriteForbidden (-32001) for the read-only refusal path. - InitializeResult + ServerCapabilities + BoughMCPCapabilities carries the round 4 priority A7 vendor block so clients can programmatically probe spec_version / read_only / state_changing_tools. - ToolDefinition / ToolCallResult / ResourceDescriptor / ResourcesReadResult: the v0.6 wire types. internal/mcp/server.go (~270 lines): - Server holds the MemoryBackend client + close func + the shut atomic.Int32 that drives Graceful Shutdown. - Run dispatches newline-framed JSON-RPC; dispatchLine routes initialize / tools/list / tools/call / resources/list / resources/read / prompts/list / shutdown. - handleInitialize advertises MCPSpecVersion = "2025-11-25" plus the BoughMCPServer block. - handleToolsList ships only memory.query on v0.6. - handleToolsCall refuses memory.store / memory.forget / memory.promote with codeWriteForbidden + a v0.6.x message; memory.query routes to MemoryBackend.Query and renders rows as text content. - handleResourcesList / handleResourcesRead expose bough://memory/scopes only. - Shutdown is idempotent; post-shutdown requests return an internal error so the host drain cleanly. internal/mcp/server_test.go (6 cases): initialize advertise, tools/list shape, write-tool refusal with codeWriteForbidden, memory.query round trip, resources/list shape, Shutdown idempotency + post-shutdown refusal. Build clean, all tests pass, lint 0 issues.
ikeikeikeike
added a commit
that referenced
this pull request
Jun 21, 2026
…cosign keyless signing) Two new binaries (round 4 priority A1 + A6) and the Sigstore cosign keyless signing flow (round 4 priority A9) land in the release pipeline. builds: - bough-plugin-memory-mem0: ./cmd/bough-plugin-memory-mem0, pure-Go (CGO_ENABLED=0) so it cross-compiles to the same 4 OS-arch matrix every other bough plugin uses. - bough-mcp-server: ./cmd/bough-mcp-server, same matrix. Ships in the same release archive as the SQLite reference-fallback it spawns at runtime. archives.ids gains both binaries so the default tarball (bough_<ver>_<os>_<arch>.tar.gz) now contains 8 binaries (host + 5 plugins + 1 mcp server + 1 mem0 adapter). signs (round 4 priority A9, A11): - cosign-keyless signs every archive with Sigstore via the GitHub Actions OIDC flow (no pre-provisioned private key needed). COSIGN_EXPERIMENTAL=1 enables the keyless path; the workflow must be run with id-token: write. - Signatures land alongside each archive as <archive>.sig + <archive>.pem so `bough plugins verify` can validate them. Snapshot builds skip signing because local `goreleaser release --snapshot` does not have OIDC context. Graphiti is intentionally NOT in this matrix — round 4 AI #2 deferred the Graphiti binary to v0.6.x as a separate GoReleaser archive so the v0.6.0 install footprint stays bounded. All cmd packages build clean; the actual signed release runs in Ν-1.12 once the v0.6.0 tag is pushed.
ikeikeikeike
added a commit
that referenced
this pull request
Jun 21, 2026
…em0 namespace Round 4 review #23 surfaced six findings that block v0.6.0 ship. This commit clears all six in-place; remaining medium / low items land in follow-up commits. #1 internal/cli/capability.go: runCompile passed `currentScope(nil, ...)`, but currentScope dereferences cfg.Repositories on the default path → nil pointer panic on every `bough capability compile/preview`. Use an explicit Scope{Level: worktree, WorktreeID: "default"} literal until --scope flags land in v0.6.x. #2 + #3 cmd/bough-mcp-server/main.go + internal/mcp/server.go: WatchStdin and Server.Run both Read from os.Stdin concurrently, so the watchdog stole bytes the JSON-RPC scanner needed. The ZOMBIE guard also only triggered on io.EOF, so wrapped errors left the goroutine spinning. Both go away by deleting WatchStdin entirely and letting Server.Run's bufio.Scanner detect EOF — main.go's `defer server.Shutdown()` handles the subprocess teardown. #4 + #5 internal/pluginsign/verifier.go + internal/cli/plugin_verify.go: verifyCosign defaulted SigPath to ".bundle" but .goreleaser.yaml writes ".sig", and the command was missing the --certificate-identity / --certificate-oidc-issuer flags cosign 2.x requires for keyless verify. Default SigPath now prefers .sig (with .bundle fallback for legacy artifacts), CertPath defaults to the GoReleaser .pem companion, and the Request grows CertIdentity / CertIdentityRegexp / CertOIDCIssuer fields the CLI exposes as --cert-identity / --cert-identity-regexp / --cert-issuer. PreRunE now validates --pubkey is set for minisign and --cert-identity (or regexp) is set for cosign BEFORE shelling out so the operator sees an Args error rather than a phantom verify failure. #6 internal/mcp/server.go: handleResourcesRead built Scope{Level: level} with no RepoName/WorktreeID, then asked the backend for a count. sqlite's scopeID returns "/" for that shape, so the filter never matched a real worktree row and the resource reported `worktree: 0` even when worktree rows existed. The honest fix is to stop returning a faithful count the host cannot guarantee: readScopesResource now lists the three scope tiers with descriptions and points clients at memory.query for counts (v0.6.x will add an explicit count API once Server learns about cfg.Repositories). #7 (review #23) plugins/memory/mem0/namespace.go: scopeToMem0 worktree case derived rootHash from `RepoName + "/" + WorktreeID`, so two worktrees of the same repo got different user_ids and the documented user-tier sharing did not hold. Use hashShort(RepoName) for both segments; branch identity continues to live entirely in session_id. Build clean, all unit + conformance tests pass, lint 0 issues.
ikeikeikeike
added a commit
that referenced
this pull request
Jun 21, 2026
…view #23 #2/#3) The existing conformance/mcp suite wires Server.Run through io.Pipe, which CI-greened the pre-fix WatchStdin design where a second goroutine on os.Stdin would have raced the bufio.Scanner inside Run. Review #23 #2/#3 surfaced that gap; the WatchStdin function itself is gone, but the in-process suite still cannot regress-test the production stdio path. This test spawns the real bough-mcp-server binary (and its bough-plugin-memory-sqlite dependency, built into a shared temp dir so the server's discoverSQLite finds it via PATH) and drives the MCP initialize handshake through actual stdin/stdout pipes: 1. Build both binaries into t.TempDir(). 2. Start cmd/bough-mcp-server with PATH=tempdir + sqlite db path. 3. Write one JSON-RPC initialize frame; read exactly one response. A WatchStdin-style second reader would steal the bytes and ReadBytes would time out. 4. Close stdin → Server.Run's bufio.Scanner returns false → main returns → defer server.Shutdown() reaps the SQLite plugin. If the SQLite subprocess outlives the server, cmd.Wait blocks past 10s and the test fires. The build cost (~10s for two binaries) is paid once per test run. The test runs without tags so the CI memory job already covers it; operators running `go test ./...` locally get coverage without configuring anything extra. Verified locally: go test -run TestSubprocessLifecycle ./conformance/mcp/... PASS: TestSubprocessLifecycle (1.65s)
ikeikeikeike
added a commit
that referenced
this pull request
Jul 3, 2026
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.
Adds
bough-plugin-postgresfor per-worktree PostgreSQL 16 via services-flake.Same lifecycle shape as
bough-plugin-mysql: Up / ReadyCheck (pg_isready + TCP fallback) / Down / Cleanup / PortRangeDefault (50000-52999) / EnvVars (BOUGH_POSTGRES_HOST/PORT/SOCKET_DIR).make test+golangci-lint runclean.