ci: publish :latest + :c-<sha> on main, :dev on dev — nothing else (318 step 4)
Build images / sign-extension (push) Successful in 4s
CI / lint (push) Successful in 4s
CI / extension-version (push) Successful in 4s
Build images / build-agent (push) Successful in 6s
CI / frontend-build (push) Successful in 19s
extension / lint (push) Successful in 22s
CI / backend-lint-and-test (push) Successful in 30s
Build images / build-web (push) Successful in 1m57s
Build images / build-ml (push) Successful in 2m35s
CI / integration (push) Successful in 3m46s
Build images / sign-extension (push) Successful in 4s
CI / lint (push) Successful in 4s
CI / extension-version (push) Successful in 4s
Build images / build-agent (push) Successful in 6s
CI / frontend-build (push) Successful in 19s
extension / lint (push) Successful in 22s
CI / backend-lint-and-test (push) Successful in 30s
Build images / build-web (push) Successful in 1m57s
Build images / build-ml (push) Successful in 2m35s
CI / integration (push) Successful in 3m46s
The narrowing itself. Rule 145, narrowed 2026-08-28 once it was verified that
nothing pins: "a third name for the same thing is upkeep for a model we do
not run."
Gone:
:2026.8.28 the per-artifact date tag from milestone 313 step 3. It shipped,
it was verified on both branches, and its premise is gone. This
is a withdrawal, not a correction — nothing about it was wrong.
:main a second moving name for whatever :latest already pointed at,
justified by nothing but symmetry with :dev.
Kept, and note the inversion: :c-<sha> on main. Milestone 313 step 6 planned
to retire it on "haven't used, not important" — amended rule 145 makes it the
rollback unit and, with version tags gone, the only immutable identifier
left. #3139 is cancelled as superseded rather than quietly dropped.
No retention job is built. One already runs (#3157): a nightly Gitea
package_cleanup_rule with keep_count=10. Two consequences worth writing down.
Its keep_pattern protects any dotted-numeric tag, so the date tags this
commit stops minting would have been exempt from retention and accumulated
without bound — silently, in the direction of unbounded growth. That trap is
now dissolved rather than needing the pattern change #3139 planned. And
:c-<sha> IS prunable under that pattern, so the honest statement is that it
is the rollback unit for roughly the last 10 builds, not for every main
commit ever.
cmd_tag goes with its last consumer rather than being left as a dead
subcommand for a later step to notice. The report step logs version and
revision, which is the same information — the date is the first three fields
of the version.
Docs needed no change: every consumer already tracks a moving tag
(docker-compose.yml on :dev, agent/README.md on :latest), which is the
evidence the whole stance rests on.
This commit is contained in:
+47
-100
@@ -123,10 +123,9 @@ jobs:
|
||||
run: |
|
||||
set -u
|
||||
A=extension
|
||||
T=$(sh scripts/artifacts.sh tag "$A" 2>&1 || echo UNAVAILABLE)
|
||||
V=$(sh scripts/artifacts.sh version "$A" 2>&1 || echo UNAVAILABLE)
|
||||
R=$(sh scripts/artifacts.sh revision "$A" 2>&1 || echo UNAVAILABLE)
|
||||
echo "derived: artifact=$A tag=$T version=$V revision=$R sha=$GITHUB_SHA"
|
||||
echo "derived: artifact=$A version=$V revision=$R sha=$GITHUB_SHA"
|
||||
|
||||
- name: Guard — the derived version must never go backwards
|
||||
env:
|
||||
@@ -369,57 +368,44 @@ jobs:
|
||||
run: |
|
||||
set -u
|
||||
A=web
|
||||
T=$(sh scripts/artifacts.sh tag "$A" 2>&1 || echo UNAVAILABLE)
|
||||
V=$(sh scripts/artifacts.sh version "$A" 2>&1 || echo UNAVAILABLE)
|
||||
R=$(sh scripts/artifacts.sh revision "$A" 2>&1 || echo UNAVAILABLE)
|
||||
echo "derived: artifact=$A tag=$T version=$V revision=$R sha=$GITHUB_SHA"
|
||||
echo "derived: artifact=$A version=$V revision=$R sha=$GITHUB_SHA"
|
||||
|
||||
- name: Determine tag
|
||||
id: tag
|
||||
run: |
|
||||
# Two trigger shapes:
|
||||
# refs/heads/main → push to main: publish :main + :latest
|
||||
# (floating) AND :c-<short_sha> (immutable
|
||||
# per-commit rollback substrate, per family
|
||||
# release-posture rule "Tags are milestones,
|
||||
# not gates — commit-SHA images are the
|
||||
# rollback unit"). Rollback to any commit
|
||||
# becomes `docker pull …:c-<sha>` without a
|
||||
# release ceremony.
|
||||
# refs/heads/dev → push to dev: publish :dev, the rolling test
|
||||
# channel (family rule 146). Rolling means it may
|
||||
# carry newer contents than the :c-<sha> of the
|
||||
# same commit; it never writes :c-<sha> itself,
|
||||
# because that is the rollback unit (rule 145).
|
||||
# Two trigger shapes, and between them they publish three tags:
|
||||
# main → :latest (production, moving — rule 147: main IS production)
|
||||
# :c-<sha> (immutable, the rollback unit — rule 145)
|
||||
# dev → :dev (the rolling test channel — rule 146)
|
||||
#
|
||||
# That is the whole list. No :<version>, and no :main — rule 145,
|
||||
# narrowed 2026-08-28 once it was verified that nothing pins:
|
||||
# "a third name for the same thing is upkeep for a model we do not
|
||||
# run." The date tag published between milestone 313 step 3 and
|
||||
# milestone 318 was exactly that; :main was a second moving name for
|
||||
# whatever :latest already pointed at.
|
||||
#
|
||||
# `dev` gets no :c-<sha> deliberately. On a channel whose entire
|
||||
# contract is that it moves, a per-push immutable tag is a rollback
|
||||
# target nobody has ever pulled, accumulating forever. The accepted
|
||||
# cost: on dev there is no rollback but the previous :dev, which is
|
||||
# gone — recovery is revert-on-git plus a CI cycle.
|
||||
#
|
||||
# Reinstating :<version> is a real decision, not a default. It earns
|
||||
# its place when something genuinely pins: a second instance held on
|
||||
# a known-good build, or a deliberately frozen window. Tag at the
|
||||
# moment you decide to freeze; no back-catalogue is needed.
|
||||
#
|
||||
# POSIX-safe substring (the runner shell is dash/BusyBox sh, not
|
||||
# bash — `${var:0:7}` errors with "Bad substitution"; cut works
|
||||
# everywhere). Operator-flagged 2026-06-01 after first :c-<sha>
|
||||
# everywhere). Operator-flagged 2026-06-01 after the first :c-<sha>
|
||||
# main-push build failed at this step.
|
||||
SHORT_SHA=$(printf '%s' "$GITHUB_SHA" | cut -c1-7)
|
||||
# The pinnable tag (milestone 313 step 3): YYYY.M.D of the commit
|
||||
# THIS artifact's shipped files last changed in. Day precision is
|
||||
# deliberate — same-day work is not something worth pinning, so a
|
||||
# second main build the same day replaces the first rather than
|
||||
# accumulating a tag nobody would roll back to.
|
||||
#
|
||||
# Derived per artifact, so an image whose files did not change keeps
|
||||
# the tag it already had: the agent reads 2026.7.17 today while web
|
||||
# reads 2026.8.27 — and the reuse step below turns that into a
|
||||
# skipped build rather than a rebuild of bytes that already exist.
|
||||
# `channel` is baked into the image as FC_CHANNEL and reported by
|
||||
# /api/extension/manifest (milestone 271 step 7).
|
||||
# Mirrors build-web's tag list; see the comment there.
|
||||
if [ "${GITHUB_REF##*/}" = "main" ]; then
|
||||
CALVER=$(sh scripts/artifacts.sh tag web)
|
||||
# Guarded, and computed only on this path. There is no `set -e` in
|
||||
# this step, so a failed derivation would otherwise leave CALVER
|
||||
# empty and publish the tag `fabledcurator:` — an invalid
|
||||
# name, from a green step. An empty pin must never reach the
|
||||
# registry.
|
||||
if [ -z "$CALVER" ]; then
|
||||
echo "ERROR: could not derive a web version tag" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "tags=git.fabledsword.com/bvandeusen/fabledcurator:main,git.fabledsword.com/bvandeusen/fabledcurator:latest,git.fabledsword.com/bvandeusen/fabledcurator:c-${SHORT_SHA},git.fabledsword.com/bvandeusen/fabledcurator:${CALVER}" >> "$GITHUB_OUTPUT"
|
||||
echo "tags=git.fabledsword.com/bvandeusen/fabledcurator:latest,git.fabledsword.com/bvandeusen/fabledcurator:c-${SHORT_SHA}" >> "$GITHUB_OUTPUT"
|
||||
echo "channel=main" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "tags=git.fabledsword.com/bvandeusen/fabledcurator:dev" >> "$GITHUB_OUTPUT"
|
||||
@@ -646,9 +632,10 @@ jobs:
|
||||
for t in $TAGS; do ARGS="$ARGS -t $t"; done
|
||||
unset IFS
|
||||
# Source is the channel tag itself — the image we just confirmed
|
||||
# carries this revision. One of the targets is that same tag, which
|
||||
# makes its copy a no-op; the others (:main, :c-<sha>, the date pin)
|
||||
# are what this exists for.
|
||||
# carries this revision. On dev the only target IS that tag, so this
|
||||
# is a no-op that keeps the code path uniform. On main it is what
|
||||
# gives the new commit its :c-<sha>, which rule 145 requires of every
|
||||
# main push whether or not a build ran.
|
||||
# shellcheck disable=SC2086
|
||||
docker buildx imagetools create $ARGS "$SOURCE"
|
||||
echo "repointed to $SOURCE: $TAGS"
|
||||
@@ -691,44 +678,22 @@ jobs:
|
||||
run: |
|
||||
set -u
|
||||
A=ml
|
||||
T=$(sh scripts/artifacts.sh tag "$A" 2>&1 || echo UNAVAILABLE)
|
||||
V=$(sh scripts/artifacts.sh version "$A" 2>&1 || echo UNAVAILABLE)
|
||||
R=$(sh scripts/artifacts.sh revision "$A" 2>&1 || echo UNAVAILABLE)
|
||||
echo "derived: artifact=$A tag=$T version=$V revision=$R sha=$GITHUB_SHA"
|
||||
echo "derived: artifact=$A version=$V revision=$R sha=$GITHUB_SHA"
|
||||
|
||||
- name: Determine tag
|
||||
id: tag
|
||||
run: |
|
||||
# Mirrors build-web's two-shape logic (main-push / dev-push),
|
||||
# including the per-commit :c-<short_sha> tag on main — the rollback
|
||||
# unit per rule 145. The -ml image follows the same cadence as web.
|
||||
# Mirrors build-web's tag list; see the comment there.
|
||||
# POSIX-safe substring (the runner shell is dash/BusyBox sh, not
|
||||
# bash — `${var:0:7}` errors with "Bad substitution"; cut works
|
||||
# everywhere). Operator-flagged 2026-06-01 after first :c-<sha>
|
||||
# main-push build failed at this step.
|
||||
SHORT_SHA=$(printf '%s' "$GITHUB_SHA" | cut -c1-7)
|
||||
# The pinnable tag (milestone 313 step 3): YYYY.M.D of the commit
|
||||
# THIS artifact's shipped files last changed in. Day precision is
|
||||
# deliberate — same-day work is not something worth pinning, so a
|
||||
# second main build the same day replaces the first rather than
|
||||
# accumulating a tag nobody would roll back to.
|
||||
#
|
||||
# Derived per artifact, so an image whose files did not change keeps
|
||||
# the tag it already had: the agent reads 2026.7.17 today while web
|
||||
# reads 2026.8.27 — and the reuse step below turns that into a
|
||||
# skipped build rather than a rebuild of bytes that already exist.
|
||||
# Mirrors build-web's tag list; see the comment there.
|
||||
if [ "${GITHUB_REF##*/}" = "main" ]; then
|
||||
CALVER=$(sh scripts/artifacts.sh tag ml)
|
||||
# Guarded, and computed only on this path. There is no `set -e` in
|
||||
# this step, so a failed derivation would otherwise leave CALVER
|
||||
# empty and publish the tag `fabledcurator-ml:` — an invalid
|
||||
# name, from a green step. An empty pin must never reach the
|
||||
# registry.
|
||||
if [ -z "$CALVER" ]; then
|
||||
echo "ERROR: could not derive a ml version tag" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "tags=git.fabledsword.com/bvandeusen/fabledcurator-ml:main,git.fabledsword.com/bvandeusen/fabledcurator-ml:latest,git.fabledsword.com/bvandeusen/fabledcurator-ml:c-${SHORT_SHA},git.fabledsword.com/bvandeusen/fabledcurator-ml:${CALVER}" >> "$GITHUB_OUTPUT"
|
||||
echo "tags=git.fabledsword.com/bvandeusen/fabledcurator-ml:latest,git.fabledsword.com/bvandeusen/fabledcurator-ml:c-${SHORT_SHA}" >> "$GITHUB_OUTPUT"
|
||||
echo "channel=main" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "tags=git.fabledsword.com/bvandeusen/fabledcurator-ml:dev" >> "$GITHUB_OUTPUT"
|
||||
@@ -857,9 +822,10 @@ jobs:
|
||||
for t in $TAGS; do ARGS="$ARGS -t $t"; done
|
||||
unset IFS
|
||||
# Source is the channel tag itself — the image we just confirmed
|
||||
# carries this revision. One of the targets is that same tag, which
|
||||
# makes its copy a no-op; the others (:main, :c-<sha>, the date pin)
|
||||
# are what this exists for.
|
||||
# carries this revision. On dev the only target IS that tag, so this
|
||||
# is a no-op that keeps the code path uniform. On main it is what
|
||||
# gives the new commit its :c-<sha>, which rule 145 requires of every
|
||||
# main push whether or not a build ran.
|
||||
# shellcheck disable=SC2086
|
||||
docker buildx imagetools create $ARGS "$SOURCE"
|
||||
echo "repointed to $SOURCE: $TAGS"
|
||||
@@ -905,37 +871,17 @@ jobs:
|
||||
run: |
|
||||
set -u
|
||||
A=agent
|
||||
T=$(sh scripts/artifacts.sh tag "$A" 2>&1 || echo UNAVAILABLE)
|
||||
V=$(sh scripts/artifacts.sh version "$A" 2>&1 || echo UNAVAILABLE)
|
||||
R=$(sh scripts/artifacts.sh revision "$A" 2>&1 || echo UNAVAILABLE)
|
||||
echo "derived: artifact=$A tag=$T version=$V revision=$R sha=$GITHUB_SHA"
|
||||
echo "derived: artifact=$A version=$V revision=$R sha=$GITHUB_SHA"
|
||||
|
||||
- name: Determine tag
|
||||
id: tag
|
||||
run: |
|
||||
SHORT_SHA=$(printf '%s' "$GITHUB_SHA" | cut -c1-7)
|
||||
# The pinnable tag (milestone 313 step 3): YYYY.M.D of the commit
|
||||
# THIS artifact's shipped files last changed in. Day precision is
|
||||
# deliberate — same-day work is not something worth pinning, so a
|
||||
# second main build the same day replaces the first rather than
|
||||
# accumulating a tag nobody would roll back to.
|
||||
#
|
||||
# Derived per artifact, so an image whose files did not change keeps
|
||||
# the tag it already had: the agent reads 2026.7.17 today while web
|
||||
# reads 2026.8.27 — and the reuse step below turns that into a
|
||||
# skipped build rather than a rebuild of bytes that already exist.
|
||||
# Mirrors build-web's tag list; see the comment there.
|
||||
if [ "${GITHUB_REF##*/}" = "main" ]; then
|
||||
CALVER=$(sh scripts/artifacts.sh tag agent)
|
||||
# Guarded, and computed only on this path. There is no `set -e` in
|
||||
# this step, so a failed derivation would otherwise leave CALVER
|
||||
# empty and publish the tag `fabledcurator-agent:` — an invalid
|
||||
# name, from a green step. An empty pin must never reach the
|
||||
# registry.
|
||||
if [ -z "$CALVER" ]; then
|
||||
echo "ERROR: could not derive a agent version tag" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "tags=git.fabledsword.com/bvandeusen/fabledcurator-agent:main,git.fabledsword.com/bvandeusen/fabledcurator-agent:latest,git.fabledsword.com/bvandeusen/fabledcurator-agent:c-${SHORT_SHA},git.fabledsword.com/bvandeusen/fabledcurator-agent:${CALVER}" >> "$GITHUB_OUTPUT"
|
||||
echo "tags=git.fabledsword.com/bvandeusen/fabledcurator-agent:latest,git.fabledsword.com/bvandeusen/fabledcurator-agent:c-${SHORT_SHA}" >> "$GITHUB_OUTPUT"
|
||||
echo "channel=main" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "tags=git.fabledsword.com/bvandeusen/fabledcurator-agent:dev" >> "$GITHUB_OUTPUT"
|
||||
@@ -1064,9 +1010,10 @@ jobs:
|
||||
for t in $TAGS; do ARGS="$ARGS -t $t"; done
|
||||
unset IFS
|
||||
# Source is the channel tag itself — the image we just confirmed
|
||||
# carries this revision. One of the targets is that same tag, which
|
||||
# makes its copy a no-op; the others (:main, :c-<sha>, the date pin)
|
||||
# are what this exists for.
|
||||
# carries this revision. On dev the only target IS that tag, so this
|
||||
# is a no-op that keeps the code path uniform. On main it is what
|
||||
# gives the new commit its :c-<sha>, which rule 145 requires of every
|
||||
# main push whether or not a build ran.
|
||||
# shellcheck disable=SC2086
|
||||
docker buildx imagetools create $ARGS "$SOURCE"
|
||||
echo "repointed to $SOURCE: $TAGS"
|
||||
|
||||
+1
-15
@@ -58,7 +58,7 @@ AGENT_PATHS='agent/Dockerfile agent/requirements.txt agent/fc_agent'
|
||||
|
||||
|
||||
usage() {
|
||||
echo "usage: artifacts.sh {paths|revision|version|tag} {web|ml|agent|extension}" >&2
|
||||
echo "usage: artifacts.sh {paths|revision|version} {web|ml|agent|extension}" >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
@@ -137,24 +137,10 @@ cmd_version() {
|
||||
"$(strip0 "$(fmt "$sha" %H%M)")"
|
||||
}
|
||||
|
||||
# The PUBLISHED IMAGE TAG: day precision, YYYY.M.D. Deliberately coarser than
|
||||
# the ordering key, per the operator 2026-08-28 — same-day work is not
|
||||
# something worth pinning, so a second build the same day replaces the first
|
||||
# rather than accumulating a tag nobody would roll back to. Safe only because
|
||||
# skip decisions key on cmd_revision, never on this.
|
||||
cmd_tag() {
|
||||
sha=$(echo "$(newest "$1")" | cut -d' ' -f2)
|
||||
printf '%s.%s.%s\n' \
|
||||
"$(fmt "$sha" %Y)" \
|
||||
"$(strip0 "$(fmt "$sha" %m)")" \
|
||||
"$(strip0 "$(fmt "$sha" %d)")"
|
||||
}
|
||||
|
||||
[ $# -ge 2 ] || usage
|
||||
case "$1" in
|
||||
paths) cmd_paths "$2" ;;
|
||||
revision) cmd_revision "$2" ;;
|
||||
version) cmd_version "$2" ;;
|
||||
tag) cmd_tag "$2" ;;
|
||||
*) usage ;;
|
||||
esac
|
||||
|
||||
Reference in New Issue
Block a user