KB-31A2

NUXT-IMG-H1 — Immutable dual-surface pin + controlled no-pull production recreate of the Nuxt image (2026-07-21)

17 min read Revision 1
current-statenuxt-img-h1docker-imageimmutable-pinoci-index-digestno-pull-recreatedual-surface-pincrash-fast-detectorpreexisting-heap-oomzero-credential-changePASS_NUXT_IMG_H1_IMMUTABLE_DUAL_PIN_NO_PULL_RECREATE_VERIFIEDNUXT_IMAGE_RECREATE_READYNUXT_IMAGE_FALLBACK_STATUSPOST_CRASH_FAST_DETECTOR2026-07-21

title: "NUXT-IMG-H1 — Immutable dual-surface pin + controlled no-pull production recreate of the Nuxt image (2026-07-21)" date: 2026-07-21 date_utc: 2026-07-21T16:10:00Z mission: NUXT-IMG-H1 (REV2) host: Contabo vmi3080463 / 38.242.240.89 author: Claude (Opus 4.8, 1M ctx) — Claude Code mode: production controlled mutation — dual-surface immutable image pin + single --no-deps --pull never --force-recreate of nuxt only verdict: PASS_NUXT_IMG_H1_IMMUTABLE_DUAL_PIN_NO_PULL_RECREATE_VERIFIED payload: REDACTED tags: [current-state, nuxt-img-h1, docker-image, immutable-pin, oci-index-digest, no-pull-recreate, dual-surface-pin, crash-fast-detector, preexisting-heap-oom, zero-credential-change, PASS_NUXT_IMG_H1_IMMUTABLE_DUAL_PIN_NO_PULL_RECREATE_VERIFIED, NUXT_IMAGE_RECREATE_READY, NUXT_IMAGE_FALLBACK_STATUS, POST_CRASH_FAST_DETECTOR, 2026-07-21]

Source: direct measurement over SSH root on vmi3080463, live docker/compose, and 5 predecessor reports (C0 rescue, C2B1-R0 packet, Wave-A1, Balo one-click, NUXT-403-R1) in the KB. Every secret value = REDACTED. Image content digests, config-hash, sha256 file hashes, HMAC fingerprints (16-hex, keyed with an ephemeral per-run key), sizes, counts, timestamps and HTTP status codes are not secrets and are recorded verbatim.


1. EXECUTIVE VERDICT

OVERALL = PASS_NUXT_IMG_H1_IMMUTABLE_DUAL_PIN_NO_PULL_RECREATE_VERIFIED

C0_CONTENT_CHAIN_MATCH        = YES  (live == C0-rescued OCI index chain 72715a…; no drift after C0)
ENV_PIN                       = EXACT_REPO_DIGEST
COMPOSE_FALLBACK_PIN          = EXACT_REPO_DIGEST
NUXT_IMAGE_FALLBACK_STATUS    = PINNED
NO_PULL_RECREATE              = PASS
UNEXPECTED_REGISTRY_CONTACT   = 0
OTHER_CONTAINER_MUTATION      = 0
NORMALIZED_CONFIG_DRIFT       = 0  (functional; only the intended pin + cosmetic mount-order + --no-deps/config-hash labels)
ROUTE_REGRESSION              = 0
BALO_GUARD_PRE_FAIL           = 0   (PRE 37/37 PASS)
BALO_GUARD_POST_FAIL          = 0   (POST 37/37 PASS)
POST_CRASH_FAST_DETECTOR      = PASS             # 15-min detector: no restart/crash/heap signature
ROLLBACK_ATTEMPTS             = 0

=> NUXT_IMAGE_REFERENCE_PINNED     = EXACT_REPO_DIGEST
=> NUXT_IMAGE_FALLBACK_STATUS      = PINNED
=> NUXT_NO_PULL_RECREATE_PROVEN    = YES
=> NUXT_IMAGE_RECREATE_READY       = YES
=> LONG_TERM_NUXT_STABILITY_PROVEN = NO
=> NUXT_CREDENTIAL_CHANGED         = NO
=> C2B1_ROTATION                   = HOLD
=> OPENS: C2B1-S1-DESIGN (only). No repoint / rotation / service-identity work begun.

One line: the exact production Nuxt image (the C0-proven sole-runnable 72715a…, which cannot be pulled — Artifact Registry API disabled) was pinned by its immutable OCI index digest on both surfaces (.env NUXT_SSR_IMAGE and the Compose :latest fallback), then the nuxt service only was recreated exactly once with --no-deps --pull never --force-recreate — same image content, zero pull, zero credential change, zero other-container mutation, PRE/POST routes and Balo guard identical.


2. OWNER AUTHORIZATION

OWNER_GO_NUXT_IMG_H1_REV2 — authorized exactly: (1) pin NUXT_SSR_IMAGE to the exact immutable RepoDigest; (2) replace the Compose :latest fallback with the same RepoDigest; (3) recreate the nuxt service exactly once via local image with --pull never; (4) at most one rollback on a defined H1-caused error. No credential, permission, Directus data or app-source change. Owner also mandated (mid-mission) a full live re-measurement of the 7 gates at execution time and no widening of Gate H0 / no exception re-ask; both were honored.

Execution window (Owner-selected "Option 2 — afternoon"). After the 13:00Z code-backup completed (13:01Z "completed successfully"), re-measured at 15:07:53Z; all gates live-re-confirmed; completion planned ≥2 h before the 18:37Z Directus backup.


3. LIVE RESTART BASELINE & CONFIDENCE (Gate H1, re-measured — no 10:20Z reuse)

Field Value
Container (PRE) incomex-nuxt id 42079c55bf9c…
Container created 2026-07-17T03:49:51Z
PRE StartedAt 2026-07-21T11:06:59Z (uptime ~4 h at T0)
PRE RestartCount 81 (was 80 at the 10:16Z read; +1 restart during the wait — the pre-existing heap cycle)
ExitCode / OOMKilled 0 / false · Health healthy · policy unless-stopped
NUXT_OBSERVED_RESTART_INTERVAL_RANGE_HOURS cumulative avg ≈ 1.3 h (81 restarts / ~107 h) but the most recent measured interval ≈ 18.8 h (16:20:59Z→11:06:59Z) — restarts are front-loaded (crash-loop soon after the 07-17 creation), long-stable since
NUXT_RESTART_PATTERN_CONFIDENCE LOW (irregular; not a fixed period)
KNOWN_PREEXISTING_CRASH_SIGNATURES FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory (V8 old-space exhaustion ≈256 MB under mem_limit 512m); secondary TypeError: a.code.localeCompare is not a function on GET /api/registry/matrix

PRE_CRASH_FAST_DETECTOR (§4 mission): observed 15:07:53Z→15:23:16Z (>15 min) — RestartCount stayed 81, StartedAt unchanged, status running/healthy across every reading ⇒ no restart in the PRE window; HOLD_NUXT_IMG_H1_PREEXISTING_INSTABILITY_WINDOW not triggered.


4. LIMITED MEANING OF THE 15-MINUTE CRASH DETECTOR

The 15-minute POST detector (§16) proves only: the container did not crash immediately after recreate, routes kept working, and no immediate config error surfaced. It does NOT prove long-term stability. Because the pre-existing V8 heap-OOM cycle is real and irregular (§3), a crash after the window does not by itself prove H1 failure or a pre-existing defect — later classification must rest on crash signature + content/config drift + route behavior + timing vs baseline, not on timing alone. LONG_TERM_NUXT_STABILITY_PROVEN = NO.


5. OCI / CONTAINERD CONTENT CHAIN (Gate H2 — live == C0, no drift)

Identity model = containerd image store: the running image ID is the OCI index digest, not the config digest (the classic "image-ID == config-JSON" assumption does not hold here — carried from C0).

RUNNING_IMAGE_ID  sha256:72715a92885c9b2a467cabccdb1e3b8e5221dbfdae68e59f9daef0af08bfab30   (OCI index)
  └─ amd64 image manifest  sha256:c1ac411b1762…
       ├─ config           sha256:36fb4d5b20fa…
       │     └─ rootfs.diff_ids (7) == docker image inspect .RootFS.Layers (7, ordered)
       └─ 7 layer blobs
RootFS DiffIDs (7, ordered):
  989e799e…  e881f558…  3bcb0b08…  1dcaf7a5…  276c9d92…  5833683e…  fcb35c07…
Platform amd64/linux · Created 2026-02-17T08:24:32Z · RepoTags […/nuxt-ssr:latest] · RepoDigests […@sha256:72715a…]

Live chain (10:16Z and re-measured 15:07:53Z) is identical to the C0-rescued chain ⇒ LIVE_IMAGE_CONTENT_CHAIN = C0_RESCUED_CONTENT_CHAIN; HOLD_NUXT_IMG_H1_IMAGE_DRIFT_AFTER_C0 not triggered.


6. EXACT REPODIGEST (Gate H3 — local-resolvable)

asia-southeast1-docker.pkg.dev/github-chatgpt-ggcloud/web-test/nuxt-ssr@sha256:72715a92885c9b2a467cabccdb1e3b8e5221dbfdae68e59f9daef0af08bfab30

docker image inspect <ref> resolves fully against the local daemon → id 72715a…. This is the OCI index digest (not the config 36fb4d5b…); not :latest, not a mutable tag, not an ID prefix, and needs no network. Unlike agent-data (a local-only self-digest), this image carries a fully-qualified RepoDigest that self-resolves. HOLD_NUXT_IMG_H1_LOCAL_DIGEST_REFERENCE_UNRESOLVED not triggered.


7. PRE ROUTES & BALO GUARD (baseline)

Route (local nginx, Host: vps.incomexsaigoncorp.vn) PRE
GET / 200 (degraded homepage — pre-existing 0-CMS-block fallback; still 200)
GET /reports 200
GET /api/health 200
GET /api/registry/counts 200
GET /api/registry/health 200
GET /api/discovery/relations 400 (endpoint's own no-param baseline)
GET /reports/report-balo-table 200
GET /api/balo/reconcile 404 (POST-only)
POST /api/balo/reconcile 403 (fail-closed, no session)

BALO_ONE_CLICK_GUARD (/opt/incomex/dot/bin/dot-balo-one-click-guard, read-only): PASS checks=37BALO_GUARD_PRE_TOTAL=37, BALO_GUARD_PRE_PASS=37, BALO_GUARD_PRE_FAIL=0. (Count measured, not hardcoded.)


8. CHECKPOINT & ONE-ROLLBACK CEILING

/root/nuxt-img-h1-checkpoint-20260721T150753Z/ (dir 700 root:root; all files 600): docker-compose.yml.PRE (sha256 557a2d65…), env.PRE (66752c8b…), nuxt-inspect.PRE/POST.json, nuxt-normalized.PRE/POST.txt, nuxt-env-hmac.PRE/POST.txt, containers-detail.PRE.txt, routes.PRE/POST.txt, balo-guard.PRE/POST.txt, hmac.key, rollback.sh, compose-image-lines.PRE/POST.txt. Not in KB/Git/Drive.

rollback.sh restores the PRE compose+.env and recreates nuxt via --no-deps --pull never --force-recreate, registry/KB/GitHub/network-independent. MAXIMUM_ROLLBACK_ATTEMPTS = 1. ROLLBACK_ATTEMPTS = 0 (none needed). Checkpoint retained until C2B1-S3 / C2B1-R1 complete.


9. .env PIN (Surface A)

/opt/incomex/docker/.env had no NUXT_SSR_IMAGE (added). New line 59:

NUXT_SSR_IMAGE=asia-southeast1-docker.pkg.dev/github-chatgpt-ggcloud/web-test/nuxt-ssr@sha256:72715a92885c…fab30

File mode preserved root:root 600; sha256 66752c8b…c8d384ce… (size 2231→2482). ENV_PIN = EXACT_REPO_DIGEST.


10. COMPOSE FALLBACK PIN (Surface B) + effective resolution + cross-service no-drift

/opt/incomex/docker/docker-compose.yml:162:

- image: ${NUXT_SSR_IMAGE:-asia-…/nuxt-ssr:latest}
+ image: ${NUXT_SSR_IMAGE:-asia-…/nuxt-ssr@sha256:72715a92885c…fab30}

Mode preserved root:root 644; sha256 557a2d65…43dec381… (size 9512→9577). No nuxt-ssr:latest remains.

Effective resolution both ways → the exact digest:

  • Path 1 (normal, .env sets the var): docker compose config…nuxt-ssr@sha256:72715a…
  • Path 2 (fallback only, --env-file /dev/null): docker compose config…nuxt-ssr@sha256:72715a…

COMPOSE_FALLBACK_PIN = EXACT_REPO_DIGEST; EFFECTIVE_NUXT_IMAGE = EXACT_REPO_DIGEST; NUXT_IMAGE_FALLBACK_STATUS = PINNED.

Cross-service no-drift — the image: lines of every other service are byte-identical PRE→POST (only line 162 changed): postgres:16 · qdrant/qdrant:latest · directus/directus:11.5 · agent-data-vps-prod:9acd60503290-20260717 · nginx:alpine. FAIL_NUXT_IMG_H1_CROSS_SERVICE_CONFIG_DRIFT not triggered.


11. EXPECTED 503 ALERT WINDOW (printed immediately before recreate)

EXPECTED_ALERT_WINDOW_START_UTC = 2026-07-21T15:51:53Z
EXPECTED_ALERT_WINDOW_END_UTC   = 2026-07-21T15:53:53Z
EXPECTED_ALERT                  = [OPS Proxy] Down 503 followed by Up
CAUSE                           = planned Nuxt-only recreate (immutable digest pin, --pull never)

Uptime Kuma / notifications were not disabled. The new container was healthy by 15:53:37Z (inside the window).


12. NO-PULL RECREATE RESULT (Gate §12–§13)

Pre-proof: digest exists local (id 72715a…); --pull never/--no-deps/--force-recreate supported (Docker 29.2.1, Compose v5.0.2); dry-run plan = only incomex-nuxt Recreate (no pull, no build, no other service); nuxt has no build: section; locks free; checkpoint valid.

Exact command (nothing added): docker compose up -d --no-deps --pull never --force-recreate nuxt

Container incomex-nuxt Recreate → Recreated → Starting → Started
PRE_CID  42079c55bf9c…   →   POST_CID  68fd38378828…   (CID changed exactly once)
POST_IMAGE_ID     = sha256:72715a92885c…fab30                  == C0 OCI index digest
POST_CONFIG_IMAGE = …/nuxt-ssr@sha256:72715a…fab30            (container now references the immutable digest)
POST_RESTARTS = 0 (fresh container) · STATUS running · HEALTH healthy

Orphan incomex-agent-api-executor preserved (no --remove-orphans). UNEXPECTED_REGISTRY_CONTACT = 0; no pull, no build. Docker healthy used only as a supplementary signal, never as primary evidence.


13. PRE/POST NORMALIZED DIFF (Gate §14)

docker inspect normalized fields, PRE vs POST — the only differences are the intended pin and benign bookkeeping:

Field PRE POST Verdict
Config.Image (IMAGE_REF) …nuxt-ssr:latest …nuxt-ssr@sha256:72715a… INTENDED (the pin)
.Image (IMAGE_ID) 72715a… 72715a… identical
Mounts (set) nuxt-output:rw + /run/incomex:ro same set (array reordered) identical set (cosmetic order)
Label com.docker.compose.config-hash 1e390b96… d1e184eb… intended (downstream of image-ref change)
Label com.docker.compose.depends_on directus:service_healthy:false "" benign artifact of the required --no-deps
Label com.docker.compose.image 72715a… 72715a… identical
entrypoint / cmd / user / workdir / restart / mem_limit(512m) / healthcheck / networks(+aliases) / ports / group_add(987) / logconfig all identical
Env var names + secret HMAC fingerprints (21 vars, same ephemeral key) 0 differences ⇒ every secret byte-identical (incl. NUXT_DIRECTUS_SERVICE_TOKEN)

NORMALIZED_CONFIG_DRIFT = 0 (functional). NUXT_CREDENTIAL_CHANGED = NO.


14. POST ROUTES & BALO GUARD (Gate §14–§15)

POST routes are identical to PRE (0 diff) for all 9 probes (/=200, /reports=200, /api/health=200, registry/counts=200, registry/health=200, discovery/relations=400, report-balo-table=200, balo GET=404, balo POST=403) ⇒ POST_ROUTE_RESULT >= PRE_ROUTE_RESULT; ROUTE_REGRESSION = 0.

BALO_ONE_CLICK_GUARD POST: PASS checks=37BALO_GUARD_POST_TOTAL=37 = PRE, BALO_GUARD_POST_PASS=37, BALO_GUARD_POST_FAIL=0. FAIL_NUXT_IMG_H1_BALO_REGRESSION not triggered.

The pre-existing homepage internal-SSR 403 (anonymous GET directus:8055/items/pages) is unchanged and masked by the homepage-simple fallback (public / = 200); it is not caused by H1 (predates it; NUXT-403-R1).


15. CRASH-FAST OBSERVATION (Gate §16)

POST_CRASH_FAST_DETECTOR — observed from recreate 15:51:55Z for ≥15 min (→ ~16:08Z):

cid = 68fd38378828…  (UNCHANGED since recreate) · restarts = 0 · status = running · health = healthy
started = 2026-07-21T15:51:55Z · heap_hits_since_recreate = 0 · window 15:51:55Z → 16:08:10Z (16m15s ≥ 15 min)

Verdict: POST_CRASH_FAST_DETECTOR = PASS — the container did not crash or restart, emitted no heap/OOM signature, and showed no immediate config error in the window. Meaning is bounded per §4.


16. OTHER-CONTAINER NO-MUTATION PROOF (Gate §13/§17)

All 10 non-nuxt containers — ids, StartedAt, RestartCount, status — identical PRE (15:11Z) vs immediately POST-recreate: agent-api-executor 1d841832 (r0) · agent-data 09188c72 (r0) · claude-kb b1b40494 (r2) · claude-mcp c7039e29 (r2) · directus 2245d86d (r0) · nginx e50172b5 (r0) · qdrant 2b5c328f (r0) · pg-restore-test 0cbc668c (r0) · postgres 22540b78 (r0) · uptime-kuma 81d9f483 (r0). OTHER_CONTAINER_MUTATION = 0. Container set = 11/11.


17. LONG-TERM STABILITY LIMITATION

LONG_TERM_NUXT_STABILITY_PROVEN = NO. The Nuxt container carries a pre-existing V8 JS-heap-OOM crash cycle (§3) driven by mem_limit 512m and heavy endpoints (e.g. the localeCompare error path on /api/registry/matrix), independent of the image pin. H1 changed the image reference only (same content, same 512m, same code mount); it neither introduced nor remediated that cycle. A future heap crash is expected and, per §4/§18, must be classified by signature+drift, not timing. Heap remediation is out of scope (no heap-remediation mission opened here).

Post-window crash interpretation: a crash after 16:08Z does not by itself prove H1 failure or a pre-existing defect; classify by crash signature vs the §3 heap signature, content/config drift, route behavior and timing.


18. GATE OPENED NEXT

PASS_NUXT_IMG_H1_IMMUTABLE_DUAL_PIN_NO_PULL_RECREATE_VERIFIED sets NUXT_IMAGE_RECREATE_READY = YES and opens C2B1-S1-DESIGN only. It does not open a repoint, credential migration, service-identity provisioning, or rotation. C2B1_ROTATION stays HOLD. NUXT_CREDENTIAL_CHANGED = NO.

Carried backlog (unchanged by H1): 4 LOCAL_ONLY_UNPROVEN images (agent-data / agent-api-executor / claude-kb / claude-mcp) — the prune/rmi prohibition still protects the whole group; X5 single-recipient recovery-key dependency (DB+Nuxt share 31799F…1837BA) — second independent recipient still PRIORITY RAISED; :latest mutable tag on qdrant; homepage 0-CMS-block degradation (NUXT-403-R1). Rollback checkpoint retained.

— End —

Back to Knowledge Hub knowledge/current-state/reports/nuxt-img-h1-immutable-dual-pin-no-pull-recreate-2026-07-21.md