Skip to content

DR-142: The deployed unit is the runtime bundle, and it moves to pipeline/ with its tests

DR-142 — The deployed unit is the runtime bundle, and it moves to pipeline/ with its tests

Section titled “DR-142 — The deployed unit is the runtime bundle, and it moves to pipeline/ with its tests”

DR-117 ratified the cocoindex relocation in principle and deferred it — “when cocoindex moves out of scripts/ (agreed in principle, deferred), anything under scripts/cocoindex_pipeline/ moves as one unit, while anything under scripts/tests/ would be orphaned by that move” — and explicitly left the destination, and where the Python tests live, unsettled. services/ has been working shorthand since S525 but was never ratified: no decision covers repo structure at all, and the only two structural occurrences of the string are DR-121’s rejected alternative and an S525 drawer, both rejecting waiting for it.

DR-117’s “moves as one unit” premise is false at the directory boundary (measured, id-443 {443.1}). Six cross-boundary edges: outbound, form_extractors/docx.py:84,89,90 imports three loose scripts/ root modules; inbound, bid_worker.py:53-57, fill_template.py:448, _okf_prototype_draft.py.

The deployment agrees with the code. onprem-deploy.yml:274 is cp -R scripts ${CTX}/scripts — the whole directory — and the single kh-cocoindex-pipeline image runs three processes off the scripts. prefix: cocoindex_pipeline.server, bid_worker (a real service in both platform composes), and cocoindex_pipeline.mock_llm. reference/deployment-architecture.md §2.1 states it: scripts/** is packaged whole by onprem-deploy.yml, so no second image build/CI pipeline is needed.”

So moving cocoindex_pipeline/ alone would split the deployed image’s source across two roots and force cp -R scripts && cp -R services — strictly worse than today.

The unit that moves is the runtime bundle, not the directory. cocoindex leaves scripts/ for a singular, root-level pipeline/ holding cocoindex_pipeline/, its six runtime sibling modules, and its Python tests.

  • pipeline/, not ingest/ (owner, S549): “ingest doesn’t cover the full pipeline scope.”
  • The package name is unchanged, so the rewrite is a single prefix substitution and the fixtures tree, DR-121’s data/ tree and the 47 guard-enforced corpus-manifest.json paths all move by prefix only.
  • The Python tests move with it. docs/reference/testing/test-philosophy.md §3’s mirror rule is scoped to __tests__/ and does not reach scripts/tests/; the doctrine that does is DR-117’s own — “filed by the domain that owns them, never by the kind of artefact they are” — whose Consequences route this question here. DR-121 already extended that rule once, from fixture trees to runtime data; tests are its third application, not a new ruling.
  • deploy/ stays a root-level sibling. Only 12 of its 30 tracked files name cocoindex; the rest are generic Coolify authoring skills and goose-pilot/, which is explicitly carved out of the pipeline concern (mempalace.yaml:94; the goose compose records at :5 that it “does NOT share the cocoindex image/compose”).
  • The move is codemod-driven (owner, S549), following the scripts/codemods/ precedent, and paired with the measured hazard list — a naive prefix rewrite is not sufficient (see Consequences).
  • id-416 / DR-119 runs first (owner, S549). It rewrites the same onprem-deploy.yml lines and the same module-path strings in the same four composes; concurrent execution risks an image that builds green and fails at container start on the live path every deployment pulls.

This amends DR-117 rather than superseding it: DR-117’s filing rule stands and is what decides the tests; only its unit-boundary assumption is corrected.

services/cocoindex_pipeline/ — the standing shorthand. Declined on two independent grounds: no requirement and no current source could be named for a services/ tier (the carried UNDECIDABLE went unanswered against the in-force register, all eight DR files naming the path, platform-context.md, deployment-architecture.md, cocoindex-pipeline.md and four MemPalace passes), and — decisively — it is insufficient, because it splits the deployed image’s source as described above.

Nesting deploy/ under the new root. Declined: it demotes deploy/ from the root level the owner named as the deciding criterion, it reintroduces the mixed-language junk-drawer property the move exists to remove (the new root is a Python package root and the image build context; deploy/ holds .yaml, .sh, Dockerfile and .ts), and the S521 ruling “deploy/ belongs with the pipeline” settles a concern grouping that mempalace.yaml’s ingest-pipeline room already discharges under DR-116.

Leaving the tests behind. Declined: only 3 of 93 are genuinely non-pipeline, and splitting would mean two testpaths and two pytest invocations for three files. It would also leave mempalace.yaml:236-240’s standing claim — “it resolves for free when cocoindex leaves scripts/ — permanently false in a live config.

Continuing to defer. Declined: DR-117 and DR-121 have both been filing files against the deferral since August, and DR-121 warned that a wrong placement guarantees “a third move of the same files.”

  • DR-116 binds the name. Room keys are directory names matched at any depth, and mempalace.yaml:141-143 keys room ingest-pipeline on the literal token cocoindex_pipeline. Because the package name is unchanged, room routing survives the move untouched. Renaming the package later would silently gut the room — mempalace.yaml:127-131 records a prior draft that lost 95 files exactly this way.
  • A naive prefix codemod is insufficient. Four measured shapes defeat grep-for-the-old-path and an attribute-node AST pass alike: 15 segment-wise Path() construction sites across 7 files; imports inside triple-quoted _PROBE_PRELUDE strings executed as subprocesses; 6 importlib.import_module literals plus one package-relative f-string form; and 2 sys.modules keyings. The id-412 lesson, quoted in the code it produced (test_form_extractors.py:48-56), is the standing instruction: grep for the segments, not the path.
  • ci.yml’s python: paths-filter (:65-68) is the one silent-and-green failure mode. If it stops matching, the pytest step never fires and every PR passes with zero Python tests run.
  • A resolve-first is owed before the move (id-443 {443.0}): the same modules are imported under two spellings simultaneously in live code, resolving only via pythonpath. This is the ID-44.5 / ID-177 dual-sys.modules-identity shape, harmless today because those modules are stateless — and the relocation is what detonates it, because the move changes pythonpath.
  • Docs are repointed by rule, not wholesale. ~42 files make current-state claims and are repointed; ~281 are point-in-time spec, task, report and retro prose that keep the old path as a historical record. A DR body is never rewritten — a decision that becomes wrong is amended by a new decision, which is what this record does to DR-117.