fix(examples/03-immich): pin valid postgres image tag#34
Merged
Conversation
The canonical Immich walkthrough referenced
`Image=ghcr.io/immich-app/postgres:16`, which does not exist on
ghcr.io/immich-app/postgres. `core-ops apply --source-repo
examples/03-immich --host example` failed end-to-end on a clean
host with:
Error: unable to copy from source docker://ghcr.io/immich-app/postgres:16:
initializing source docker://ghcr.io/immich-app/postgres:16:
reading manifest 16 in ghcr.io/immich-app/postgres: manifest unknown
Pin to `16-vectorchord0.3.0-pgvector0.8.0-pgvectors0.2.0` — a real,
fully-qualified tag that exists on the registry and matches the
unit's `Description=Immich Postgres + pgvecto.rs database` (the
chosen tag includes the pgvecto.rs / pgvectors extension). This is
the closest 16-major build to Immich's officially recommended
14-line in their compose template.
The bump to `2.2.1` is required: modifications under `examples/`
trigger `unclassified_path_releasable_default` (patch). The
provenance-state fixture is bumped in lock-step so
`controller_version_provenance_matches_cargo_package_version`
remains green.
Discovered while exercising the canonical walkthrough for spec/018
(adoption-readiness); spec/018's recording task was blocked on
this end-to-end failure.
Verification:
$ cargo test 472 passed
$ cargo clippy --all-targets -- -D warnings clean
$ cargo run --bin core-ops-release -- validate --base-ref master passed (patch)
$ ssh core@<uat> 'sudo podman manifest inspect ghcr.io/immich-app/postgres:16-vectorchord0.3.0-pgvector0.8.0-pgvectors0.2.0' resolves
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
outergod
added a commit
that referenced
this pull request
May 7, 2026
PR #34 (`fix(examples/03-immich): pin valid postgres image tag`) landed on master and the post-merge promote tagged v2.2.1. Spec/018's prior bump (2.2.0 -> 2.2.1) is now subsumed by master's tip; rebasing onto post-promote master collapses 018's Cargo.toml change to a no-op relative to the new base, so `core-ops-release validate` reports `Required bump: patch | Version bump: none`. Bump 2.2.1 -> 2.2.2 and propagate to the provenance-state fixture (`controller_version_provenance_matches_cargo_package_version` pins `CARGO_PKG_VERSION`). The packaged_readme_surface bump rule still applies via the FR-017 carve-out documented in decision_018-packaged-readme-surface-cargo-bump. Verification: $ cargo test 472 passed $ cargo run --bin core-ops-release -- validate --base-ref master passed (patch) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
outergod
added a commit
that referenced
this pull request
May 7, 2026
…n blocks T011 (initial plan capture) and T012 (apply + idempotent re-run capture) were exercised end-to-end against `core-ops-uat` (Fedora CoreOS guest on ulthar libvirt). Apply succeeded after PR #34's postgres image-tag fix landed and after the operator-prereq podman secret `immich-db-password` was created on the host (per `examples/03-immich/README.md`). T013 replaces the `## What using CoreOps feels like` placeholder with two verbatim fenced code blocks per FR-006: - Plan output for `core-ops plan --source-repo examples/03-immich --host example` on a clean host: header + `[+] Create • 10` + `immich-database.container` entry with its `requires` graph and diff fragment + `...` elision + summary footer. - Idempotent re-run after `core-ops apply` converged the host: `[·] Unchanged • 10` + three representative service entries + `...` + `10 unchanged` summary. Every non-elided line appears byte-for-byte in actual core-ops output; `...` lines elide repeats and uninteresting content per FR-006. Combined non-blank line count: 25 (SC-007b ~25 budget). Five recognizable Quadlet unit identifiers from `examples/03-immich/services/` appear in the walkthrough (immich-database, immich-server, traefik-edge, immich-internal, immich-db-data) — well above SC-007's "≥ 1" threshold. Notes from exercising the example: - The `(stateless) (recovery from failed initial apply)` annotation in the second block reflects spec/017's stateless mode semantics: in stateless mode core-ops keeps no state under `/var/lib/`, so any plan invoked after a successful apply on the same host inspects raw host state and tags the run as "recovery from failed initial apply" because there is no persisted apply record. The annotation is truthful for stateless mode and is preserved verbatim. - The host overlay drop-in `examples/03-immich/hosts/example/immich-ml/quadlet/immich-ml.container.d/20-gpu.conf` was removed locally on the recording host (no `/dev/dri`); this is the documented host-shape-mismatch workaround from `docs/onboarding-script.sh`. README total: 295 lines (≤ 400). Verification: $ cargo test 472 passed $ cargo run --bin core-ops-release -- validate --base-ref master passed (patch) $ wc -l README.md 295 (≤ 400) PASS SC-001 $ awk '/^## What using CoreOps feels like/,/^## Real-world examples/' README.md | grep -c '^```text' 2 PASS FR-006 $ <walkthrough section>: 5 distinct unit identifiers from examples/03-immich/services/ PASS SC-007 T014 (record asciinema cast) and T016 (link cast from README) carry through the recording-host work that follows. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
7 tasks
outergod
added a commit
that referenced
this pull request
May 7, 2026
) `examples/03-immich/services/immich-server/quadlet/immich-server.container` declared `DB_USERNAME=immich` and `DB_HOSTNAME=immich-database` but did not mount the `immich-db-password` Podman secret nor point the server at the password file. The server fell back to whatever default Immich's image assumes, which does not match the random password the sibling `immich-database.container` initialises Postgres with via: Secret=immich-db-password,target=/run/secrets/immich-db-password Environment=POSTGRES_PASSWORD_FILE=/run/secrets/immich-db-password End-to-end on a clean host this manifested as: PostgresError: password authentication failed for user "immich" code: '28P01' file: 'auth.c' routine: 'auth_failed' with `immich-server.service` cycling on `Restart=always` indefinitely even after `core-ops apply` reported `Outcome: converged`. The canonical Immich walkthrough (FR-005 of spec/018) thus presented an all-green plan/apply/re-plan triple while the application was fundamentally non-functional — directly undercutting spec/018's "increase adoption likelihood" goal. Add to `immich-server.container`: Environment=DB_PASSWORD_FILE=/run/secrets/immich-db-password Secret=immich-db-password,target=/run/secrets/immich-db-password Immich supports the `_FILE` env-var convention for secrets natively (per Immich documentation), matching the same Podman secret as the database, so a single operator-provided secret feeds both ends. Discovered while exercising the canonical walkthrough for spec/018 session 3; the canonical example never ran end-to-end on a clean host since spec/017 shipped it. Same shape as PR #34 (postgres image-tag pin). Patch bump (`unclassified_path_releasable_default` fires for `examples/`); provenance-state fixture pinned in lock-step. Verification on `core-ops-uat` (Fedora CoreOS guest): $ podman secret create immich-db-password <(openssl rand -hex 16) $ sudo core-ops apply --source-repo examples/03-immich --host example Outcome: converged $ systemctl is-active immich-server active $ systemctl show immich-server -p NRestarts NRestarts=1 $ journalctl -u immich-server | grep "Microservices is running" Immich Microservices is running [v2.7.5] [production] $ core-ops plan --source-repo examples/03-immich --host example Summary: 10 unchanged $ cargo test 472 passed $ cargo clippy --all-targets -- -D warnings clean $ cargo run --bin core-ops-release -- validate --base-ref master passed (patch) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
outergod
added a commit
that referenced
this pull request
May 7, 2026
…n blocks T011 (initial plan capture) and T012 (apply + idempotent re-run capture) were exercised end-to-end against `core-ops-uat` (Fedora CoreOS guest on ulthar libvirt). Apply succeeded after PR #34's postgres image-tag fix landed and after the operator-prereq podman secret `immich-db-password` was created on the host (per `examples/03-immich/README.md`). T013 replaces the `## What using CoreOps feels like` placeholder with two verbatim fenced code blocks per FR-006: - Plan output for `core-ops plan --source-repo examples/03-immich --host example` on a clean host: header + `[+] Create • 10` + `immich-database.container` entry with its `requires` graph and diff fragment + `...` elision + summary footer. - Idempotent re-run after `core-ops apply` converged the host: `[·] Unchanged • 10` + three representative service entries + `...` + `10 unchanged` summary. Every non-elided line appears byte-for-byte in actual core-ops output; `...` lines elide repeats and uninteresting content per FR-006. Combined non-blank line count: 25 (SC-007b ~25 budget). Five recognizable Quadlet unit identifiers from `examples/03-immich/services/` appear in the walkthrough (immich-database, immich-server, traefik-edge, immich-internal, immich-db-data) — well above SC-007's "≥ 1" threshold. Notes from exercising the example: - The `(stateless) (recovery from failed initial apply)` annotation in the second block reflects spec/017's stateless mode semantics: in stateless mode core-ops keeps no state under `/var/lib/`, so any plan invoked after a successful apply on the same host inspects raw host state and tags the run as "recovery from failed initial apply" because there is no persisted apply record. The annotation is truthful for stateless mode and is preserved verbatim. - The host overlay drop-in `examples/03-immich/hosts/example/immich-ml/quadlet/immich-ml.container.d/20-gpu.conf` was removed locally on the recording host (no `/dev/dri`); this is the documented host-shape-mismatch workaround from `docs/onboarding-script.sh`. README total: 295 lines (≤ 400). Verification: $ cargo test 472 passed $ cargo run --bin core-ops-release -- validate --base-ref master passed (patch) $ wc -l README.md 295 (≤ 400) PASS SC-001 $ awk '/^## What using CoreOps feels like/,/^## Real-world examples/' README.md | grep -c '^```text' 2 PASS FR-006 $ <walkthrough section>: 5 distinct unit identifiers from examples/03-immich/services/ PASS SC-007 T014 (record asciinema cast) and T016 (link cast from README) carry through the recording-host work that follows. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
outergod
added a commit
that referenced
this pull request
May 7, 2026
Rebases onto post-promote master at v2.2.3 (which now contains both spec/018-blocking fixes: PR #35 wired the immich-db-password Podman secret into immich-server.container, and PR #36 introduced ApplyRunDisplayState::Stateless so stateless re-plans no longer flag a healthy host as "(recovery from failed initial apply)"). Re-recording on `core-ops-uat` (Fedora CoreOS guest) against the fixed binary + fixed example produces the truthful narrative the spec/018 walkthrough is supposed to demonstrate: Beat 1 (plan): Plan for host example @ (stateless) (first run) Beat 2 (apply): Apply for host example @ (stateless) (first run) Outcome: converged Beat 3 (replan): Plan for host example @ (stateless) 10 unchanged immich-server reaches `active running` (NRestarts=1) and journal shows "Immich Microservices is running [v2.7.5] [production]" — no auth restart loop. The misleading "(recovery from failed initial apply)" suffix is gone. Cast post-processed to strip OSC 3008 sequences (pam_systemd hostname/machineid leak under sudo) and replace nix-store-path SHELL env value, per decision_018-recording-ssh-delegation. Cast duration 4.80 s, ≤ 90 s SC-005a budget. T013 README walkthrough block updated to keep verbatim fidelity to the new T012 capture: the second fenced block's header and underline now match the corrected output (no "(recovery)" suffix; underline length adjusted from 72 chars to 35 chars to match the shorter title). Block count = 2 (FR-006), combined non-blank lines = 25 (SC-007b), unique unit identifiers = 5 (SC-007). The rebase dropped the prior `chore(release): bump 2.2.1 -> 2.2.2` commit (subsumed by master's PR #34/#35/#36 promotes); spec/018 now bumps `2.2.3 -> 2.2.4` per `packaged_readme_surface` carve-out. Verification: $ cargo test 473 passed $ cargo clippy --all-targets -- -D warnings clean $ cargo run --bin core-ops-release -- validate --base-ref master passed (patch) $ wc -l README.md 297 (≤ 400) PASS SC-001 $ <walkthrough section>: 2 fenced blocks, 25 non-blank lines, 5 unit IDs PASS FR-006/SC-007/SC-007b $ head -n 1 docs/onboarding.cast | jq '.version' 2 PASS SC-005 $ head -n 1 docs/onboarding.cast | jq '.duration' 4.80 (≤ 90) PASS SC-005a $ grep -iE '(not\.one|ulthar|192\.168\.|10\.0\.|172\.16\.)' docs/onboarding.cast docs/onboarding-script.sh (no matches) PASS SC-006a $ grep -c '3008' docs/onboarding.cast 0 PASS FR-009a $ asciinema play docs/onboarding.cast plays end-to-end PASS Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
outergod
added a commit
that referenced
this pull request
May 7, 2026
… playback Operator reported staircase distortion in the rendered cast/GIF: each successive line started indented by the length of the previous line, the classic "LF without CR" pattern. Root cause: the inner recording script piped SSH output through `| sed 's/\r$//'`, which stripped every `\r` that the remote PTY's ONLCR translation had added. The captured cast events therefore contained bare `\n` (line feed only). asciinema 2.4.0 plays casts through a raw-mode local TTY (so it can faithfully replay any escape sequences without double-translation), which means LF stays LF on output — the terminal treats each LF as "down one row" with no column reset, hence the staircase. The original sed filter was probably defensive — line-buffered tools sometimes choke on trailing `\r` — but for asciinema-capture the CRs are essential. Drop the filter and re-record. After re-recording the plan-output event has 124 CRs and 124 LFs (matched), confirming `\r\n` line terminators throughout. `asciinema cat docs/onboarding.cast` now renders left-aligned. The GIF rendered from this cast (`docs/assets/core-ops-demo.gif`, 107 KB GIF89a) displays the correct terminal layout inline on GitHub. Cast re-recorded against the same fixed stack (PRs #34/#35/#36 merged): Beat 1 (plan): Plan for host example @ (stateless) (first run) Beat 2 (apply): Apply for host example @ (stateless) (first run) Outcome: converged Beat 3 (replan): Plan for host example @ (stateless) 10 unchanged Same OSC 3008 strip + SHELL sanitize post-processing applied (per decision_018-recording-ssh-delegation). Duration 4.88 s, well under SC-005a's 90 s budget. Verification: $ head -n 1 docs/onboarding.cast | jq '.version' 2 $ head -n 1 docs/onboarding.cast | jq '.duration' 4.88012 (≤ 90) $ grep -c '3008' docs/onboarding.cast 0 $ grep -iE '(not\.one|ulthar|192\.168\.|10\.0\.|172\.16\.)' docs/onboarding.cast docs/onboarding-script.sh (no matches) $ asciinema cat docs/onboarding.cast | head -3 left-aligned, no staircase $ wc -c < docs/assets/core-ops-demo.gif 106777 (≤ 1 MB) The non-tracked recording driver `/tmp/onboarding-inner.sh` carried the buggy sed pipe in this session; the canonical `docs/onboarding-script.sh` does NOT have a sed filter (its inner `demo()` runs commands directly via `eval`, not through SSH delegation). The bug therefore lives only in the SSH-delegation recording procedure documented in decision_018-recording-ssh-delegation, not in the canonical regeneration script. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
outergod
added a commit
that referenced
this pull request
May 7, 2026
…n blocks T011 (initial plan capture) and T012 (apply + idempotent re-run capture) were exercised end-to-end against `core-ops-uat` (Fedora CoreOS guest on ulthar libvirt). Apply succeeded after PR #34's postgres image-tag fix landed and after the operator-prereq podman secret `immich-db-password` was created on the host (per `examples/03-immich/README.md`). T013 replaces the `## What using CoreOps feels like` placeholder with two verbatim fenced code blocks per FR-006: - Plan output for `core-ops plan --source-repo examples/03-immich --host example` on a clean host: header + `[+] Create • 10` + `immich-database.container` entry with its `requires` graph and diff fragment + `...` elision + summary footer. - Idempotent re-run after `core-ops apply` converged the host: `[·] Unchanged • 10` + three representative service entries + `...` + `10 unchanged` summary. Every non-elided line appears byte-for-byte in actual core-ops output; `...` lines elide repeats and uninteresting content per FR-006. Combined non-blank line count: 25 (SC-007b ~25 budget). Five recognizable Quadlet unit identifiers from `examples/03-immich/services/` appear in the walkthrough (immich-database, immich-server, traefik-edge, immich-internal, immich-db-data) — well above SC-007's "≥ 1" threshold. Notes from exercising the example: - The `(stateless) (recovery from failed initial apply)` annotation in the second block reflects spec/017's stateless mode semantics: in stateless mode core-ops keeps no state under `/var/lib/`, so any plan invoked after a successful apply on the same host inspects raw host state and tags the run as "recovery from failed initial apply" because there is no persisted apply record. The annotation is truthful for stateless mode and is preserved verbatim. - The host overlay drop-in `examples/03-immich/hosts/example/immich-ml/quadlet/immich-ml.container.d/20-gpu.conf` was removed locally on the recording host (no `/dev/dri`); this is the documented host-shape-mismatch workaround from `docs/onboarding-script.sh`. README total: 295 lines (≤ 400). Verification: $ cargo test 472 passed $ cargo run --bin core-ops-release -- validate --base-ref master passed (patch) $ wc -l README.md 295 (≤ 400) PASS SC-001 $ awk '/^## What using CoreOps feels like/,/^## Real-world examples/' README.md | grep -c '^```text' 2 PASS FR-006 $ <walkthrough section>: 5 distinct unit identifiers from examples/03-immich/services/ PASS SC-007 T014 (record asciinema cast) and T016 (link cast from README) carry through the recording-host work that follows. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
outergod
added a commit
that referenced
this pull request
May 7, 2026
Rebases onto post-promote master at v2.2.3 (which now contains both spec/018-blocking fixes: PR #35 wired the immich-db-password Podman secret into immich-server.container, and PR #36 introduced ApplyRunDisplayState::Stateless so stateless re-plans no longer flag a healthy host as "(recovery from failed initial apply)"). Re-recording on `core-ops-uat` (Fedora CoreOS guest) against the fixed binary + fixed example produces the truthful narrative the spec/018 walkthrough is supposed to demonstrate: Beat 1 (plan): Plan for host example @ (stateless) (first run) Beat 2 (apply): Apply for host example @ (stateless) (first run) Outcome: converged Beat 3 (replan): Plan for host example @ (stateless) 10 unchanged immich-server reaches `active running` (NRestarts=1) and journal shows "Immich Microservices is running [v2.7.5] [production]" — no auth restart loop. The misleading "(recovery from failed initial apply)" suffix is gone. Cast post-processed to strip OSC 3008 sequences (pam_systemd hostname/machineid leak under sudo) and replace nix-store-path SHELL env value, per decision_018-recording-ssh-delegation. Cast duration 4.80 s, ≤ 90 s SC-005a budget. T013 README walkthrough block updated to keep verbatim fidelity to the new T012 capture: the second fenced block's header and underline now match the corrected output (no "(recovery)" suffix; underline length adjusted from 72 chars to 35 chars to match the shorter title). Block count = 2 (FR-006), combined non-blank lines = 25 (SC-007b), unique unit identifiers = 5 (SC-007). The rebase dropped the prior `chore(release): bump 2.2.1 -> 2.2.2` commit (subsumed by master's PR #34/#35/#36 promotes); spec/018 now bumps `2.2.3 -> 2.2.4` per `packaged_readme_surface` carve-out. Verification: $ cargo test 473 passed $ cargo clippy --all-targets -- -D warnings clean $ cargo run --bin core-ops-release -- validate --base-ref master passed (patch) $ wc -l README.md 297 (≤ 400) PASS SC-001 $ <walkthrough section>: 2 fenced blocks, 25 non-blank lines, 5 unit IDs PASS FR-006/SC-007/SC-007b $ head -n 1 docs/onboarding.cast | jq '.version' 2 PASS SC-005 $ head -n 1 docs/onboarding.cast | jq '.duration' 4.80 (≤ 90) PASS SC-005a $ grep -iE '(not\.one|ulthar|192\.168\.|10\.0\.|172\.16\.)' docs/onboarding.cast docs/onboarding-script.sh (no matches) PASS SC-006a $ grep -c '3008' docs/onboarding.cast 0 PASS FR-009a $ asciinema play docs/onboarding.cast plays end-to-end PASS Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
outergod
added a commit
that referenced
this pull request
May 7, 2026
… playback Operator reported staircase distortion in the rendered cast/GIF: each successive line started indented by the length of the previous line, the classic "LF without CR" pattern. Root cause: the inner recording script piped SSH output through `| sed 's/\r$//'`, which stripped every `\r` that the remote PTY's ONLCR translation had added. The captured cast events therefore contained bare `\n` (line feed only). asciinema 2.4.0 plays casts through a raw-mode local TTY (so it can faithfully replay any escape sequences without double-translation), which means LF stays LF on output — the terminal treats each LF as "down one row" with no column reset, hence the staircase. The original sed filter was probably defensive — line-buffered tools sometimes choke on trailing `\r` — but for asciinema-capture the CRs are essential. Drop the filter and re-record. After re-recording the plan-output event has 124 CRs and 124 LFs (matched), confirming `\r\n` line terminators throughout. `asciinema cat docs/onboarding.cast` now renders left-aligned. The GIF rendered from this cast (`docs/assets/core-ops-demo.gif`, 107 KB GIF89a) displays the correct terminal layout inline on GitHub. Cast re-recorded against the same fixed stack (PRs #34/#35/#36 merged): Beat 1 (plan): Plan for host example @ (stateless) (first run) Beat 2 (apply): Apply for host example @ (stateless) (first run) Outcome: converged Beat 3 (replan): Plan for host example @ (stateless) 10 unchanged Same OSC 3008 strip + SHELL sanitize post-processing applied (per decision_018-recording-ssh-delegation). Duration 4.88 s, well under SC-005a's 90 s budget. Verification: $ head -n 1 docs/onboarding.cast | jq '.version' 2 $ head -n 1 docs/onboarding.cast | jq '.duration' 4.88012 (≤ 90) $ grep -c '3008' docs/onboarding.cast 0 $ grep -iE '(not\.one|ulthar|192\.168\.|10\.0\.|172\.16\.)' docs/onboarding.cast docs/onboarding-script.sh (no matches) $ asciinema cat docs/onboarding.cast | head -3 left-aligned, no staircase $ wc -c < docs/assets/core-ops-demo.gif 106777 (≤ 1 MB) The non-tracked recording driver `/tmp/onboarding-inner.sh` carried the buggy sed pipe in this session; the canonical `docs/onboarding-script.sh` does NOT have a sed filter (its inner `demo()` runs commands directly via `eval`, not through SSH delegation). The bug therefore lives only in the SSH-delegation recording procedure documented in decision_018-recording-ssh-delegation, not in the canonical regeneration script. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
examples/03-immich/services/immich-database/quadlet/immich-database.containerreferencedImage=ghcr.io/immich-app/postgres:16, a tag that does not exist on the registry.core-ops apply --source-repo examples/03-immich --host example) failed end-to-end on a clean host: `Error: ... reading manifest 16 in ghcr.io/immich-app/postgres: manifest unknown`.Discovered while exercising the canonical walkthrough for spec/018 (adoption-readiness); spec/018's recording task is blocked on this end-to-end failure.
Test plan
🤖 Generated with Claude Code