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: internalclassification — 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 overhttp://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 isingress: 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 surface | PRODUCT invariants | Why it survives |
|---|---|---|
The co-resident POST /stage byte-drop route | Inv-1, Inv-2, Inv-3, Inv-4, Inv-5, Inv-6, Inv-9 | cocoindex 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 image | Inv-8 | The 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 surface | Inv-18, Inv-21, Inv-22 | pollContentItemsFor / 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 contract | Inv-2, Inv-20, Inv-33 | Bytes-on-the-wire survives; see §3 for the one nuance (B1 may make the HTTP hop optional). |
| On-demand, not PR-blocking; launch-flip recorded | Inv-19, Inv-35 | The 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 invocation | Inv-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. What MUST CHANGE (the wholesale delta)
Section titled “3. What MUST CHANGE (the wholesale delta)”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_PATHset 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;
/stageand 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/stageto 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.jsonmust exist at process start before the watch arms — SURVIVES unchanged (it is a cocoindexapp_mainbehaviour, not a Cloud Run behaviour). The Slice-B seeding logic inserver.main()is portable as-is.
3.4 No probe required (Liam decision)
Section titled “3.4 No probe required (Liam decision)”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.ymldoing 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 theCOCOINDEX_*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
fetchlocalhost). 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:
- Slice A —
POST /stageroute (PORT AS-IS)._stage_handler+build_appregistration inserver.py; multipart bytes; loud-reject; path-escape guard. Inv-1..6, 8, 9. Unchanged. - Slice B — corpus activation (REFRAME).
COCOINDEX_SOURCE_PATH=/corpuson the B1 host (Compose env, not a Cloud Run manifest);mkdir -p+.kh-workspace-map.jsonseed inserver.main()(PORT AS-IS); the corpus is on persistent local disk (no ephemeral caveat). Inv-7 (reframed), Inv-17, Inv-29, Inv-30 (moot). - 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
/stagePOST 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). - Slice D —
stageFixturemultipart (PORT AS-IS, with OQ-62-P4-2 nuance). Inv-2, 20, 33. - Slice E′ — trigger + Vitest go-live (REFRAME). B1 trigger (operator/host hook, not
gcloud run jobs execute); theCOCOINDEX_*Vitest env block + the Tier-3 reframe carry; the Inv-35 launch-flip doc item. Inv-18, 19, 35. - {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).
- 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.
5. Open questions for Liam (OQ-62-P4-N)
Section titled “5. Open questions for Liam (OQ-62-P4-N)”- 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
/stageover 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/stageHTTP route optional for the co-located case — but/stageis still wanted for the bytes-on-the-wire contract and for any non-co-located caller. Decide whether/stagestays 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
/healthfor B1 container liveness, or drop it? No readiness probe is required (Liam decision). If B1’s container-health mechanism (Coolify health check / Compose healthcheck) reusesGET /health, keep Inv-9 as a/stage-doesn’t-break-/healthregression note; otherwise drop it. B1 datapath monitoring (watchpipeline_runsarrival, 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.
6. Verification / grounding
Section titled “6. Verification / grounding”- 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 correcteddocs/research/s281-cloud-run-vs-onprem-implications.md(§3 P4, §4-(i) the/healthno-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_dirPRESENT againstcocoindex[postgres]==1.0.3). The/stageroute andwalk_dirwatcher are topology-agnostic, so that verification carries forward unchanged.