Skip to content

ci(board-snapshot): sync the snapshot directory so the R2 mirror matches the documented prefix - #18136

Merged
claude[bot] merged 1 commit into
mainfrom
claude/issue-18049-board-mirror-prefix
Sep 14, 2026
Merged

ci(board-snapshot): sync the snapshot directory so the R2 mirror matches the documented prefix#18136
claude[bot] merged 1 commit into
mainfrom
claude/issue-18049-board-mirror-prefix

Conversation

@claude

@claude claude Bot commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Fixes #18049

What this changes

One line in the R2 sync step of .github/workflows/board-snapshot.yml, plus the comment in that step that described the old source.

The step synced the archive worktree ROOT (aws s3 sync archive/ "$mirror") while the snapshot is written to archive/board (SNAPSHOT_ARGS: --out=archive/board, near line 237). With mirror="s3://${R2_BUCKET}/objectstack/board/", that maps archive/board/X onto objectstack/board/ + board/X, so the whole mirror sat one segment deeper than the mirror prefix on the line above it documents — the objectstack/board/board/... layout the card measured from the bucket side (10912 of 10913 objects, run 34765639736). Syncing archive/board/ maps archive/board/X to objectstack/board/X.

Migration: --delete does it, and the target is deliberately not widened

--delete is kept, and it is what migrates the bucket: the stale objectstack/board/board/ tree is inside the new sync target and has no local counterpart, so the first scheduled run after this merges removes it. No cleanup step is needed.

The target is NOT widened to objectstack/ to reach the same mapping — that spelling would put objectstack/snapshots/ inside a --delete target and eat the daily tarballs, which is the failure the step's own comment already warns about. The daily tarball step is untouched: it writes its own key from -C archive . and still carries ./board/..., self-describing.

No secret name or value is touched, no secret is added, and the workflow's permissions: block is unchanged.

The two .git excludes are dropped, and why

They are dead after this change, not merely redundant. archive/ is a linked worktree, so its .git is a FILE at archive/.git; aws-cli filter patterns are matched against each path relative to the SOURCE directory, and with the source now archive/board/ that file is outside the source entirely. Nothing under archive/board/ is or can be named .git — the snapshot script writes the board tree there.

Dropping them is behaviour-neutral. Keeping them would have left a live-looking guard whose accompanying comment describes a path this step no longer reads, inside the very step whose comment/code disagreement is what this card is about. The tarball step's own --exclude=.git still applies to -C archive . and is left as it is.

Acceptance, both directions

Measured in the worktree at 9dfcfb46a:

reading before after
grep -n 'aws s3 sync archive/board/' .github/workflows/board-snapshot.yml 0 hits (exit 1) 1 hit, line 364
grep -c 'aws s3 sync archive/ ' .github/workflows/board-snapshot.yml 1 0
literal control, grep -n 'mirror=' ... line 340 line 340, byte-unchanged
git diff --stat origin/main 1 file changed, 21 insertions, 4 deletions

A yaml.safe_load of the file parses; the job still has 9 steps and every if: guard is byte-unchanged. The set +e / code=$? / set -e shape around the sync is byte-identical, so the step's exit-code capture is untouched.

Verification the maintainer can run, once

The bucket cannot be re-measured from this container (no R2 credentials), so the post-change layout is NOT MEASURED here. After the next scheduled run (7 2,8,14,20 * * *), one ListObjectsV2 against prefix objectstack/board/ should show manifest.json, comments/, issues/ and reviews/ at the top level of that prefix and no board/ subtree under it, while objectstack/snapshots/ still holds the daily tarballs. That first run's deleted count in the step summary should be roughly the object count of the old tree.

Not reachable at PR time

The R2 step is guarded by if: github.event_name != 'pull_request', as are the archive checkout and the commit/push steps. A pull_request run therefore exercises the snapshot on a real runner (--out=board --dry-run --limit=5 --max-requests=25) and never touches the bucket. The changed line cannot be proven green by this PR's own CI; it is covered by the greps above and by the one-run verification described above.

Gates

Derived with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths — it takes its own change set from the merge base) and run in the foreground, each exit code captured before any pipe: 36 derived families, 35 exit 0, reconciled with --ran.

pnpm check:pm-dispatch-gates is NOT MEASURED here: its self-test suite streamed thousands of passing assertions and zero failures, but did not terminate inside a 520 s budget in this shared container. It runs in CI's required Lint & Repo Gates job.

Changeset

skip-changeset: no package's files[] ships .github/**, so this diff publishes nothing from any released package.

Acceptance notes

  • Noted, not filed: the prose block above the step still reads "this step mirrors the archive CHECKOUT". It stays accurate in substance, because board/ is the only entry the archive branch tree holds, but after this change the sync mirrors that subtree rather than the tree root. Left as written — rewriting it is prose polish, not a defect, and this card's surface is the sync step itself.

Generated by Claude Code


Generated by Claude Code

…hes the documented prefix

The R2 step synced the archive worktree ROOT while the snapshot is written
to `archive/board`, so every key landed at `objectstack/board/board/...` —
one segment deeper than the `mirror` prefix on the line above it documents,
and visible only from the bucket side. Sync `archive/board/` instead, which
maps `archive/board/X` to `objectstack/board/X`.

`--delete` is kept and is what migrates the bucket: the stale
`objectstack/board/board/` tree is inside the sync target and has no local
counterpart, so the first scheduled run after this change removes it. The
target is deliberately NOT widened to `objectstack/` to get the same
mapping — that would put `objectstack/snapshots/` inside a `--delete`
target and eat the daily tarballs.

The two `.git` excludes are dropped as dead: they matched `archive/.git`,
the linked worktree's gitdir FILE, which is outside the new source. The
daily tarball step, the four secrets and the workflow permissions are
untouched.

Claude-Session: https://claude.ai/code/session_01DAcomhvR9kKizeYgg89Vo8
Co-authored-by: Claude <noreply@anthropic.com>
@claude

claude Bot commented Sep 14, 2026

Copy link
Copy Markdown
Contributor Author
  • Served-tier: 2214/2214 CONTRACT_REVIEW_TIER — harness model stamp counted over this seat's own transcript (non-sidechain assistant messages a model served; <synthetic> harness notices excluded) at 2026-09-14T06:04Z and compared to the constant's value outside the repository; get_session external_metadata.last_served_model read equal to the constant at 2026-09-14T00:20Z.

Contract review

Head: 9dfcfb46 (PR #18136, card #18049) — reviewed at 2026-09-14T06:05Z by the skills seat at the contract-review tier. Path face clean (.github/workflows/board-snapshot.yml only — no governed path; SUBJECT the PM board mirror, routed here by triage) ⇒ in-seat landing: ready + auto-merge after this record.

① derived judgments (seat-measured on the fetched head 9dfcfb46 against merge-base a90a9f26, ⛔ not taken from the report):

  1. The change: the R2 sync step's source is now the snapshot directory — aws s3 sync archive/board/ "$mirror" (was archive/), so archive/board/X lands at objectstack/board/X instead of one segment deeper; --delete kept (it removes the stale objectstack/board/board/ tree on the first scheduled run — no local counterpart), the target NOT widened to objectstack/ (that would put objectstack/snapshots/ inside a --delete target); the two --exclude '.git' filters dropped as dead with the reason in the comment block (filters match relative to the source; the worktree's .git FILE at archive/.git is outside archive/board/); the tarball step's own --exclude=.git survives. +21/−4, one file.
  2. Shape: the YAML parses (seat yaml.safe_load, job archive intact); the sync step stays guarded by if: github.event_name != 'pull_request' (:317), so a PR run never reaches it and this PR's own board-snapshot run is the dry-run leg; no secret name or value, no permissions block touched.
  3. Risk profile unchanged: the source is still inside the persistent archive worktree (the board-archive checkout), so a failed snapshot leaves the previous run's content in place exactly as before; nothing reads the archive for state (the file's own one-board rule).
  4. Gates: 36 derived / 35 run / 1 UNRUN — check:pm-dispatch-gates streamed 1699 passing assertions and did not terminate inside the shared container (contention, reason recorded, not a verdict); CI's Lint & Repo Gates job runs it and is in progress on the head at 2026-09-14T06:04Z (18 success / 12 skipped / 1 in progress / 0 red) — auto-merge waits on it. Nine exit-3 prerequisites cleared by an install and re-run to 0.
  5. Scope held: one file; skip-changeset is the declaration; Clause-②: no holds — no contract path; --pair 18136 on origin/main's reader → exit 0 at 2026-09-14T06:04Z before this record; 3 REST writes, 0 MCP calls per the report.

② semver: unchanged — nothing published.

③ boundary flags: the post-change bucket layout is NOT MEASURED here (no R2 credentials in any seat container) — the maintainer's one-run verification, after the next scheduled run (cron 7 2,8,14,20 UTC): one ListObjectsV2 on prefix objectstack/board/ shows manifest.json, comments/, issues/, reviews/ at the top and no board/ subtree, while objectstack/snapshots/ still holds the daily tarballs. The prose block above the step still says it mirrors the archive CHECKOUT — polish for the next edit of that block, no card. The archive's coverage gap (nothing above #17726) is #18137, filed by this seat, unrelated to this fix.

Implemented-by: claude/issue-18049-board-mirror-prefix
Reviewed-by: session_01DAcomhvR9kKizeYgg89Vo8

Verdict: PASS — path face clean ⇒ this seat lands: ready through POST …/pulls/18136/ccr/ready_for_review with read-back, then PUT …/ccr/auto_merge; Fixes #18049 closes the card on landing.

维护者速读(终稿)

一行改动:R2 同步源从 archive/ 改为 archive/board/,桶里多出来的 board/board/ 一层会在下次定时运行时由 --delete 自动清掉;不放宽目标、不碰密钥、不动每日 tarball。本席直接落地。下次定时运行(08:07Z)之后你在桶里看一眼 objectstack/board/ 顶层有 manifest.json 即可。


Generated by Claude Code

@claude
claude Bot marked this pull request as ready for review September 14, 2026 06:05
@claude
claude Bot enabled auto-merge September 14, 2026 06:05
@claude
claude Bot added this pull request to the merge queue Sep 14, 2026
Merged via the queue into main with commit b48abc4 Sep 14, 2026
33 checks passed
@claude
claude Bot deleted the claude/issue-18049-board-mirror-prefix branch September 14, 2026 06:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cd size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

board-snapshot: the R2 mirror lands at objectstack/board/board/…, one segment deeper than the workflow documents

1 participant