Known technical debt

These are known, deliberate, and load-bearing. Do not “fix” any of them without discussion – each one is the honest interim for a problem whose real fix is a product decision, and several look like oversights precisely because the honest interim resembles one.

These are known, deliberate, and load-bearing — do not “fix” them without discussion:

  • introspectionScript.py duplicates format-handling logic from dataLoaders.py. Container scripts cannot import from the host.

  • dictCtx abbreviates “context” although the style guide forbids abbreviating words shorter than eight characters. It is the per-hub context dictionary every route module receives, and its spelling is part of a registration signature used throughout the backend (fnRegisterAll(app, dictCtx)). Ruled an accepted, documented exception (ruling R4, 2026-10-02): neither style scanner flags it and it is not to be renamed in passing. tests/testStyleInvariants.py pins this entry. el in the frontend is NOT such an exception; it waits for a JavaScript naming lane and must not be mass-renamed piecemeal.

  • DockerConnection.fnWriteFileViaTar no longer builds a tarball. It execs a fixed program as the container user and streams the bytes on stdin (vaibify/docker/confinedWrite.py), because a put_archive extracts as root and follows symlinks the in-container agent planted. The name is kept because the mutation ledger, the falsification registry and sixty-odd call sites key on it; renaming it is a mechanical follow-up, not a behavior change. fnWriteTreeViaTar no longer uses put_archive either: it streams a tar to a fixed receiver program (the same module) that lands each member relative to directory descriptors, so a planted symlink is refused rather than followed. fnWriteFileViaTar is now a thin wrapper over fnWriteFileFromStream, the streamed funnel: the program copies stdin in 1 MiB chunks, fsyncs, refuses a stream whose length differs from the stated size, and can refuse to replace an existing file (atomically, by hard link, where the filesystem has one). The host connection has the same method. put_archive must never come back for streaming; the streaming write is this one.

  • Downloads no longer ask the daemon for an archive. get_archive and docker cp read as root, so a component an agent swapped for a symlink after the path check redirected the read. DockerConnection.fiterReadFileConfined and fiterReadDirectoryAsTar run fixed programs as the container user (vaibify/docker/confinedRead.py); the host leg is vaibify/host/hostConfinedRead.py, held to the same contract by tests/testConfinedReadParity.py. A final symlink is followed only lexically and only inside the authorized root; a folder archive never follows a link and holds no member beneath one. The older fiterStreamFile (daemon archive) still serves callers that have not moved.

  • scriptFigureViewer.js was not part of the 2026-01 frontend refactor. Kept as a single cohesive module.

  • Re-export blocks exist across pipelineRunner, pipelineServer, testGenerator, and syncDispatcher for backward compatibility. Callers should migrate toward canonical imports over time; do not delete the re-exports until external callers are updated.

  • vaibify/reproducibility/githubWorkflow.py is implemented, tested, and unreachable — no product code imports it. Kept rather than deleted because wiring it expands remote-execution surface, which is a product decision. tests/testOrphanedPublishMachinery.py fails if the docs re-advertise it or if it gains a caller while still marked unreachable.

  • condaPackages is refused at validation rather than installed. The Dockerfile installs Miniforge for a non-pip package manager but has no conda install step and no build argument carries the list, so accepting the field produced a container without the requested packages and said nothing. Wiring it is the honest fix; refusing is the honest interim.

  • terminalContainment.py and the terminal journal kind also reconcile records written by EARLIER hub versions — release, the safe reaper and shutdown all settle terminal records through fdictTerminateAndProveRecord — and tests/testTerminalContainmentLive.py keeps the container process-group prover as the standing demonstration that it cannot see a setsid descendant (tests/testHostTerminal.py carries the host leg’s twin demonstration). This bullet used to say the in-memory registry was permanently empty; the terminal came back for containers on 2026-08-11 and for host projects on 2026-08-15, so live sessions register in production again.

  • commitCarrier.fdictRequestDurableTaskCancel has no caller and refuses everything. Kept deliberately: Python cannot interrupt a worker in asyncio.to_thread, so there is no honest generic cancel, and a reader who finds no function at all re-derives that from scratch — or writes one. The refusal is the answer, in the place the question is asked.

  • The poison axis is NOT subsumed by the journal quarantine. A quarantine record survives a crash; it does not fence a socket that is open right now. Poison does both — the pipeline lane is refused at the gate and revalidated per frame — so the two are complementary, not redundant.

  • bAllowEmulation on an obtained project’s registry entry is frozen at conversion. The wizard offers the emulation checkbox only when the daemon was reachable while the wizard was open and its architecture differed from the pin, so a clone converted while Docker was unreachable can never consent later: no route updates the flag (fnUpdateImageSource is called only for the overlay fields). The forward switch (switch-to-pinned-image) asks the question afresh, so switching to building and back is the workaround today; a route that updates the flag is the fix, once a researcher meets it.

  • “Retire this container” is spelled out twice, and vaibify destroy means something narrower than the dashboard’s Delete. The stop-or-remove branch plus the keep-alive stop lives in both registryRoutes._fnExecuteStop and environmentDeletion._fbRemoveContainer, so both reach the lifecycle gateway’s primitives from outside it and the second copy raised I_MUTATION_CAPABLE_OUTSIDE_GATEWAY_BUDGET by two. Homing the operation inside containerManager would collapse both call sites and lower that ratchet by three; it was not done alongside the delete feature because _fnExecuteStop is patched and source-inspected by name from four test modules, so the move needs its own change and its own reconfirmation. Separately, the CLI’s vaibify destroy removes only the workspace volume and optionally the image — not the container, the credentials volume, the other image tags, or the registry entry — so the CLI and the GUI now mean different things by “destroy”. Widening a CLI command’s blast radius is a product decision, so the divergence is recorded rather than quietly resolved.

  • The memory watch never names the process an out-of-memory kill took. The kernel writes the victim’s name only to the Docker virtual machine’s own log, which a container can read only when it is privileged and shares the host’s process namespace – a container vaibify will not create to answer a display question. Guessing the victim by comparing process lists before and after would show a guess as fact, so the sentence says “a process” and asks the researcher to check what was running.

  • The memory watch keeps its incident history in the hub’s memory (containerMemoryWatch). A hub restart forgets the kills of containers that have since been removed, and a running container’s kernel counter is reported again, as a beforeObservation incident whose time vaibify did not see. Persisting the history is possible; it was not done because a restart that loses history is honest about it, and a store on disk would need its own retention rules.

  • A container terminal’s prompt reset (S_PROMPT_RESET_RCFILE in terminalContainment.py) is appended after the hooks in the researcher’s ~/.bashrc, but a hook that a tool adds to PROMPT_COMMAND later, at runtime, runs after it. That is harmless: the reset is idempotent and hands on the exit status it received, so a later hook sees what it would have seen. Running it after every hook ever added would mean rewriting PROMPT_COMMAND on every prompt, which is a larger intrusion into the researcher’s shell than the defect it would cure.

  • A live CPU or memory limit change (resourceLimitApplication) runs under the per-container mutation lock, so it is serialized with guarded writes and with a start’s settlement, but not with a stop: the stop route holds no such lock (its own comment in registryRoutes records why). The update is pinned to the container id a fresh inspect named, so it can only ever change that container, and a stop that lands mid-update makes the re-inspect fail and the outcome read “could not be applied”, never “applied”.

  • A vaibify connect or vaibify verify shell is recorded on the host (~/.vaibify/cliShells/) and ended when its window closes or, once the CLI is provably dead, by the hub’s reaper, but it takes no container flock and writes no journal record, so it remains invisible to the quiescence claim: a project in which only a CLI shell has run still reports its quiescence as it did before. The record exists for cleanup, not for the claim; giving the claim sight of CLI execs is a later step that needs no lock to add.

  • An interactive session in a container that no vaibify window or CLI opened cannot be attributed: vaibify cannot tell a researcher’s own docker exec or an editor’s remote shell from a leak, so the remnant panel lists it as possibly orphaned and never ends it on its own.

  • The setsid escape: a descendant that calls setsid leaves the session the containment record tracks, so every group-based proof, the dashboard terminal’s, the CLI shell’s and the scanner’s, can miss it. Whether the agent CLIs or their tool subprocesses do this has not been measured; measure before building anything on the answer.

  • A caffeinate -s that is not in vaibify’s spawn ledger cannot be told from one the researcher started by hand, which the docker-status hint invites them to do. It is listed as possibly and removed only on the researcher’s confirmation, start-clock gated.

  • A terminal or CLI shell orphaned by a hub that died abruptly runs until the next reaper pass, up to ten minutes, or until a hub is started. A dead-man switch in the container was considered and not built: the VM freezes while the host sleeps, so an in-container watcher would kill live sessions on wake, exactly when the researcher returns. The reaper logs how many it ends per pass; revisit if that is non-zero at more than one hub start in a month.

  • Source comments, docstrings and test oracles still cite design documents that are no longer in the tree: a path under design/, or a label such as “design fact 28”, “D5”, “F7” or “I8”. The directory was removed on 2026-10-26 (planning documents, not reference documentation), and the citations were left in place, because one of them is in the frozen comparisonPolicyV1.py, which cannot change, and several falsification tests name these documents as the independent source of their expected values. Each file is in git history at 4a15f03b; read one with git show 4a15f03b:design/<name>.md. The files were agentCouncil.md, agentCouncilContinuationPlan.md, agentCouncilMultiProviderImplementationPlan.md, agentCouncilPhase0Findings.md, agentCouncilRemediation.md, agentCouncilReviewCouncil.md, agentCouncilVerificationLanes.md, copiesLevel3MergeAndOrderedEndgame.md, cpuClassReproducibility.md, cpuClassReproducibilityImplementation.md, externalServices.md, level3AttainmentSequence.md, permanentArchivePromotion.md and permanentArchivePromotionImplementation.md.