Skip to content

ci(framework-build-base): tag every published base digest immutably, so a GHCR untagged cleanup or a rebuild: true never removes the digest cloud pins #31756

Closed

Description

@objectstack-fleet

repo:cloud#2 · os-warren · session_01JQwxbWTyRFQwYN1hZXbCHL

Filing gate: ② defect with a named place, the publish half of objectstack-ai/cloud#3934, which can only be fixed here. Cloud PR objectstack-ai/cloud#3964 makes the loss loud on cloud's side (daily red card); only the publisher can make the digest durable. Dedupe: no open objectstack issue names framework-build-base, an immutable tag or untagged cleanup. Related: #31441 (published the base, closed).

Problem

Cloud pins the framework build base image by digest in .objectstack-base-image (ghcr.io/objectstack-ai/framework-build-base:PIN@sha256:DIGEST, cloud PR #3898). Two ordinary events make that digest unservable:

  • a GHCR retention or cleanup policy deleting untagged versions (a republish moves the PIN tag, leaving the old digest untagged), or
  • republishing the same pin with rebuild: true, which moves the PIN tag to a new digest.

Cloud then falls back to the ~20 min source build for every staging, production, EE-verify and migration image (EE verify measured 24m09s from source vs 5m25s from the base, cloud PR #3898).

Fix (proposal from the cloud#3934 thread)

  • File: .github/workflows/framework-build-base.yml
  • Change: (1, recommended) Tag every published digest immutably. The digest is only known after the push, so add a step right after 'Build and push' (if: steps.existing.outputs.skip != 'true', env DIGEST from the build step's digest output, SHA from steps.sha.outputs.sha): docker buildx imagetools create --tag "${IMAGE}:${SHA}-${DIGEST:7:12}" "${IMAGE}@${DIGEST}". Every digest then carries a tag of its own: a GHCR cleanup of untagged versions never takes it, and a rebuild: true that moves the SHA tag leaves the old digest served under SHA-DIGEST12, so cloud's committed .objectstack-base-image keeps resolving. Never delete or re-point a SHA-DIGEST12 tag (say so in the header next to 'A tag is written once'). (2, complementary) Refuse rebuild: true over a published pin unless a new boolean input replace_published is also true: in 'Is this commit already packaged', when DIGEST is non-empty, REBUILD is true and replace_published is not, print ::error naming the existing digest and the downstream step (cloud scripts/framework-base-image.sh write, then commit .objectstack-base-image) and exit 1.
  • Why: cloud#3964 only makes the loss loud (daily red card within a day). The card's fix item 1 is to make the pinned digest durable, and only the publisher can do that: the tag is moved by the publish workflow, and untagged versions are what a GHCR retention policy deletes. (1) removes both causes at once without changing what the SHA tag means; (2) stops the deliberate case from silently moving the tag. With (1) in place cloud needs no change: it pins by digest, and the extra tag only keeps that digest alive.

Done when

A published digest stays served under a tag of its own after a rebuild: true of the same pin and after an untagged-versions cleanup, and the workflow header says a SHA-DIGEST12 tag is never deleted or re-pointed.

Governed (.github/workflows/**): contract review + human approval. Not urgent while cloud has no .objectstack-base-image committed (cloud main today): the risk starts the day the first base pin lands.

Activity

  1. added
    pm:queueReady for the PM dispatch loop
    domain:cloudTouches cloud: the whole card is the cloud PM's (repo:cloud seat); no objectstack lane claims it
    on Nov 9, 2026
  2. objectstack-fleet commented on Nov 9, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: lane domain:cloud → domain:devx · p3 → p2 · 2026-11-09T02:57Z

    ⛔ Not a claim.


    Generated by Claude Code

  3. added
    domain:devxDomain lane — seat card indexed by label:pm:seat
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    domain:cloudTouches cloud: the whole card is the cloud PM's (repo:cloud seat); no objectstack lane claims it
    pm:queueReady for the PM dispatch loop
    on Nov 9, 2026
  4. objectstack-fleet commented on Nov 9, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:devx#2 · zhuangjianguo · session_01QsR2jTo666XXx9e1Gm21C6

    Claim: PM loop round 10 · 2026-11-09T03:20Z
    Session: session_01QsR2jSrcejjpWHd8D1fpmZ (card thread, dev) · seat session_01QsR2jTo666XXx9e1Gm21C6
    Account: zhuangjianguo
    Branch: claude/project-thread-g9042w
    Worktree: objectstack-issue-31756
    Domain: domain:devx
    Seat: domain:devx#2
    File surface: .github/workflows/framework-build-base.yml (an immutable SHA-DIGEST12 tag after Build and push, a backfill of that tag on the skip path, a replace_published refusal of rebuild: true over a published pin, and the header and summary), plus the line-number pin framework-build-base.yml:32 in scripts/pm/dispatch-gates.self-test.mjs if it moves. Tier H by path, hand-merged. ⛔ No docker/framework-build-base/**, no other workflow, no GHCR settings, no cloud-repo change, no registry write from the dev session.
    Container & model: S, mode:thread, model: default tier (no path-derived mandate), effort: high
    Clause-②: no
    Metadata-protocol: none
    Responsibility: framework build base publish | a republish or an untagged-version cleanup leaves cloud's pinned digest unservable, so every cloud image falls back to the ~20 min source build | cloud staging, production, EE-verify and migration images
    Thread-read: 6731150162
    Premise: holds. On main, publish tags only $IMAGE:$SHA, rebuild: true re-points that tag, and no other tag holds an older digest.
    Serial constraints cleared: no open PR touches framework-build-base.yml.

    Scope: one PR. The PR says Fixes #31756. Brief: /mnt/project-files/issue-31756/brief.md. Summons: triage 6731150162 (lane domain:devx, p2).

  5. objectstack-fleet commented on Nov 9, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:devx#2 · zhuangjianguo · session_01QsR2jSrcejjpWHd8D1fpmZ
    os-dev-report

    {
      "issue": 31756,
      "status": "done",
      "branch": "claude/project-thread-g9042w",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/31784",
      "head": "dd539bd5",
      "session": "session_01QsR2jSrcejjpWHd8D1fpmZ",
      "premise_still_valid": true,
      "summary": "framework-build-base.yml publish job: every pushed digest gets its own tag IMAGE:SHA-DIGEST12 (imagetools create --prefer-index=false, read back, refused unless it holds exactly that digest; digest shape refused unless sha256 plus 64 lowercase hex). The digest already published under SHA gets the same tag whenever it is requested (skip path backfill, and before the build on a replace). rebuild: true over a published tag is refused unless the new input replace_published is true; repository_dispatch is unaffected. Header and Summary updated; dispatch-gates self-test census row re-anchored 32 to 44.",
      "files_changed": [".github/workflows/framework-build-base.yml", "scripts/pm/dispatch-gates.self-test.mjs"],
      "line_budget": "120 changed lines (+112 / -8) vs the 3000 human-merge threshold: under",
      "tests": "No test file (the publish path cannot run from a PR). Local proof at dd539bd5: the workflow's own run: bodies extracted verbatim from the YAML, run against a fake docker keeping a registry in a text file; the two tag steps asserted byte-identical. Six branches: fresh exit 0 (SHA and SHA-aaaaaaaaaaaa both hold D1); backfill exit 0 twice (second run no create); refuse exit 1 (registry unchanged); replace exit 0 (old digest tagged before the build, new digest tagged after); shape exit 1 (nothing written); repoint exit 1 (registry unchanged). Red on main c2bb879f: main's existing step with REBUILD=true over a published tag exits 0 with skip=false. Mutation: --prefer-index=false removed from a temp copy (anchor count 2 to 0) makes the fresh case exit 1 on the read-back. Logs and harness in project files issue-31756.",
      "gates": "dispatch-gates --commands at dd539bd5: 46 derived, all 46 exit 0 (check:pm-dispatch-gates 2205 fast-tier cases, 316.5 s). --ran: 46 derived, 46 run, 0 NOT-MEASURED, 0 UNRUN. check-self-test-wired --self-test was red on the first refusal text (named cloud's script as a scripts/ path, read as a missing in-tree invocation), green after the respelling. Body gates on the PR body: check-changeset-no-major exit 0, check:adr-0087-registration exit 0, check-closing-keyword-parity exit 0, check-partof-closing-keyword exit 0. PR check-runs at report time: Check Changeset success; verify dry run, Lint, Type Check, Governed Surface Queue Guard in_progress.",
      "dogfood_battery": "n/a: no ! in the PR title and no (narrowing) arm",
      "deviations": [
        "imagetools create carries --prefer-index=false, which the brief's command lacked: buildx's default wraps a lone image manifest in a new index (buildx util/imagetools/create.go combine), so the tag would have held a different digest.",
        "The backfill step also runs on the replace_published path, before the build, so a deliberate replace never leaves the old digest untagged.",
        "The fresh-path tag step sits after Note the build wall, not directly after Build and push, so the wall reading stays the build.",
        "The no-check-families declaration's reason text (line 44, same file) now names the digest-tag writes, to stay true."
      ],
      "mcp_calls": "2: issue_read get and issue_read get_comments on card 31756; no MCP write",
      "api_writes": "1 so far: fleet-write pr_create (PR 31784); this report comment is the 2nd (post-stamped)",
      "open_questions": [],
      "security_findings": "none",
      "out_of_scope_findings": []
    }
  6. objectstack-fleet commented on Nov 9, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:devx#2 · zhuangjianguo · session_01QsR2jTo666XXx9e1Gm21C6

    PR #31784 is ACCEPTed and waits on the maintainer's hand merge (Tier H): head 65fc33f44b5095b6c27de9309e438f0bcc8b05a4, ACCEPT 6732432351, contract review PASS 6732418896 (the delta round of 6732088839). Every published digest of the framework build base now keeps a SHA-DIGEST12 tag of its own. That tag is backfilled for digests published before this change, rebuild: true over a published pin needs replace_published: true, and a failed registry read stops the job. After it lands, this seat asks the maintainer to approve one dispatch on a published pin, verifies the backfilled tag anonymously, and writes the landing note.

  7. objectstack-fleet commented on Nov 9, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:devx#2 · zhuangjianguo · session_01QsR2jTo666XXx9e1Gm21C6

    Landed · PR #31784 was merged by the maintainer (os-zhuang) through the merge queue as 38ce26ff9018fa2252c11ba71277a8859bef7bcb. It is Tier H. The seat posted ACCEPT 6732432351 on head 65fc33f4. The contract review passed in two rounds: 6732088839, with one should-fix, and the delta 6732418896. From this commit on, framework-build-base.yml holds every digest it publishes under a SHA-DIGEST12 tag of its own, and that tag is never deleted or re-pointed. A digest published before this change gets its tag backfilled the next time its pin is requested. rebuild: true over a published pin needs replace_published: true, and a failed registry read stops the job instead of reading as unpublished. Cloud needs no change: it already pins by digest.

    Owed by this seat, waiting on the maintainer's OK because it writes to the registry: one workflow_dispatch on an already-published pin, which should report the existing digest and backfill its SHA-DIGEST12 tag. Then an anonymous manifest read of that tag, which must answer the same digest. The result goes on this card.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    domain:devxDomain lane — seat card indexed by label:pm:seatpriority:p2Medium: important, M3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions