Skip to content

Make the release base-image rewriter and --verify recursive over nested service templates #390

Description

@swinney

Objective

Make scripts/dev/update_service_base_images.py rewrite and --verify every service
template the preflight declares, including nested ones, so that the guard currently blocking
nested service templates can be removed.

Context you need

service_templates() in src/cli/managers/base_image_preflight.py walks the template
directory recursively (PR #388, merged as fix/issue-383-nested-service-templates). Two
functions in the release rewriter still do not:

  • verify_base_tagsscripts/dev/update_service_base_images.py:322
  • update_base_tagsscripts/dev/update_service_base_images.py:382

Both iterate sorted(DOCKERFILES_DIR.glob("Dockerfile*")), which does not descend into
subdirectories. DOCKERFILES_DIR is defined at :13.

The consequence, if a nested service template existed: .github/workflows/test-and-build-tag.yml
would rewrite only the top-level templates and its three --verify invocations would all
pass, while the nested template still named whatever base reference it was committed with.
A release could ship one service built on a stale development base image with every check
green.

Why this is currently latent, and what is holding it. There are zero nested service
templates today — the only nested files, base-python-image/Dockerfile and
base-pytorch-image/Dockerfile, are excluded by NON_SERVICE_TEMPLATES. PR #388's review
closed the gap with a guard rather than the capability:

  • nested_service_templates() in src/cli/managers/base_image_preflight.py
  • test_no_service_template_is_nested_while_the_release_rewriter_is_top_level_only in
    tests/unit/test_base_image_preflight.py

That test fails the moment a nested service template appears, naming the two call sites
above. So nothing can ship broken — but nothing can be nested either. This issue is the
capability the guard is standing in for.

Why it was deferred. Raised twice by automated review on PR #388
(#388) during the 2026-08-29 nightly review. Making
release automation recursive is a change to the rewriter, its own test module, and two CI
workflows; it does not belong in a PR whose subject was the preflight's traversal.

Constraints

  • Branch from origin/dev. Open the PR with
    gh pr create --repo fasrc/archi --base dev. Never commit to dev directly.
  • origin is fasrc/archi. Do not target upstream (archi-physics/archi).
  • No Co-Authored-By and no session trailers on commits in this repo.
  • TDD: failing test first, watch it fail, then the minimum code, then the gate.
  • bash scripts/gate.sh must pass before every commit. Never --no-verify.
  • Do not touch deploy/fasrc-dev/**.
  • The rewriter is the only writer of service FROM lines. Do not hand-edit a template.
  • --verify must keep failing on a run that matched no reference at all — a check that
    reads nothing must not pass. See the --verify description in
    docs/docs/developer_guide.md.

Plan

One PR. The rewriter change and the guard removal must land together, or the guard blocks
the very thing the change enables.

  1. Write a failing test in tests/unit/test_update_service_base_images.py: a template
    directory with a nested sub/Dockerfile-svc pinned to an old tag, asserting
    update_base_tags rewrites it. Watch it fail.
  2. Write a failing test that verify_base_tags exits non-zero when that nested template
    names a reference other than the target, and zero when it matches.
  3. Replace both DOCKERFILES_DIR.glob("Dockerfile*") calls with the recursive traversal.
    Prefer importing service_templates() from the preflight over a second rglob, so the
    rewriter and the preflight cannot disagree about the set; if the import direction is
    wrong for a scripts/ tool, add a shared helper rather than duplicating the walk.
    Check whether the "checked" counter and the "matched no reference" failure path still
    behave when the set grows.
  4. Remove test_no_service_template_is_nested_while_the_release_rewriter_is_top_level_only
    and nested_service_templates(), and replace the guard with a test that a nested service
    template is rewritten and verified end to end. Removing the guard without that
    replacement is what this issue must not do.
  5. Update docs/docs/developer_guide.md, in "Pointing the service templates at a base
    image": the bullet stating the rewriter is not recursive, and the sentence naming the
    guard test, both become wrong.

Commands

git fetch origin && git checkout -b fix/issue-<N>-recursive-rewriter origin/dev

# the two non-recursive call sites
grep -n 'DOCKERFILES_DIR.glob' scripts/dev/update_service_base_images.py

# the guard this issue retires
python -m pytest tests/unit/test_base_image_preflight.py -q -k nested

python -m pytest tests/unit/test_update_service_base_images.py -q
python -m pytest tests/unit/test_base_image_preflight.py -q
python -m pytest tests/unit/test_python_version_declaration.py -q
bash scripts/gate.sh

Acceptance criteria

  • grep -n 'DOCKERFILES_DIR.glob' scripts/dev/update_service_base_images.py returns
    nothing.
  • A nested service template pinned to an old tag is rewritten by update_base_tags and
    reported by verify_base_tags when it disagrees with --tag.
  • --verify still exits non-zero on a run that matched no reference at all.
  • nested_service_templates() and its guard test are gone, replaced by a test that
    exercises a nested template through rewrite and verify.
  • bash scripts/gate.sh exits 0 with patch coverage at or above 80%.
  • docs/docs/developer_guide.md no longer claims the rewriter is non-recursive.
  • The PR targets fasrc/archi:dev and its body carries a Closes #<N> keyword — in the
    body, not the title, or the issue is not linked.

Start here

Run grep -n 'DOCKERFILES_DIR.glob' scripts/dev/update_service_base_images.py and read both
functions around those lines. Then write the step-1 test and watch it fail before changing
the rewriter.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3Priority: when possibleenhancementNew feature or requestneeds-humanBlocked: needs a human design decision

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions