docs(plans): add S13 virtual and remote foundation plan
📦 What this is
The implementation plan for S13 virtual and remote foundation — the format-agnostic service layer that every remote and virtual repository slice composes. It proxies and caches artifacts from external upstreams (remote repos) and fans a request across an ordered set of hosted + remote upstreams to serve the priority winner (virtual repos), plus the security-hardened upstream HTTP client, cache freshness, credential handling, and upstream health monitoring underneath.
This is a plan-only docs MR — no product code ships here. Merging it is the approval gate that unblocks the step MRs (and the S14/S15/S16 remote slices that gate on S13's interface).
🗺️ How the work is split
Fifteen MR-sized steps ordered by a dependency DAG rather than by number. Unlike its consumers, S13 has no external gate — both foundations it needs (the S05-A counter and S27-A periodic scheduling) are already on main — so there is no Track A/B split; the plan front-loads independent work instead:
- Foundation & seam.
VirtualRepositoriesConfig(config), and theinternal/remotecontract — errors, result types, the Lookup/Probe/Fetch interfaces + slice seams + a stub. The contract (Step 2) is the cross-slice gate S14/S15/S16 ratify with the S13 DRI. - SSRF-hardened upstream client (3 steps): pure URL/request validators ∥ the resolved-IP secure dialer, then the assembled RoundTripper +
Client. Each ships its slice of the 15 security-cover fixtures. - Operations. Lookup + cache freshness; the teed Fetch cache-fill pipeline; single-flight coalescing over it; Probe.
- Composed flows. Standalone remote request ∥ virtual priority resolution (+ the allow/deny rule matcher), in
internal/virtual. - Cross-cutting. Credential management; health monitoring (on-demand probe + counter, then the S27-A scheduled sweep +
wireCache/periodic-job wiring).
🧩 What S13 owns vs. leaves to the slices
S13 owns the format-agnostic behavior and the Go interfaces the slices satisfy — and no migrations and no HTTP handlers. The per-format remote/virtual cache tables, URL/media-type parsing, upstream protocol auth, and the kind=1/kind=2 request dispatch are slice-owned (S14 Maven, S15 npm, S16 container; S30/S31/S32 virtual). S17 owns the REST CRUD and the connection-test endpoint that invoke S13 behavior. The slices compose S13's operations; they do not rebuild them.
⚠️ Nuances worth a reviewer's attention
- S13 is a pure library. No schema, no routes — it's
internal/remote/+internal/virtual/+ config + minimal composition-root wiring. Acceptance is unit/integration tests against the service layer, verified against the spec's Data Model ("introduces no new tables") and API Contracts ("no public HTTP endpoints"). - The spec's dependency table is stale. It says the failure counter (S05) and scheduled probe (S27) are "coded against an interface and stub until it lands," but both backends already merged:
internal/cache/counter.Counterandinternal/jobsriver.RegisterPeriodic. S13 wires directly and, as the first consumer, owns adding thewireCachetier and the first periodic-job registration. A spec-amendment MR correcting the table is a tracked follow-up. - The upstream client is high blast radius and built from spec. The reference (
gitlab-http/http_v2) is Ruby; Go SSRF libraries exist but are unaudited with negligible adoption, so the 14 covers are re-implemented in Go (Workhorse'sWithSSRFFilteris a second behavioral reference). The169.254.169.254cloud-metadata rejection is the required canary proving the resolved-IP cover is wired. - Interim plaintext credentials.
tmp_plaintext_*is a time-boxed, closed-beta-only stopgap; the registry must not reach GA on plaintext storage. - #269 container gaps are out of scope here. The blob-transfer deadline,
probe_timeoutscope, and disconnect-survives-fill questions are container-slice concerns not resolved in the S13 spec; this plan implements the spec as written and does not pre-empt them. - Two decomposition calls recorded. The over-ceiling Fetch step was split into pipeline (Step 7) + single-flight (Step 8); virtual resolution (Step 12) was interrogated and kept bundled (its Probe/rules/Lookup/Fetch seams are already separate steps, and the remaining phase machine is one algorithm).