Name shared variables, functions, types, database fields, quota families, metrics and configuration after the domain or capability they implement. Do not name shared infrastructure after the AI agent, authoring tool, model or provider that first introduced it. A shared subscription quota, for example, uses an administrative subscription name.
Provider names remain appropriate for actual provider-specific adapters, protocol identifiers and external contracts. Historical data and migration compatibility identifiers may retain legacy names when the concrete compatibility requirement is documented. Keep those references confined to that boundary; do not copy them into new shared runtime names.
Use an existing healthy, branch-owned Minikube profile for successive local
iterations. Do not create a new profile merely because the current commit,
gate, or test command changes. Preserve the verified profile by passing its
explicit MINIKUBE_PROFILE value into the next operation.
Resolve that profile through this worktree's
scripts/minikube-profiles/branch.mk; do not derive a profile from the
current HEAD or invent ports. Profile identity is stable for the canonical
worktree path plus branch, while deployed freshness is owned by the exact
gitHead + worktreeId + clusterFingerprint marker. A creation SHA in
legacy profile metadata is historical, not a reason to allocate another
profile. Reuse the persisted ports.env byte-for-byte; missing, corrupt, or
ambiguous ownership metadata must fail closed instead of regenerating ports.
Before reusing a profile, record and compare all of the following:
- active worktree path, branch,
HEAD, andorigin/dev(the value the T2 lease owner pinned at lane start, not a later re-read of the ref); - target Minikube profile and explicit Kubernetes context;
- the pre-gate state marker:
worktreeId,gitHead,clusterFingerprint, and the exactimagesGeneratedAtvalue from the image manifest; - profile status and currently running profile, pre-gate-sync, and port-forward processes.
Reusing a profile is allowed only when it remains clearly owned by the active worktree/lane. If it belongs to another active branch or task, or ownership is ambiguous, do not touch it: use a dedicated profile instead. A fresh profile is also required when the selected profile is missing or unhealthy. Never use shared fixed localhost ports for branch-owned profiles; use the profile-owned random port mapping already recorded for that profile.
First-hand entry point in this worktree:
scripts/minikube-profiles/branch.mk. Implementation is
scripts/minikube-profiles/branch-profile.sh.
Do not ls/cat ~/.cache/clerum/minikube-profiles/.
MINIKUBE_PROFILE=<owned-profile> \
make -f scripts/minikube-profiles/branch.mk branch-profile-pf
MINIKUBE_PROFILE=<owned-profile> \
make -f scripts/minikube-profiles/branch.mk branch-profile-healthThis is the host-side hold for Control UI / Desktop. Run it on the host, not
from a sandboxed agent shell. A make ... branch-profile-pf that prints PF
lines and exits 0 can still leave registered-but-dead pidfiles when the
runner reaps nohup kubectl children. The planner then fail-louds
PORT_FORWARD_CONFLICT + DEVELOPMENT_SCOPE_REQUIRED before a transition;
T2_PORT_FORWARD_COMMAND has not run yet. Restore with a lasting host hold
and branch-profile-health, then re-enter make minikube-t2. Not a new
profile. make minikube-pf-all-bg is a gate refresh only and must not
replace branch-profile-pf. Do not kill this lane's forwards.
branch-profile-pf-health starts then stops PFs on EXIT — do not use it as
the lasting hold. Inner pre-gate-sync may use --skip-port-forwards; never
pass that globally into make minikube-t2. pre-gate-sync can restart every
deployment, which leaves the host hold bound to terminated pods. Run
make minikube-t2 from a host terminal with T2_PORT_FORWARD_COMMAND set to
the branch-profile-pf command above; T2 runs it once after an in-run sync,
before Health and Playwright, and records PortForwards= in the evidence.
Port-forwards are owned by atomic 0600 records bound to the exact profile,
context, canonical worktree, namespace, Service, local/remote ports, PID,
process start time, and kubectl argv. Never kill or adopt a live legacy
/tmp/pf-*.pid process: it lacks enough provenance and must fail closed.
After a user-facing health probe, revalidate the same live process, start time,
argv, and binding. A registered but dead/reused/ambiguous pidfile is itself a
PORT_FORWARD_CONFLICT; it must not disappear from the T2 verdict merely
because ps no longer lists a child.
All image acquisition, image builds, and targeted deploys must enter through
the documented Make targets or the T2 orchestrator so they inherit the exact
live profile mutation lease. Calling scripts/minikube/build-images.sh or
scripts/minikube/pull-images.sh directly is unsupported for mutating work;
--verify-only is the read-only exception. Docker and Minikube operations must
use finite deadlines. Docker endpoint discovery must resolve
to an explicit local Unix socket or loopback TCP endpoint before switching to
an empty task-local Docker config. Ambient registry credentials are never
copied; a private pull requires an explicit MINIKUBE_DOCKER_AUTH_CONFIG.
The legacy scripts/minikube/setup.sh compatibility path follows the same
boundary: it requires matching MINIKUBE_PROFILE and
CONTROL_API_REAL_PG_CONTEXT, acquires the branch-owned lease before its first
cluster operation for every invocation (with or without --build), and never
falls back to the shared clerum-test profile. The published-image puller
rejects invalid parallelism/retry/delay values and an empty successful
minikube docker-env; neither condition may produce a green acquisition.
For a service-only change in an already healthy profile, rebuild and restart only the affected workload. Prefer the existing targeted path:
MINIKUBE_PROFILE=<verified-profile> \
make minikube-deploy-service SVC=<image-selector> NS=<namespace> \
DEPLOYMENT=<deployment>Examples: control-api / control-plane / control-api,
external-rest-api / profiles / external-rest-api, rpc-proxy /
rpc-proxy / rpc-proxy, and mcp-host / mcp-host / chatllm.
scripts/minikube/build-images.sh --only=<image-selector> is the underlying
image-only primitive; do not call the all-image builder for a known
single-service change.
Use make minikube-pre-gate-sync to reconcile the owned profile after T1
inputs change. It owns the cluster fingerprint, migration ordering, rollout
checks, and runtime marker. It is not a T2 verdict. It compares the deployed
marker to the current worktree, rebuilds only the known affected image
selectors, and restarts only their deployments. A fresh profile, an explicit
forced sync, or an unmapped runtime path must fall back to the established
complete image build. A service-only update may use the targeted path when the
profile is already healthy; record it as a targeted sync and prove the
affected deployment is Ready plus its user-facing health endpoint. Do not
report that as a full reconcile or as T2.
The image acquisition stamp is part of the baseline in both GHCR and local
image modes. If the manifest's generated value differs from the pre-gate
marker, the incremental planner must invalidate the old gitHead baseline and
reconcile before certifying; a matching commit alone does not prove that pods
run the newly acquired image IDs.
Expect a full reconcile when deployment manifests, CRDs, charts, network
policy, or other infrastructure inputs changed; do not replace that safe path
with partial image updates. Every kubectl invocation must use the verified
context explicitly.
The T0/T1/T2 validation tooling is development-only. It must run only from a
clean development branch descended from the current origin/dev, against the
branch-owned Minikube profile and its explicit Kubernetes context. It is not a
production, GKE, Cloudflare, staging, shared-cluster, or customer-data
runbook.
Shell contract tests for this tooling must be hermetic. They may source the
real harness scripts from the checkout, but any fixture that needs Git state
must use a temporary fixture repository (the shared helper is
scripts/tests/lib/minikube-fixture-repo.sh) and point T2_PROJECT_DIR at
that repository. Tests must never git switch, create branches, or restore
HEAD in the developer checkout, including when CI checks out a detached HEAD.
Each such fixture must assert that the host checkout's HEAD, branch, and
working-tree status are unchanged on exit. The source checkout remains the
Makefile/script root; the temporary repository is only the lease identity
under test.
Use these canonical entry points:
MINIKUBE_PROFILE=<verified-profile> CONTROL_API_REAL_PG_CONTEXT=<verified-context> make minikube-t2-preflight
MINIKUBE_PROFILE=<verified-profile> CONTROL_API_REAL_PG_CONTEXT=<verified-context> make minikube-t2
MINIKUBE_PROFILE=<verified-profile> CONTROL_API_REAL_PG_CONTEXT=<verified-context> make minikube-t2-runtime
make minikube-t2-preflight is a cluster-read-only planner; it may write its
ignored local lock/evidence metadata. It is not T0, T1, or T2.
With the default T2_PLAN_MODE=false it fails loud on an unbootstrapped
profile and does not call pre-gate-sync. make minikube-t2 is the full
orchestrator: T0, the selected bootstrap/reconcile, T1, then T2. A T2 verdict
is produced only by the final exact-head preflight inside that orchestrator
(T2_PLAN_MODE=false with plan state already-synced; T2_PLAN_MODE=true is
only the internal planner so full-bootstrap is reachable).
make minikube-pre-gate-sync alone never constitutes T2 evidence.
Follow the rule .cursor/rules/minikube-t0-t1-t2.mdc and the skill
.cursor/skills/minikube-t0-t1-t2/SKILL.md for the certification workflow.
A T1 next: line or REAL_PG_REQUIRED_BUT_UNAVAILABLE is not a license to
operate Docker: do not docker run probes, docker desktop restart, or
experiment with published ports. Re-enter make minikube-t2 or
make minikube-t2-real-postgres on the owned profile.
After T0 and T1 are already green on the same HEAD and owned profile, close
T2 with make minikube-t2-runtime (T2_RUN_T0=false T2_RUN_T1=false). That
path is valid only when the pre-gate marker already matches HEAD
(already-synced). Do not set those flags to skip an uncertified lane.
Before repeating an already completed full T0/T1/T2 certification, propose
the repeat with its reason and expected cost, then ask the user whether to run
it. A recommendation alone does not authorize a repeat. This choice does not
turn an earlier HEAD's evidence into certification for a newer HEAD: if the
user declines, report the newer HEAD as uncertified. A same-HEAD
minikube-t2-runtime retry to finish an authorized run is the supported
recovery path, not a new full recertification.
For branch-owned user-facing E2E health journeys, resolve the seeded test
credential locally before patching HCC or creates fault-injection fixtures.
Use scripts/e2e/load-dotenv.sh with dotenv_load_canonical_root and
scripts/e2e/admin-credentials.sh with e2e_resolve_admin_password; an
explicit E2E_USER_PASSWORD may override that journey's credential. Keep the
resolved value in process memory. Never inspect, print, log, or persist .env
contents, even to test whether a key exists. Missing credentials must fail
preflight before mutating the owned profile. Use the profile's persisted
ports and the exact T2_PORT_FORWARD_COMMAND hold when pre-gate-sync can
restart a service; targeted-sync also requires a bounded
T2_HEALTHCHECK_COMMAND.
The active profile mutation lock is $T2_LOCK_ROOT/<profile>.lock; stale-lock
reclaim coordination uses the sibling directory
$T2_LOCK_ROOT/<profile>.reclaim. A killed reclaimer may leave that sibling
claim behind. Before manual recovery, verify that the recorded owner PID, any
reclaimer process, and every other profile-mutating session are gone. Operate
only on those two exact paths: use rmdir -- "$T2_LOCK_ROOT/<profile>.reclaim"
for an empty claim and
rm -rf -- "$T2_LOCK_ROOT/<profile>.lock" for the stale lock. If only one
path exists, remove only that path; never remove either path while its owner or
reclaimer is live, and never remove the lock root.
A fresh or uninitialized profile must complete the supported bootstrap. The
standalone preflight refuses to call pre-gate-sync on an incomplete profile;
make minikube-t2 runs the supported setup when its planner selects
full-bootstrap. The single run is self-healing: when a bootstrapped profile
has an unready required deployment, the orchestrator planner selects
full-reconcile (naming the deployment) instead of failing
PROFILE_UNHEALTHY before a transition exists; the T1 lane restores
branch-profile GFS credentials on exit and pre-gate-sync provisions GFS
serving with GFS_RESTORE_ACTIVE_NOLOGIN=true and
GFS_RECOVER_ABANDONED_STATE=true in the minikube-t2 transition, so other
security gates do not mutate GFS and no manual repair script belongs between
plan and verdict. Harness GFS reconciles settle
Ready-reader leftovers first (settle-gfs-reader-rollout.sh) and wait on
reader readiness through the gfs-rollout-shim PATH prefix instead of a
generation-based rollout status, because HCC's gfsReconciler now preserves
the restartedAt annotation and leftover ReplicaSets can still poison a
generation wait. The standalone preflight and
the final exact-head T2 check stay fail-loud on an unready deployment.
When REUSE_DB recovery is needed, the fence covers all four database writers:
HCC, workflow-recipes, trace-maintenance-worker, and control-api. The durable
recovery identity is the owned profile/context/worktree/branch; the interrupted
HEAD is retained as audit data while the exact-head marker controls freshness.
The trace-maintenance-worker fence must scale its Deployment to zero and prove
both .spec.replicas == 0 and no remaining writer pods before persisting the
trace-fenced phase; restoring its saved replica count happens only after the
recovery window.
Real PostgreSQL suites are opt-in in the ordinary test
matrix, but a T1 run that requires them must fail when the database/DSN is
unavailable or when zero tests execute; a green run must never be produced by
silently skipping the suites. The JSON reporter must be complete and green,
must identify the exact selected physical files, and the Vitest process must
also exit zero; a green reporter cannot hide a teardown, worker, OOM, signal,
or partial-selection failure. Run the local Node/package/Docker preflight
before expensive T0 work. That preflight (packages=2) only checks host
vitest+pg in control-api and gfs-controller. pre-gate-sync later
runs host npm test in each changed package (packages/workflow-runtime-core,
packages/network-policy-core, control-api, external-rest-api,
rpc-proxy, mcp-host, host-context-controller, workflow-recipes,
packages/workflow-sdk, workflow-approval-request-reader, control-ui,
and desktop-app; gfs-controller is not on that list, and the
minikube-t0-t1-t2 skill reference.md carries the same list).
sh: vitest: command not found /
minikube-pre-gate-sync Error 127 after planner PASS and Ready deployments
is a missing host npm ci in that directory — not GFS and not a license
for a new profile. Install every remaining pre-gate package, then re-enter
make minikube-t2. Do not edit tracked files while that run is live: T0
fixtures fail fixture mutated the host checkout working tree, and T2
refuses a dirty tree. T1 is serial by safety contract
(VITEST_MAX_WORKERS=1, no file parallelism); do not widen it for speed.
Suites that drop or rewrite cluster-global roles must use the harness throwaway
Postgres 16, never the shared control-postgres.
NP08 observes the existing Host access-token lineage and may reread a newer,
same-binding persisted access token. It must never consume a refresh token,
call refresh/reissue, or weaken single-use rotation. After T0/T1 have emitted
an exact-head lane attestation, failures in NP08, a user-facing health check, or
Playwright retry through minikube-t2-runtime; T1 failures may use the
standalone Real PostgreSQL target while iterating, followed by one full
certification run once green.
T0 (static/unit/contract checks), T1 (real PostgreSQL), T2 (validated runtime),
CI, Control UI/Desktop Playwright, and product E2E scripts such as
scripts/e2e/e2e-hcc-rollout-readiness.sh are separate evidence lanes. One
lane does not stand in for another. User-facing health is mandatory and bounded
for a targeted-sync transition via T2_HEALTHCHECK_COMMAND; it remains
opt-in for bootstrap, full reconcile, and already-synced runs. A planner
T2_PREFLIGHT_PASS / transition=targeted-sync followed by
PROFILE_UNHEALTHY (targeted sync requires a profile-owned user-facing health command) means the command was missing — re-enter make minikube-t2
with it set. For workflow-recipes, use branch-profile-health plus an
in-pod kubectl --context=<owned> -n control-plane exec deploy/workflow-recipes
wget of container /health on the Deployment http port. Do not invent a
host port or write a loopback URL into the public tree. Playwright is
opt-in via T2_PLAYWRIGHT_COMMAND (T2_REQUIRE_PLAYWRIGHT=true refuses
NOT_RUN). Private operational state,
generated ports, profile metadata, logs, and evidence belong under the ignored
.local-notes/infra/runs/ path and must never be committed.
Electron's postinstall downloads the runtime used by Desktop tests and the
local T2 gate. When that script does not run, npm ci still exits 0 and the
missing runtime only surfaces later as a test failure that looks like a product
bug. Nothing in package.json can force the script to run, so the invariant is
enforced by verification rather than by configuration: npm run verify:electron
resolves require('electron') and fails when the runtime is absent. Desktop
validation must use Node 24, which is the CI and runtime contract. Do not
install desktop-app with --ignore-scripts, and do not count a Desktop
test/build result until these checks succeed from desktop-app:
node --version # must report v24.x
npm run verify:electronIf it fails with Electron failed to install correctly, the dependency install
is incomplete, not a product failure. Stop the gate, switch to Node 24, remove
only the generated desktop-app/node_modules/electron directory, rerun npm ci with lifecycle scripts enabled, and rerun the checks. Do not set
ELECTRON_SKIP_BINARY_DOWNLOAD, point
ELECTRON_OVERRIDE_DIST_PATH at an unverified binary, or report green evidence
from a missing runtime. A verified local cache may be used only when its
version and checksum are recorded in the run evidence.
Do not create new branches with agent/vendor prefixes such as codex/*,
claude/*, openai/*, anthropic/*, antrophic/*, or any other Frontier
Labs reference. Use repository-standard conventional prefixes instead:
feat/*, fix/*, hotfix/*, chore/*, docs/*, test/*, refactor/*,
ci/*, build/*, perf/*, or revert/*, choosing the one that describes
the change. This rule applies to new branches; it does not retroactively
rewrite branches that already exist outside the task's scope.
Use structured service logging for production Node.js/runtime code. Pino is
the repository reference implementation: control-api already exposes a
redacting root Pino logger and workspace-files-controller uses Pino with
pino-http. The repository does not use Winston. Other services currently
have local structured adapters (for example, mcp-host, HCC, workflow
recipes, and the workflow approval reader); those adapters remain the local
boundary until a separately scoped service migration is approved.
The choice is about the logging contract, not a blanket dependency migration:
structured JSON, explicit levels, contextual fields/child loggers,
LOG_LEVEL filtering, and secret-safe redaction. Direct console.* calls in
production code bypass those guarantees; in control-api they also bypass
Pino's redaction list. Winston or a new ad-hoc logger must not be introduced
to solve a one-file cleanup.
When modifying an individual file:
- In production Node.js code, use the existing logger or structured adapter
for that service. Do not bypass it with
console.log,console.info,console.warn,console.error,console.debug, orconsole.trace. - Map levels consistently:
log/info→info,warn→warn,error→error, anddebug/trace→ the adapter's debug/trace level. Pass fields as structured data; pass errors as an error field (for Pino,{ err }) rather than interpolating them into a string. - Preserve the service's redaction, correlation, and JSON-output behavior. Never log tokens, credentials, authorization headers, DSNs, private URLs, cookies, keys, or raw request/response bodies.
- Scope the cleanup to the file already being changed and the minimum import or adapter support it needs. Do not sweep neighboring files, start a repository-wide migration, or add a second logging convention as collateral work.
- Direct
console.*/process.stdoutis allowed only inside a logger adapter, an explicit CLI/bootstrap path that runs before a logger exists, tests, E2E/fixtures, or intentional development-only diagnostics. Do not import a server logger into browser/renderer bundles; use their existing client logging boundary.