<?xml version="1.0" encoding="UTF-8"?>
<spec xmlns="https://vibevm.org/spec/1">
  <title id="root">TCG-ORACLE-GO v0.1 — the gopls oracle process model</title>
  <status stage="spec" state="done"/>
  <p p="1"><fact id="status-line" status="impl/done">**Status: v0.1 — authored with GO-AI-NATIVE-PLAN v0.1 (Phase 3),
implemented by its Phase 7.**</fact></p>
  <p p="2"><fact id="companion-documents" status="impl/done">The component brief is
[`tools/vibe-agentic-tcg-go.xml`](../tools/vibe-agentic-tcg-go.xml); the
message grammar is
[`TCG-PROTOCOL-GO-v0.1`](TCG-PROTOCOL-GO-v0.1.xml).</fact></p>
  <p p="3"><fact id="DOCUMENT-OWNS-THE-ORACLE-PROCESS" status="impl/done">This document owns
the oracle PROCESS: resolution, LSP lifecycle, configuration, overlays,
quiescence, the fidelity posture, and latency.</fact></p>
  <p p="4"><fact id="QUANTITIES-ARE-CAMPAIGN-MEASURED" status="spec/done">Where the sibling Rust
mechanism cites measured spike facts, this one names the same
quantities as campaign-measured — *the bench harness
(`go-ai-native-tcg bench`) is the instrument; no Go corpus or baseline
has yet been taken, so the figures below are posted targets rather
than measured ones. Creating the Go test codebase to measure against
is deliberately far-future work (the corpus could be LLM-generated or
fuzzer-generated); it is not being built now* — a target moves only
with a committed REPORT reason.</fact></p>
  <section id="resolution" title="1. The process and its resolution">
    <p p="5"><fact id="kind-line-resolution" status="impl/done">`req r1`</fact></p>
    <p p="6"><fact id="ORACLE-IS-THE-CONSUMERS-OWN-GOPLS" status="impl/done">The oracle process is the CONSUMER's own `gopls` binary — the stack
never bundles, links, or vendors an analyzer.</fact></p>
    <p p="7"><fact id="resolution-order-lead" status="impl/done">Resolution order, run
from the project root so `go.work`/module context is honoured, each
failure recipe-carrying and never silently skipped:</fact></p>
    <list ordered="true" p="8">
      <item><fact id="RESOLUTION-GOPLS-ON-PATH" status="impl/done">the `GO_AI_NATIVE_GOPLS` env override when set (a set-but-not-a-file value refuses with the recipe, without probing further), then `gopls` on PATH;</fact></item>
      <item><fact id="RESOLUTION-GOBIN" status="impl/done">`$GOBIN/gopls`, then `$(go env GOBIN)/gopls`;</fact></item>
      <item><fact id="RESOLUTION-GOPATH-BIN" status="impl/done">`$(go env GOPATH)/bin/gopls`;</fact></item>
      <item><fact id="RESOLUTION-HARD-FAILURE" status="impl/done">hard failure: the bridge's `gopls-missing` error with the recipe
   `go install golang.org/x/tools/gopls@latest`.</fact></item>
    </list>
    <p p="9"><fact id="STACK-OBLIGES-THE-MACHINE" status="impl/done">Installing this stack OBLIGES the machine to carry go ≥ 1.24 and gopls
(the same posture as rust-analyzer for the Rust stack and node ≥ 22.6
for the TS one): inside the stack's own test suite an absent tool is a
recipe-carrying FAILURE, never a skip; outside the stack no obligation
exists.</fact></p>
    <p p="10"><fact id="INIT-RESULT-CARRIES-PATH-AND-VERSION" status="spec/done">The resolved path and the server's reported version land in
the `init` result. *Specified, not built — half of it. The version half
ships: `init_result` emits `gopls_version`
(`crates/go-ai-native-tcg/src/serve.rs:74-84`), beside `position_encoding`,
`pull_diagnostics` and `ready`. The path half does not: the path IS resolved —
`resolve_gopls` (`crates/go-ai-native-tcg-bridge/src/lib.rs:145`) returns it —
but it is never put into the result. The Rust stack states the same fact and
fails it the same way.*</fact></p>
  </section>
  <section id="session" title="2. LSP session and capabilities">
    <p p="11"><fact id="kind-line-session" status="impl/done">`req r1`</fact></p>
    <p p="12"><fact id="BRIDGE-SPEAKS-LSP-3-17-OVER-STDIO" status="impl/done">The bridge speaks LSP 3.17 over the child's stdio (Content-Length
framing).</fact></p>
    <p p="13"><fact id="initialize-declares-lead" status="impl/done">The `initialize` request declares:</fact></p>
    <list ordered="false" p="14">
      <item><fact id="INITIALIZE-DECLARES-UTF-8" status="impl/done">utf-8 in
  `general.positionEncodings` (fallback: utf-16 positions converted
  through the line's text, unit-tested on non-ASCII content),</fact></item>
      <item><fact id="INITIALIZE-DECLARES-PULL-DIAGNOSTICS" status="impl/done">pull diagnostics (`textDocument.diagnostic`),</fact></item>
      <item><fact id="INITIALIZE-DECLARES-PUBLISH-DIAGNOSTICS" status="impl/done">publish-diagnostics handling,</fact></item>
      <item><fact id="INITIALIZE-DECLARES-WORK-DONE-PROGRESS" status="impl/done">and `window.workDoneProgress`.</fact></item>
    </list>
    <p p="15"><fact id="FEATURES-KEY-OFF-THE-GRANTED-SET" status="impl/done">Every downstream feature keys off the
GRANTED set — a capability the server does not grant degrades per §6
into a well-formed error or a documented fallback, never a crash.</fact></p>
    <p p="16"><fact id="BRIDGE-ANSWERS-THE-SERVERS-REQUESTS" status="impl/done">The bridge answers the server's own requests:
`workspace/configuration` (with §3's config object),
`window/workDoneProgress/create` and `client/registerCapability` (null
results).</fact></p>
    <p p="17"><fact id="DIAGNOSTICS-CHANNEL-HISTORY" status="spec/done">**Diagnostics channel, stated honestly.** gopls has historically
PUSHED diagnostics (`textDocument/publishDiagnostics`) and gained pull
support later than rust-analyzer; which channel the shipped gopls
grants is pinned by the Phase-7 live chain and recorded in the
differential corpus. *Specified, not built — the history is true, the
recording is not. The channel IS negotiated at run time and carried on the
capabilities (`pull_diagnostics`,
`crates/go-ai-native-tcg-bridge/src/client.rs:33`), but nothing records
which one the shipped gopls granted: `research/tcg-bench/` holds a
TypeScript corpus (`corpus/`) and a Rust one (`corpus-rust/`) and no Go
corpus at all, so there is no differential corpus for this to be recorded
in.*</fact></p>
    <p p="18"><fact id="BRIDGE-SUPPORTS-BOTH-DIAGNOSTIC-CHANNELS" status="impl/done">The bridge supports BOTH: prefer the pull channel
when granted; otherwise collect pushed diagnostics for the target
document with a bounded settle window after `didOpen`/`didChange`.</fact></p>
    <p p="19"><fact id="VALIDATE-ANSWERS-ONE-DOCUMENT" status="impl/done">Either way `validate` answers one document's diagnostics — never a
whole-workspace sweep.</fact></p>
  </section>
  <section id="config" title="3. Configuration">
    <p p="20"><fact id="kind-line-config" status="impl/done">`req r1`</fact></p>
    <p p="21"><fact id="BRIDGE-SHIPS-ONE-CONFIGURATION-OBJECT" status="impl/done">The bridge ships one configuration object, passed as
`initializationOptions` and repeated in every
`workspace/configuration` answer.</fact></p>
    <p p="22"><fact id="config-is-minimal-and-documented-lead" status="impl/done">v0.1 keeps it minimal and DOCUMENTED
— gopls's defaults are production-grade (its diagnostics are not
gated behind experimental flags the way rust-analyzer's E0308-class
ones are; the Rust bridge's config lesson transfers as a posture, not
as content):</fact></p>
    <list ordered="false" p="23">
      <item><fact id="CONFIG-STATICCHECK-STAYS-OFF" status="impl/done">staticcheck integration stays OFF (the floor runs
  staticcheck itself; one tool, one truth),</fact></item>
      <item><fact id="CONFIG-ANALYSES-STAY-AT-DEFAULTS" status="impl/done">analyses stay at gopls
  defaults,</fact></item>
      <item><fact id="CONFIG-FUTURE-KNOBS-EXTEND-ONE-OBJECT" status="impl/done">and any future knob (build tags, env) extends this one
  object in one place.</fact></item>
    </list>
  </section>
  <section id="overlays" title="4. Overlays and versions">
    <p p="24"><fact id="kind-line-overlays" status="impl/done">`req r1`</fact></p>
    <p p="25"><fact id="overlay-is-an-lsp-owned-document-lead" status="impl/done">An overlay is an LSP-owned text document:</fact></p>
    <list ordered="false" p="26">
      <item><fact id="OVERLAY-DIDOPEN-CLAIMS-THE-DOCUMENT" status="impl/done">`didOpen {uri, version: 1,
  text}` claims the document (the server stops reading disk for it),</fact></item>
      <item><fact id="OVERLAY-DIDCHANGE-REPLACES-IT" status="impl/done">`didChange` with full-text sync and a MONOTONICALLY increasing
  per-document version replaces it,</fact></item>
      <item><fact id="OVERLAY-DIDCLOSE-RELEASES-IT" status="impl/done">`didClose` releases it back to disk.</fact></item>
    </list>
    <p p="27"><fact id="proven-rules-are-law-here-lead" status="impl/done">The rules the TS and Rust campaigns proved are law here and the bridge
enforces them structurally:</fact></p>
    <list ordered="false" p="28">
      <item><fact id="OVERLAY-VERSIONS-NEVER-REPEAT-OR-RESET" status="impl/done">versions never repeat within an
  overlay's lifetime (a monotonic counter per open document, never derived
  from content); clearing an overlay (`update {content: null}`) closes the
  document and a later reopen starts again at 1 —
  `crates/go-ai-native-tcg-bridge/src/oracle.rs`, and the bridge's own
  `overlay_versions_are_monotonic_and_close_resets` test;</fact></item>
      <item><fact id="VALIDATE-WITHOUT-CONTENT-READS-DISK" status="impl/done">`validate` WITHOUT inline content reads the disk file and opens it
  with that text, so version bookkeeping has exactly one owner (the
  bridge) and a later disk edit is picked up by the next validate's
  `didChange`;</fact></item>
      <item><fact id="OVERLAID-FILE-NEED-NOT-EXIST-ON-DISK" status="impl/done">an overlaid file need not exist on disk — a hypothetical new file in
  an existing package participates via `didOpen` alone;</fact></item>
      <item><fact id="UPDATE-NULL-MAPS-TO-DIDCLOSE" status="impl/done">`update {content: null}` maps to `didClose`.</fact></item>
    </list>
  </section>
  <section id="fidelity" title="5. The fidelity posture (gopls is go/types, not the compiler)">
    <p p="29"><fact id="kind-line-fidelity" status="impl/done">`req r1`</fact></p>
    <p p="30"><fact id="fidelity-spectrum-lead" status="impl/done">The three stacks now span a fidelity spectrum, and this oracle's place
on it is spec, not fine print:</fact></p>
    <list ordered="false" p="31">
      <item><fact id="FIDELITY-TS-ORACLE-IS-THE-COMPILER" status="spec/done">The TS oracle IS the compiler (the LanguageService is tsc's engine —
  agreement by construction).</fact></item>
      <item><fact id="FIDELITY-RUST-ANALYZER-IS-NOT-RUSTC" status="spec/done">rust-analyzer is NOT rustc (an independent, deliberately partial
  analysis).</fact></item>
      <item><fact id="FIDELITY-GOPLS-STANDS-ON-GO-TYPES" status="spec/done">**gopls stands on `go/types` — the reference library implementation
  of the Go specification, the same framework `go vet` builds on —
  while the gc compiler type-checks with `types2`, go/types'
  deliberately-synchronized port.** The delta is a maintained-identical
  pair whose divergences are treated as bugs upstream: far tighter
  than rust-analyzer↔rustc, still not identity.</fact></item>
    </list>
    <p p="32"><fact id="consequences-lead" status="impl/done">Consequences, all normative:</fact></p>
    <list ordered="false" p="33">
      <item><fact id="CLEAN-VALIDATE-DOES-NOT-CERTIFY-A-CLEAN-FLOOR" status="impl/done">A clean `validate` does NOT certify a clean floor. The floor
  (`go-ai-native floor` → gofmt/vet/build/test) remains the truth;
  consumer-facing docs repeat it.</fact></item>
      <item><fact id="DIFFERENTIAL-CORPUS-PINS-DIAGNOSTIC-CLASSES" status="spec/done">The differential corpus curates diagnostic classes and pins each to
  the floor's own verdict (`go build` / `go vet` exit + message class)
  through a committed mapping table: type mismatch, undeclared name,
  wrong argument count, unknown field, missing return, unused
  import/variable. *Specified, not built: there is no Go corpus and no
  mapping table. The corpora that exist are
  `research/tcg-bench/corpus/` (TypeScript, 7 cases) and
  `research/tcg-bench/corpus-rust/` (Rust, 9 cases); no `corpus-go`
  exists anywhere in the tree. The six classes named above are a
  curation nobody has performed yet.*</fact></item>
      <item><fact id="KNOWN-ASYMMETRIES-ARE-DOCUMENTED-GAP-CASES" status="impl/done">Known asymmetries are DOCUMENTED-GAP corpus cases, not omissions —
  the standing candidates to probe in Phase 7: diagnostics gated on
  saved-vs-overlay state, `go.mod`-dependent resolution under a pure
  overlay, and vet-only findings (printf shapes) that the floor
  reports and the oracle may not. Each observed asymmetry becomes a
  corpus case asserting exactly that shape, so the gap list never
  rots.</fact></item>
    </list>
  </section>
  <section id="degradation" title="6. Quiescence, degradation, never crashes">
    <p p="34"><fact id="kind-line-degradation" status="impl/done">`req r1`</fact></p>
    <p p="35"><fact id="workspace-load-after-initialized" status="spec/done">After `initialized`, the server loads the workspace (go.mod parsing,
package metadata, cache priming).</fact></p>
    <p p="36"><fact id="READINESS-WAIT-IS-DEADLINE-BOUNDED" status="impl/done">The bridge bounds its readiness wait
by a deadline keyed on `workDoneProgress` end events for the initial
load; a deadline pass degrades: answers carry `degraded: true`, so
callers can distinguish warm truth from cold best-effort.</fact></p>
    <p p="37"><fact id="PROGRESS-DRAIN-HEURISTIC-IS-INHERITED-AS-A-WARNING" status="impl/done">The Rust
campaign's falsified progress-drain heuristic is inherited as a
WARNING, not a mechanism: no wait strategy is trusted until the
Phase-7 live chain pins gopls's actual signalling, and a replay test
pins whatever is chosen.</fact></p>
    <p p="38"><fact id="b5-extends-to-the-whole-session-lead" status="impl/done">B5 extends to the whole session:</fact></p>
    <list ordered="false" p="39">
      <item><fact id="UNKNOWN-OP-ANSWERS-A-PROTOCOL-ERROR" status="impl/done">an op the relay does not know answers a protocol error naming the
  known set;</fact></item>
      <item><fact id="ANALYZER-CRASH-ENDS-THE-SESSION" status="impl/done">an analyzer crash surfaces `oracle-crashed` op-grain and ends the
  session (the product registry owns respawn-once);</fact></item>
      <item><fact id="NO-INPUT-MAY-POISON-THE-SESSION" status="impl/done">no input may poison the session.</fact></item>
    </list>
  </section>
  <section id="lifecycle" title="7. Process lifecycle and Windows discipline">
    <p p="40"><fact id="kind-line-lifecycle" status="impl/done">`req r1`</fact></p>
    <p p="41"><fact id="ONE-LONG-LIVED-CHILD-PER-ROOT-SESSION" status="impl/done">One long-lived child per (root, session).</fact></p>
    <p p="42"><fact id="GRACEFUL-EXIT-IS-THE-LSP-DANCE" status="impl/done">Graceful exit is the LSP
dance — `shutdown` request, `exit` notification — with kill-on-drop as
the backstop; the no-zombie property is test-asserted.</fact></p>
    <p p="43"><fact id="PATHS-BECOME-URIS-AFTER-VERBATIM-PREFIX-STRIPPING" status="impl/done">Paths become
URIs only after verbatim-prefix stripping (`\\?\` breaks child argv
and URI builders — the standing house lesson).</fact></p>
    <p p="44"><fact id="STDOUT-CARRIES-LSP-FRAMES-ONLY" status="impl/done">stdout carries LSP
frames only; gopls's own stderr chatter is drained and discarded by
the reader (surfaced only in bridge debug logging), so protocol
streams stay clean.</fact></p>
  </section>
  <section id="latency" title="8. Latency posture">
    <p p="45"><fact id="kind-line-latency" status="impl/done">`req r1`</fact></p>
    <p p="46"><fact id="TARGETS-ARE-POSTED-AND-MEASURED-NEVER-GATED" status="impl/done">Targets are POSTED and MEASURED, never CI-gated (the standing split:
gate what cannot flake, record what can).</fact></p>
    <p p="47"><fact id="posted-targets-lead" status="impl/done">Posted targets for
demo-class trees:</fact></p>
    <list ordered="false" p="48">
      <item><fact id="TARGET-WARM-VALIDATE" status="impl/done">warm `validate` p50 &lt; 500 ms,</fact></item>
      <item><fact id="TARGET-COMPLETE" status="spec/done">`complete` p50 &lt; 300 ms — posted, not yet measured:
  the bench harness (`crates/go-ai-native-tcg/src/bench.rs`) records
  per-case `warm_ms` for `validate` only and computes no percentile of
  any kind; the measurement corpus is deliberately far-future work
  (`BACKLOG.md` B-042),</fact></item>
      <item><fact id="TARGET-COLD-INIT" status="impl/done">cold init-to-ready &lt; 15 s.</fact></item>
    </list>
    <p p="49"><fact id="BENCH-HARNESS-RECORDS-DISTRIBUTIONS" status="spec/done">The bench harness
(`go-ai-native-tcg bench`) records distributions per run; the Phase-7
ledger entry carries the first measured set on `research/go-demo`,
and a target that moves, moves in a committed REPORT with a reason.
*Specified, not built — the harness, not the ledger entry. The harness
ships and does record a per-case distribution: `run_bench`
(`crates/go-ai-native-tcg/src/bench.rs:106`) collects `warm_ms` over three
warm passes per case. What does not exist is the measured set:
`research/tcg-bench/reports/` holds `bench-2026-07-07-baseline.json`
(TypeScript) and `bench-rust-2026-07-07-baseline.json` (Rust) and no Go
run at all, so nothing has ever been measured on `research/go-demo` and
there is no committed REPORT for a target to move against.*</fact></p>
    <p p="50"><fact id="LARGE-WORKSPACE-COLD-INIT-WARNING" status="spec/done">Large-workspace consumers are warned about the product's 60 s
first-request ceiling; the relay's eager init at `serve` start spends
the cold cost as early as possible. *Specified, not built — the eager
init is real, the 60 s ceiling is not a number this stack has anywhere.
The relay does spend the cold cost up front: `serve` boots gopls before
the first host frame (`crates/go-ai-native-tcg/src/serve.rs:235`). A
large-workspace warning also exists, one document over — the tcg brief's
`##RISK-COLD-INIT-ON-LARGE-WORKSPACES` — but it is a spec-layer risk
record, not anything the product emits to a consumer at run time. And the
60 s figure is supported by nothing: the shipped readiness budget is
`READINESS_BUDGET = 45 s` (`crates/go-ai-native-tcg/src/lib.rs:32`) and
this document's own `##TARGET-COLD-INIT` posts `&lt; 15 s`. Three numbers,
and 60 is not one of them.*</fact></p>
  </section>
</spec>
