Skip to content

P4-RECONCILIATION — ID-62 fixture-staging infra under B1 (co-locate / P4)

P4-RECONCILIATION — ID-62 fixture-staging infra under B1 (co-locate / P4)

Section titled “P4-RECONCILIATION — ID-62 fixture-staging infra under B1 (co-locate / P4)”

Status: PROPOSAL — pending Liam ratification. Not a ratified spec change. This document records what must change in ID-62’s ratified {62.2} PRODUCT / {62.3} TECH / {62.4} PLAN to make the Task P4-consistent, and why the change is WHOLESALE rather than surgical (so the binding specs are NOT silently rewritten — per the Planner forbidden-actions discipline). Author date: 29/05/2026 (S282). Trigger: Liam ratified Option B1 (co-locate cocoindex + pullmd, the s281 §3 P4 topology) in S282 — docs/specs/ID-66-onprem-pivot/RESEARCH.md. ID-62 was specced end-to-end for the Cloud-Run in-VPC topology (the P2 world); B1 dissolves the premise the harness is built on. Liam decision carried in: no readiness/liveness probe is required under B1 (Liam decision) — the s281 §4-(i) finding that the Cloud Run /health probe was a datapath no-op is one of the reasons the probe is dropped, not reframed.

Why a proposal, not in-place edits. The Planner brief allowed SURGICAL, largely mechanical edits to be applied inline to PRODUCT/TECH (marked <!-- S282 P4 reconciliation — pending Liam ratification -->). On assessment the change is material/wholesale: it removes the harness’s organising mechanism (the in-VPC Cloud Run Job) and the entire reachability/security-posture invariant block, not just the “in-VPC” wording. Editing that inline would mis-represent ratified content. Hence this proposal. No edits were applied to {62.2}/{62.3}/{62.4} this session.


1. Verdict — WHOLESALE (re-architects the verify mechanism, not a wording reframe)

Section titled “1. Verdict — WHOLESALE (re-architects the verify mechanism, not a wording reframe)”

ID-62’s title is “/stage server + CI → internal-ingress reachability (shared in-VPC harness)”. The in-VPC Cloud Run Job is not a peripheral implementation detail — it is the Task’s organising spine, present in the title, the Goals, a full invariant block (Inv-10..17), the reachability/security-posture block (Inv-23..25), the empirical entry gate (Inv-34), and the {42.10} split-assertion design (Inv-26..28). Under B1 that spine is dissolved, not relabelled:

  • No ingress: internal classification — there are no Cloud Run Services on B1, so the 403-from-external-runner problem that the in-VPC Job exists to solve does not exist.
  • No VPC, no NAT, no WIF gcloud run jobs execute — the reachability mechanism is localhost on a single co-located host. cocoindex reaches pullmd over http://localhost:3000 (s281 §3 P4 / ID-66 RESEARCH §1-b); a verify driver reaches /stage (or the corpus disk) on the same host.
  • The OQ-62-9 / Inv-34 entry gate becomes moot. The spec’s stated FIRST implementation step — “empirically verify a Cloud Run Job’s egress reaches an internal-ingress Service (curl /health → 200 not 403 from inside the Job)” — has no analogue under B1: there is no internal-ingress Service and no Cloud Run Job. The infra-side import-and-call check this gate stood in for is simply not needed.
  • Inv-23/24/25 are moot, not reframed. “No ingress flip”, “internal-only reachability is the whole point”, “no self-hosted runner” are all statements about the Cloud Run ingress model. On B1 there is no ingress model to flip or preserve; localhost is the reachability.
  • The {42.10} split-assertion rationale evaporates. Inv-9’s “the /s/<share_id> round-trip runs in-Job because pullmd is ingress: internal (unreachable from the GH runner)” (PLAN §2 OQ-62-12) is the sole reason the round-trip was pushed into the Job. On B1 pullmd is on localhost — the round-trip can run wherever the verify driver runs.

Approximately 15 of the 35 PRODUCT invariants are materially changed or rendered moot (Inv-10..17, Inv-23..28, Inv-34, plus the reachability rationale woven through Inv-21/22). That is well past the “surgical, largely mechanical” bar. WHOLESALE.


2. What SURVIVES P4 intact (the durable core)

Section titled “2. What SURVIVES P4 intact (the durable core)”

The reconciliation is not a teardown — a large, valuable core survives untouched and should be preserved verbatim in any re-spec:

Surviving surfacePRODUCT invariantsWhy it survives
The co-resident POST /stage byte-drop routeInv-1, Inv-2, Inv-3, Inv-4, Inv-5, Inv-6, Inv-9cocoindex still runs an aiohttp server with the walk_dir(live=True) watcher in one process. A co-resident /stage route writing to the watched corpus dir is topology-agnostic — same on Cloud Run or a co-located host.
AGPL boundary on the cocoindex imageInv-8The cloudbuild Inv-9 assertion (zero pullmd / Playwright in the cocoindex image) is independent of where the image runs. Survives. (Note: on B1 pullmd is a sibling container on the same host, still NOT baked into the cocoindex image — the AGPL boundary is preserved by the same container-isolation logic.)
The 39-test Vitest assertion surfaceInv-18, Inv-21, Inv-22pollContentItemsFor / dropFixture query live Supabase, reachable from anywhere on any topology. The assertion half is unchanged. (Caveat: see §3 — the reachability rationale for “runs from anywhere” changes.)
The stageFixture multipart-bytes wire contractInv-2, Inv-20, Inv-33Bytes-on-the-wire survives; see §3 for the one nuance (B1 may make the HTTP hop optional).
On-demand, not PR-blocking; launch-flip recordedInv-19, Inv-35The gating policy is independent of topology. The launch-flip action item in the sequencing doc still applies.
One primitive parameterised by (fixture set, assertion set); {42.10} is one invocationInv-26 (shape), Inv-27 (assertions)The “one harness, two invocations” design survives; only the execution context of the harness changes (Job → co-located driver).

The Slice-A work (_stage_handler in server.py) and the Slice-D work (stageFixture JSON→multipart) are essentially portable as-is. The corpus-activation Slice-B work is mostly portable (see §3 — the cold-start / ephemeral-volume framing changes for the better).


3.1 The reachability mechanism: in-VPC Cloud Run Job → co-located localhost driver

Section titled “3.1 The reachability mechanism: in-VPC Cloud Run Job → co-located localhost driver”

PRODUCT Inv-10..17 (the in-VPC verify job block) — re-architect. The verify driver is no longer a Cloud Run Job authenticated via WIF that reaches an internal-ingress Service. Under B1 it is a process on the co-located host that either (a) POSTs fixtures to the cocoindex /stage route over http://localhost:<port>, or (b) — newly possible — writes fixture bytes directly into the local-disk corpus directory that the watcher polls, since the driver and the watcher share the host filesystem (this could make the HTTP /stage hop optional for the co-located case; OPEN — see OQ-62-P4-2). Specifically:

  • Inv-10 (in-VPC Job to reach internal Services)DELETE / REPLACE. No internal Services exist. Replace with “the verify driver runs on the co-located host and reaches /stage (and pullmd, for {42.10}) over localhost.”
  • Inv-11 (WIF gcloud run jobs execute)DELETE / REPLACE. No Cloud Run Job. The trigger becomes the B1 push-to-deploy/operator path (ID-66 RESEARCH §1-d: Coolify webhook or thin GH-Action→SSH, OQ-66-6). The auth primitive is whatever B1 chooses for host access, not WIF.
  • Inv-12 (Secret Manager mounts)REFRAME. Secrets move to the B1 host secrets store (ID-66 RESEARCH §1-f / OQ-66-3 — Coolify env or a host secrets manager). The behaviour invariant “no plaintext secret value in manifest/workflow/logs” survives; the store changes from GCP Secret Manager to the host store.
  • Inv-13/14 (job stages only; no duplicated assertions)SURVIVE in spirit. The driver still stages only; the Vitest tier still asserts. The container/Job framing is dropped but the responsibility split is preserved.
  • Inv-15/16 (exit-code semantics; idempotent re-stage)SURVIVE. Topology-agnostic.
  • Inv-17 (corpus activation is an explicit, observable precondition)SURVIVE, simpler. On B1 the watcher still arms once at process start with COCOINDEX_SOURCE_PATH set to a real dir; the local-disk corpus persists across restarts (no ephemeral-tmpfs caveat). See §3.3.

3.2 The reachability/security posture: Inv-23/24/25 — moot, REPLACE with the B1 posture

Section titled “3.2 The reachability/security posture: Inv-23/24/25 — moot, REPLACE with the B1 posture”

These three invariants are entirely about the Cloud Run ingress model:

  • Inv-23 (no ingress flip)MOOT. No Cloud Run ingress to flip. Replace with the B1 network posture: the co-located stack is not publicly exposed; /stage and pullmd bind to localhost / the host’s private interface, not a public ingress. The corpus-injection attack-surface concern that motivated Inv-23 still matters and must be re-expressed for B1 (don’t bind /stage to a public interface).
  • Inv-24 (internal-only reachability is the whole point)MOOT / REPLACE. Localhost is the reachability; there is no network-layer isolation to preserve or relax.
  • Inv-25 (no self-hosted runner)MOOT. The rejected option (b) (self-hosted GH runner in-VPC) was a Cloud-Run-era alternative. On B1 the verify driver runs on the B1 host by construction; the self-hosted-runner question does not arise.

3.3 Corpus volume: Inv-7/30 + OQ-62-2 — the tension dissolves (change for the better)

Section titled “3.3 Corpus volume: Inv-7/30 + OQ-62-2 — the tension dissolves (change for the better)”

PRODUCT Inv-7 / TECH OQ-62-2 carefully reasoned that the corpus must be an ephemeral in-pod dir (no Filestore, cold-start loss acceptable because fixtures re-stage per run). Under B1 the corpus lives on the same persistent local disk as the cocoindex LMDB (ID-66 RESEARCH §1-c — local disk mmaps/locks natively, the corpus dir co-hosts via COCOINDEX_SOURCE_PATH). So:

  • Inv-7 (no new persistent volume while co-resident)REFRAME. On B1 there is no Filestore-vs-ephemeral tension; the corpus is a directory on the host’s persistent disk. The “cold-start corpus loss mid-run” risk (TECH §Risks) disappears — the corpus survives restarts. This is strictly simpler.
  • Inv-30 (corpus volume kind only if not co-resident)MOOT. The co-resident default is the only shape on B1.
  • The PLAN §3.1 load-bearing carry — /corpus/.kh-workspace-map.json must exist at process start before the watch armsSURVIVES unchanged (it is a cocoindex app_main behaviour, not a Cloud Run behaviour). The Slice-B seeding logic in server.main() is portable as-is.

The Cloud Run readiness/liveness httpGet /health probe — which s281 §4-(i) flagged as a datapath no-op (it kept the revision green on a separate aiohttp thread even when the worker idled/crashed) — is NOT required under B1 (Liam decision). On B1, host/container liveness + datapath monitoring is the B1 ops concern (ID-66 RESEARCH §1-f — watch pipeline_runs row arrival, not just process liveness). Inv-9’s “/health behaviour unchanged by adding /stage” can be kept as a /stage-does-not-break-/health regression note if /health is retained for B1 container health, OR dropped if B1 uses a different liveness mechanism — OPEN, see OQ-62-P4-3.

3.5 {42.10} split-assertion design: Inv-26/27/28 + PLAN OQ-62-12 — simplify

Section titled “3.5 {42.10} split-assertion design: Inv-26/27/28 + PLAN OQ-62-12 — simplify”

The PLAN split the Inv-9 /s/<share_id> round-trip into the Job “because pullmd is ingress: internal, unreachable from the GH runner”. On B1:

  • Inv-26 (one primitive, {42.10} is one invocation)SURVIVES.
  • Inv-27 (Inv-7/8 GH-runner assertions via live Supabase)SURVIVES (Supabase reachable from anywhere on any topology).
  • Inv-28 (Inv-9 /s/<id> round-trip from inside the in-VPC Job context)REFRAME. The round-trip runs wherever the B1 verify driver runs (the co-located host can reach pullmd on localhost). The PLAN OQ-62-12 “split-home because pullmd is internal” rationale dissolves — Inv-9 can run on the host driver, or even on the GH runner IF the B1 host exposes a private path the runner can reach (unlikely / undesirable). Most natural: the co-located driver runs the Inv-9 round-trip over localhost. OPEN, see OQ-62-P4-2.

3.6 Trigger workflow: Slice E / Inv-11/19 — replace the trigger, keep the policy

Section titled “3.6 Trigger workflow: Slice E / Inv-11/19 — replace the trigger, keep the policy”
  • Inv-19 (on-demand, not PR-blocking; commented schedule: flip marker)SURVIVES as policy. The gating policy is topology-agnostic.
  • The mechanism (.github/workflows/cocoindex-live-verify.yml doing WIF + gcloud run jobs execute) → REPLACE with the B1 trigger (operator runs the verify driver on the host, or a B1 push-to-deploy hook triggers it). The Vitest go-live step (running the cocoindex tier with the COCOINDEX_* env block) survives — but see §3.7.

3.7 The Tier-3 topology tests: the reframe is REDUCED (a P4 bonus)

Section titled “3.7 The Tier-3 topology tests: the reframe is REDUCED (a P4 bonus)”

PLAN §3.3 forced a per-file reframe for the 8 Tier-3 topology files (health-probe, sidecar-*, stage-topology, latency-budget, transient-retry, audit-log-shipping) because they fetch the internal Service URL → 403 from the GH runner. On B1:

  • If the co-located stack exposes no GH-runner-reachable URL, these still cannot run on the GH runner — but the reason is “the stack is on a private host”, not “Cloud Run ingress”.
  • The natural P4 home for topology assertions is the co-located verify driver (it can fetch localhost). This is cleaner than the Cloud-Run-era “skip-on-GH-runner + assert in-Job exit surface” gymnastics. OPEN, see OQ-62-P4-2 (whether the driver runs topology assertions or they are deferred).

4. Net shape of a P4-reconciled ID-62 (the proposal)

Section titled “4. Net shape of a P4-reconciled ID-62 (the proposal)”

If Liam ratifies, ID-62 re-specs to roughly:

  1. Slice A — POST /stage route (PORT AS-IS). _stage_handler + build_app registration in server.py; multipart bytes; loud-reject; path-escape guard. Inv-1..6, 8, 9. Unchanged.
  2. Slice B — corpus activation (REFRAME). COCOINDEX_SOURCE_PATH=/corpus on the B1 host (Compose env, not a Cloud Run manifest); mkdir -p + .kh-workspace-map.json seed in server.main() (PORT AS-IS); the corpus is on persistent local disk (no ephemeral caveat). Inv-7 (reframed), Inv-17, Inv-29, Inv-30 (moot).
  3. Slice C′ — co-located verify driver (RE-ARCHITECT, replaces the in-VPC Job). A process on the B1 host that stages fixtures into the corpus (localhost /stage POST or direct disk-drop — OQ-62-P4-2) and exits non-zero on failure. NO Cloud Run Job, NO WIF, NO Secret-Manager-via---set-secrets. Inv-13..16 (survive in spirit); Inv-10/11 (deleted); Inv-12 (host-secrets reframe); Inv-23/24/25 (replaced by the B1 network posture).
  4. Slice D — stageFixture multipart (PORT AS-IS, with OQ-62-P4-2 nuance). Inv-2, 20, 33.
  5. Slice E′ — trigger + Vitest go-live (REFRAME). B1 trigger (operator/host hook, not gcloud run jobs execute); the COCOINDEX_* Vitest env block + the Tier-3 reframe carry; the Inv-35 launch-flip doc item. Inv-18, 19, 35.
  6. {42.10} invocation (SIMPLIFY). Same primitive + the HTML fixture; Inv-9 round-trip runs on the co-located driver over localhost. Inv-26, 27, 28 (reframed).
  7. DELETE: the OQ-62-9 / Inv-34 entry gate (moot — no internal-ingress Service to probe).

The durable core (Slices A, B-seed, D, the Vitest surface, the one-primitive design) carries forward; the Cloud-Run-specific scaffolding (in-VPC Job, WIF, ingress posture, entry gate) is removed and replaced by the B1 localhost/host-driver shape.


  • OQ-62-P4-1 — re-spec vs amend-in-place? Given ~15/35 invariants change and the Task title itself (“internal-ingress reachability / in-VPC harness”) is now wrong: re-author {62.2}/{62.3}/{62.4} as a fresh P4 spec chain, OR amend the ratified docs in place with a clearly-marked P4 superseding block per invariant? (This proposal recommends a re-spec because the organising mechanism changed; amend-in-place risks a Frankenstein doc.)
  • OQ-62-P4-2 — does the co-located verify driver POST to /stage over localhost, or write bytes directly to the local-disk corpus? B1 makes both possible (driver shares the host fs with the watcher). Direct disk-drop could make the /stage HTTP route optional for the co-located case — but /stage is still wanted for the bytes-on-the-wire contract and for any non-co-located caller. Decide whether /stage stays load-bearing or becomes a convenience. This also decides where the Tier-3 topology assertions and the {42.10} Inv-9 round-trip run.
  • OQ-62-P4-3 — keep /health for B1 container liveness, or drop it? No readiness probe is required (Liam decision). If B1’s container-health mechanism (Coolify health check / Compose healthcheck) reuses GET /health, keep Inv-9 as a /stage-doesn’t-break-/health regression note; otherwise drop it. B1 datapath monitoring (watch pipeline_runs arrival, not process liveness) is the separate ID-66 §1-f ops concern.
  • OQ-62-P4-4 — sequencing against ID-66. ID-62’s B1 re-spec depends on the B1 host + Compose + persistent volume existing (ID-66 §1-a/b/c). Should ID-62 be re-sequenced to depend on ID-66 at the Task level (Task.dependencies[]), since the verify driver runs on the B1 host? (Sibling-only constraint: this is a Task-level dep, not a Subtask cross-dep — consistent with §3.3.) ID-62 impl is already deferred to a later session per the S282 brief; this just records the dependency edge.

  • No edits applied to {62.2}/{62.3}/{62.4} this session — they remain Liam-ratified-as-of-S279, unmodified. This proposal is additive.
  • Invariant counts are from a full read of {62.2} PRODUCT (35 invariants), {62.3} TECH (5 slices A–E + entry gate, 1:1 invariant map), {62.4} PLAN (6 Subtasks {62.5}{62.10}).
  • B1 topology facts are grounded in docs/specs/ID-66-onprem-pivot/RESEARCH.md (§1-a..f, §3) and the corrected docs/research/s281-cloud-run-vs-onprem-implications.md (§3 P4, §4-(i) the /health no-op finding).
  • No external-library symbols are asserted by this proposal that were not already empirically verified in {62.3} TECH §Verification (aiohttp multipart PRESENT, cocoindex.connectors.localfs.walk_dir PRESENT against cocoindex[postgres]==1.0.3). The /stage route and walk_dir watcher are topology-agnostic, so that verification carries forward unchanged.