Copy CHANGELOG.md into the release image builder stage #8

Merged
graham merged 3 commits from fix/release-image-changelog-copy into main 2026-09-05 13:41:39 -05:00
Owner

Production has been serving main-1788188084-14d3204 since 2026-08-31 — the commit before the changelog page. That is why the path calculator still shows a blocked profile with no over-the-horizon rays, common-volume marker, or explanatory note: those commits (74b79a4e, 3bca4272, 9ffa7dec) were pushed but never produced an image.

Every build.yaml run since has failed in the Build and Push step:

== Compilation error in file lib/microwaveprop/changelog.ex ==
** (File.Error) could not read file "CHANGELOG.md": no such file or directory

Microwaveprop.Changelog and MicrowavepropWeb.Router read CHANGELOG.md from a module body, but the builder stage only copied algo.md before RUN mix compile. CI's own verify/test steps tar the whole checkout into the CI image, so they stay green — only the release Dockerfile compiles from a curated subset.

Changes:

  • Dockerfile: COPY CHANGELOG.md CHANGELOG.md alongside algo.md.
  • test/microwaveprop/release_image_test.exs: fails when a compile-time @external_resource is not copied into the builder stage before mix compile. Verified red without the Dockerfile line, green with it.
  • CHANGELOG.md: the entry 3bca4272 never wrote for the blocked-path over-the-horizon view.

Verification: MIX_ENV=prod mix compile --warnings-as-errors passes locally; make format/deps/credo/xref pass; make test fails only the 12 Wgrib2Test cases that need the wgrib2 binary (absent locally, present in the CI/base image). A local image build could not be used as proof — the base images are amd64-only and the BEAM crashes under qemu on arm64.

After merge the image build needs to succeed and the deployment rolled onto the new tag before the end user sees any of it.

Production has been serving `main-1788188084-14d3204` since 2026-08-31 — the commit *before* the changelog page. That is why the path calculator still shows a blocked profile with no over-the-horizon rays, common-volume marker, or explanatory note: those commits (`74b79a4e`, `3bca4272`, `9ffa7dec`) were pushed but never produced an image. Every `build.yaml` run since has failed in the **Build and Push** step: ``` == Compilation error in file lib/microwaveprop/changelog.ex == ** (File.Error) could not read file "CHANGELOG.md": no such file or directory ``` `Microwaveprop.Changelog` and `MicrowavepropWeb.Router` read `CHANGELOG.md` from a module body, but the builder stage only copied `algo.md` before `RUN mix compile`. CI's own verify/test steps tar the whole checkout into the CI image, so they stay green — only the release Dockerfile compiles from a curated subset. Changes: - `Dockerfile`: `COPY CHANGELOG.md CHANGELOG.md` alongside `algo.md`. - `test/microwaveprop/release_image_test.exs`: fails when a compile-time `@external_resource` is not copied into the builder stage before `mix compile`. Verified red without the Dockerfile line, green with it. - `CHANGELOG.md`: the entry `3bca4272` never wrote for the blocked-path over-the-horizon view. Verification: `MIX_ENV=prod mix compile --warnings-as-errors` passes locally; `make format/deps/credo/xref` pass; `make test` fails only the 12 `Wgrib2Test` cases that need the `wgrib2` binary (absent locally, present in the CI/base image). A local image build could not be used as proof — the base images are amd64-only and the BEAM crashes under qemu on arm64. After merge the image build needs to succeed and the deployment rolled onto the new tag before the end user sees any of it.
Copy CHANGELOG.md into the release image builder stage
Some checks failed
skippy-bot/review Skippy review: 1 blocking finding open — see the PR thread
55265bbb1c
Microwaveprop.Changelog and the router both read CHANGELOG.md from a module
body, but the Dockerfile only copied algo.md before `mix compile`. Every
image build since the changelog page landed has died with

    == Compilation error in file lib/microwaveprop/changelog.ex ==
    ** (File.Error) could not read file "CHANGELOG.md"

so production has been pinned to the commit before it and never picked up
the changelog page, the path calculator's over-the-horizon view, or the
hook hardening.

CI cannot see this: the verify and test steps tar the whole checkout into
the CI image, while only the release Dockerfile compiles from a curated
subset. The new test keeps that subset in sync with the compile-time
@external_resource list, and the missing changelog entry for the blocked
path view is added.
skippy-bot left a comment

🤖 Skippy PR review

1 finding — none blocking.

Severity Location Issue
🟡 Warning test/microwaveprop/release_image_test.exs:36 Guard only sees literal-string @external_resource — 3 of 7 resources escape it

Reviewed 55265bbb1c4d. Comment skippy review to re-run.

### 🤖 Skippy PR review **1 finding** — none blocking. | Severity | Location | Issue | | --- | --- | --- | | 🟡 Warning | `test/microwaveprop/release_image_test.exs:36` | Guard only sees literal-string @external_resource — 3 of 7 resources escape it | <sub>Reviewed `55265bbb1c4d`. Comment `skippy review` to re-run.</sub> <!-- skippy-pr-review -->
@ -0,0 +33,4 @@
"lib/**/*.ex"
|> Path.wildcard()
|> Enum.flat_map(fn file ->
~r/@external_resource\s+"([^"]+)"/
First-time contributor

🟡 Warning — Guard only sees literal-string @external_resource — 3 of 7 resources escape it

The regex ~r/@external_resource\s+"([^"]+)"/ only matches attributes whose value is a quoted literal. Three compile-time resources in this repo are declared indirectly and silently escape the guard: @external_resource @denylist_path (lib/microwaveprop/accounts/password_policy.ex:42), @external_resource @source (lib/microwaveprop/propagation/validation_report.ex:21), and @external_resource path (lib/microwaveprop_web/controllers/agent_skills_controller.ex:29, two files). So deleting COPY priv/security priv/security or COPY priv/agent-skills priv/agent-skills from the Dockerfile — the exact class of regression that froze prod — still passes this test green. Pin the directory COPY lines too (or assert each of priv/security, priv/agent-skills, docs/api, docs/algo-reports appears before RUN mix compile), not just the two root files.

**🟡 Warning — Guard only sees literal-string @external_resource — 3 of 7 resources escape it** The regex `~r/@external_resource\s+"([^"]+)"/` only matches attributes whose value is a quoted literal. Three compile-time resources in this repo are declared indirectly and silently escape the guard: `@external_resource @denylist_path` (lib/microwaveprop/accounts/password_policy.ex:42), `@external_resource @source` (lib/microwaveprop/propagation/validation_report.ex:21), and `@external_resource path` (lib/microwaveprop_web/controllers/agent_skills_controller.ex:29, two files). So deleting `COPY priv/security priv/security` or `COPY priv/agent-skills priv/agent-skills` from the Dockerfile — the exact class of regression that froze prod — still passes this test green. Pin the directory COPY lines too (or assert each of `priv/security`, `priv/agent-skills`, `docs/api`, `docs/algo-reports` appears before `RUN mix compile`), not just the two root files.
skippy-bot marked this conversation as resolved
Merge branch 'main' into fix/release-image-changelog-copy
Some checks failed
skippy-bot/review Skippy review: 1 blocking finding open — see the PR thread
b10f80b1b5
Harden release image test against indirect @external_resource
All checks were successful
skippy-bot/review Skippy review: clean — 1 non-blocking note open
345be9d87e
The literal-string regex missed 3 of 7 declaration sites
(PasswordPolicy, ValidationReport, AgentSkillsController), so removing
COPY priv/security, docs/algo-reports, or priv/agent-skills from the
Dockerfile passed the guard green while the image build would have
died on mix compile.

Detect non-literal declarations and require each such module to be
pinned in @indirect_resource_dirs to the build-context dirs it reads;
new indirect declarations fail the test until pinned. Verified by
mutating all three COPY lines plus an unpinned declaration - each
fails with a message naming the module.
graham merged commit 5fd0d64df4 into main 2026-09-05 13:41:39 -05:00
graham deleted branch fix/release-image-changelog-copy 2026-09-05 13:41:39 -05:00
skippy-bot left a comment

🤖 Skippy PR review

1 finding — none blocking.

Severity Location Issue
🔵 Suggestion test/microwaveprop/release_image_test.exs:33 Suggestion — regexes scan heredoc prose, not just declarations

Resolved 1 earlier finding (indirect @external_resource escape) — fixed in 345be9d; 0 still open.

Reviewed 345be9d87eef. Comment skippy review to re-run.

### 🤖 Skippy PR review **1 finding** — none blocking. | Severity | Location | Issue | | --- | --- | --- | | 🔵 Suggestion | `test/microwaveprop/release_image_test.exs:33` | Suggestion — regexes scan heredoc prose, not just declarations | Resolved 1 earlier finding (indirect @external_resource escape) — fixed in 345be9d; 0 still open. <sub>Reviewed `345be9d87eef`. Comment `skippy review` to re-run.</sub> <!-- skippy-pr-review -->
@ -0,0 +30,4 @@
@literal_resource ~r/@external_resource\s+"([^"]+)"/
# Non-literal argument: attribute reference, variable, comprehension binding.
@indirect_resource ~r/@external_resource\s+(?!")[^#\s]/
First-time contributor

🔵 Suggestion — Suggestion — regexes scan heredoc prose, not just declarations

The line-based scans strip # comments but not """ heredocs, so a lib @moduledoc/@doc that documents this pattern trips the detectors: prose like "declare it as @external_resource @path" flags the file as indirect (demanding a bogus pin), and a docstring example @external_resource "priv/x" demands a bogus COPY. The old literal scan had the same heredoc blindness; the new @indirect_resource detector widens it. Since you now parse line-by-line anyway, track heredoc state (skip lines between """ delimiters) before running either regex — cheap and makes the tripwire only fire on real declarations.

**🔵 Suggestion — Suggestion — regexes scan heredoc prose, not just declarations** The line-based scans strip `#` comments but not `"""` heredocs, so a lib `@moduledoc`/`@doc` that documents this pattern trips the detectors: prose like "declare it as `@external_resource @path`" flags the file as indirect (demanding a bogus pin), and a docstring example `@external_resource "priv/x"` demands a bogus COPY. The old literal scan had the same heredoc blindness; the new `@indirect_resource` detector widens it. Since you now parse line-by-line anyway, track heredoc state (skip lines between `"""` delimiters) before running either regex — cheap and makes the tripwire only fire on real declarations.
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
graham/prop!8
No description provided.