Compare commits
26 Commits
phase-lvl5
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 369f4f486b | |||
| cba53b69a4 | |||
| f1500123e7 | |||
| cfda9e72db | |||
| 73889ed860 | |||
| 72b3d6c089 | |||
| e9745c8c74 | |||
| f88c6bc78d | |||
| 823023a19a | |||
| fc16250db2 | |||
| 8d5bf305e8 | |||
| 9ce987188a | |||
| 13cad1f985 | |||
| a521d43a17 | |||
| dc924c679b | |||
| 763f8d1a47 | |||
| 68c3486216 | |||
| 1fb70aafa6 | |||
| 29047a8dec | |||
| 08e6cc8273 | |||
| cfc87fd8d3 | |||
| 5ce813e910 | |||
| 40caaab8fb | |||
| 24baac559c | |||
| cd62743055 | |||
| 589943f46e |
18
BACKLOG-bsky.md
Normal file
18
BACKLOG-bsky.md
Normal file
@ -0,0 +1,18 @@
|
||||
# BACKLOG — phase bsky
|
||||
|
||||
## Build backlog
|
||||
|
||||
- [x] B1: Root-cause diagnosis — inspect recipe compose/entrypoint + actual `:0.4` image vs exact tags on cc-ci (2026-06-11)
|
||||
- [x] B2: Upstream research persisted to cc-ci-plan/upstream/bluesky-pds.md (plan repo f395247)
|
||||
- [x] B3: DECISIONS.md entry — pin choice (exact 0.4.219 over 0.5.1-main / digest pin), version label bump
|
||||
- [x] B4: Mirror PR branch `upgrade-0.3.0+v0.4.219` — compose.yml re-pin + label bump; open PR on recipe-maintainers/bluesky-pds
|
||||
- [x] B5: `!testme` on the PR → full lifecycle green (install/health, upgrade-path status justified, backup/restore, functional, L5 lint); record level under de-capped semantics + reconcile expected baseline
|
||||
- [x] B6: Screenshot on the green PR run — verify PNG real/representative/credential-free (Read it); SCREENSHOT hook only if needed
|
||||
- [x] B7: Claim M1 (root cause + green fix PR + screenshot verified)
|
||||
- [ ] B8: Close DEFERRED bluesky entries with pointers; JOURNAL note updating shot-phase N/A disposition
|
||||
- [ ] B9: Operator handoff summary in STATUS-bsky.md (what was wrong, what the PR changes, post-merge expectations incl. canonical/warm reseed)
|
||||
- [ ] B10: Claim M2
|
||||
|
||||
## Adversary findings
|
||||
|
||||
(Adversary-owned)
|
||||
105
BACKLOG-lvl5.md
105
BACKLOG-lvl5.md
@ -2,17 +2,98 @@
|
||||
|
||||
## Build backlog
|
||||
|
||||
- [ ] B1 (P1) `level.py`: append rung `lint` (L5); new status vocabulary {pass, fail, skip, unver}; `compute_level()` → new formula (level = max i: rung_i pass ∧ ∀j<i status ∈ {pass,skip}); DELETE cap_reason/capped concepts.
|
||||
- [ ] B2 (P1) lint executor (`harness/lint.py`): `abra recipe lint <recipe>` against the exact tested ref; hard ~60s timeout; rc+full output → `lint.txt` artifact; pass/fail/unver classification (missing abra / timeout / exception → unver, never pass, never skip); mirror-context handling per phase-plan §2.3 (probe abra behavior first; any filtering = named + unit-tested + DECISIONS.md).
|
||||
- [ ] B3 (P1) `results.py`: wire lint into `derive_rungs` + explicit intentional-vs-unintentional classification of EVERY N/A source; drop level_cap_reason/level_cap_rung from schema; `skips()` reflects new statuses; orchestrator (`run_recipe_ci.py`) runs lint executor at the tested-ref point + passes result through; verdict-neutral (R7 wrap).
|
||||
- [ ] B4 (P1) unit tests: rewrite test_level.py/test_results.py to new semantics incl. mission worked examples (fail-blocks → L1; intentional-skip climbs → L5; unver-blocks → L2; lint unver → L4; unclassifiable N/A → unver default); lint executor tests; old-artifact rendering compat tests.
|
||||
- [ ] B5 (P2) `card.py`: 0–5 color ramp; cap line removed ("level N of 5" neutral); rung table renders ✔/✘/intentional-skip/unverified; level_badge_svg loses cap_skip third segment (badge = number+color only); tolerate old artifacts.
|
||||
- [ ] B6 (P2) `dashboard.py`: _LEVEL_COLOR 5-scale; _level_pill/badge SVG number-only; legend text; old results.json (cap_reason present, lint absent) render without KeyError.
|
||||
- [ ] B7 (P2) docs: results-ux.md, testing.md, recipe-customization.md §EXPECTED_NA wording — L5 ladder, de-cap semantics.
|
||||
- [ ] B8 (P1) DECISIONS.md: semantics change record (replaces Phase-3 "N/A caps"); N/A classification table (every derive_rungs N/A source → intentional|unintentional); mirror-filter decision for lint (if any filtering).
|
||||
- [ ] B9 — gate M1: claim (branch w/ P1+P2; clean tree; cold-verifiable).
|
||||
- [ ] B10 (P3) lint sweep over ALL enrolled recipes (scratch clones — never touch ~/.abra/recipes during builds); matrix here (pass/fail + rule hits); mechanical fixes → mirror PRs (never push main/never merge); rest → DEFERRED.md.
|
||||
- [ ] B11 (P4) real-CI proofs: ≥1 genuine L5; ≥1 lint-blocked L4 (synth branch ok); ≥1 N/A-skip climb; 2× drone !testme; canary suite at re-derived designed levels; 1 synthesized unver-blocks run; before/after level table for ALL enrolled recipes; card/dashboard PNG/SVG visually verified.
|
||||
- [ ] B12 — gate M2: claim; then ## DONE after fresh PASS.
|
||||
- [x] B1 (P1) `level.py`: append rung `lint` (L5); new status vocabulary {pass, fail, skip, unver}; `compute_level()` → new formula (level = max i: rung_i pass ∧ ∀j<i status ∈ {pass,skip}); DELETE cap_reason/capped concepts.
|
||||
- [x] B2 (P1) lint executor (`harness/lint.py`): `abra recipe lint <recipe>` against the exact tested ref; hard ~60s timeout; rc+full output → `lint.txt` artifact; pass/fail/unver classification (missing abra / timeout / exception → unver, never pass, never skip); mirror-context handling per phase-plan §2.3 (probe abra behavior first; any filtering = named + unit-tested + DECISIONS.md).
|
||||
- [x] B3 (P1) `results.py`: wire lint into `derive_rungs` + explicit intentional-vs-unintentional classification of EVERY N/A source; drop level_cap_reason/level_cap_rung from schema; `skips()` reflects new statuses; orchestrator (`run_recipe_ci.py`) runs lint executor at the tested-ref point + passes result through; verdict-neutral (R7 wrap).
|
||||
- [x] B4 (P1) unit tests: rewrite test_level.py/test_results.py to new semantics incl. mission worked examples (fail-blocks → L1; intentional-skip climbs → L5; unver-blocks → L2; lint unver → L4; unclassifiable N/A → unver default); lint executor tests; old-artifact rendering compat tests.
|
||||
- [x] B5 (P2) `card.py`: 0–5 color ramp; cap line removed ("level N of 5" neutral); rung table renders ✔/✘/intentional-skip/unverified; level_badge_svg loses cap_skip third segment (badge = number+color only); tolerate old artifacts.
|
||||
- [x] B6 (P2) `dashboard.py`: _LEVEL_COLOR 5-scale; _level_pill/badge SVG number-only; legend text; old results.json (cap_reason present, lint absent) render without KeyError.
|
||||
- [x] B7 (P2) docs: results-ux.md, testing.md, recipe-customization.md §EXPECTED_NA wording — L5 ladder, de-cap semantics.
|
||||
- [x] B8 (P1) DECISIONS.md: semantics change record (replaces Phase-3 "N/A caps"); N/A classification table (every derive_rungs N/A source → intentional|unintentional); mirror-filter decision for lint (if any filtering).
|
||||
- [x] B9 — gate M1: claim (branch w/ P1+P2; clean tree; cold-verifiable).
|
||||
- [x] B10 (P3) lint sweep over ALL enrolled recipes (scratch clones — never touch ~/.abra/recipes during builds); matrix here (pass/fail + rule hits); mechanical fixes → mirror PRs (never push main/never merge); rest → DEFERRED.md.
|
||||
- [x] B11 (P4) real-CI proofs: ≥1 genuine L5; ≥1 lint-blocked L4 (synth branch ok); ≥1 N/A-skip climb; 2× drone !testme; canary suite at re-derived designed levels; 1 synthesized unver-blocks run; before/after level table for ALL enrolled recipes; card/dashboard PNG/SVG visually verified.
|
||||
- [x] B12 — gate M2: claim; then ## DONE after fresh PASS.
|
||||
|
||||
## Adversary findings
|
||||
|
||||
## P3 lint sweep matrix (B10) — all 19 enrolled, mirror main HEAD, 2026-06-11
|
||||
|
||||
Method: per recipe, fresh scratch clone of its canonical origin (mirror for the 17
|
||||
recipe-maintainers recipes; coopcloud upstream for bluesky-pds/custom-html-tiny/mumble) +
|
||||
upstream version tags fetched (production fetch_recipe shape), then `harness.lint.run_lint`
|
||||
from phase-lvl5 @ 3d8d286 in a scratch ABRA_DIR (`/tmp/lvl5-sweep` on cc-ci; full outputs in
|
||||
`/tmp/lvl5-sweep/art/<recipe>/lint.txt`). Canonical `~/.abra/recipes` never touched.
|
||||
|
||||
**Result: 19/19 PASS** (no error-severity rule unsatisfied anywhere). No recipe-mirror PRs and
|
||||
no DEFERRED entries needed. Warn-severity misses (informational, do not fail the rung):
|
||||
|
||||
| recipe | lint | warn-rule misses |
|
||||
|---|---|---|
|
||||
| bluesky-pds | pass | R002 R007 R015 |
|
||||
| cryptpad | pass | R002 R005 R007 |
|
||||
| custom-html | pass | R002 R004 R005 |
|
||||
| custom-html-tiny | pass | R002 |
|
||||
| discourse | pass | R002 R007 R015 |
|
||||
| ghost | pass | R015 |
|
||||
| hedgedoc | pass | R015 |
|
||||
| immich | pass | R002 R005 |
|
||||
| keycloak | pass | R002 R015 |
|
||||
| lasuite-docs | pass | R005 |
|
||||
| lasuite-drive | pass | R002 R005 |
|
||||
| lasuite-meet | pass | R002 |
|
||||
| mailu | pass | R002 |
|
||||
| matrix-synapse | pass | R002 R015 |
|
||||
| mattermost-lts | pass | R002 R015 |
|
||||
| mumble | pass | R002 |
|
||||
| n8n | pass | R002 R015 |
|
||||
| plausible | pass | R002 R005 R007 |
|
||||
| uptime-kuma | pass | R015 |
|
||||
|
||||
Note: lasuite-meet's historically-lightweight tag `0.3.0+v1.16.0` is now ANNOTATED upstream
|
||||
(verified `git cat-file -t` = tag on all three version tags) — R014 passes genuinely; the
|
||||
abra.py:105 lightweight-tag deploy fallback simply no longer triggers for it.
|
||||
|
||||
## Before/after level table skeleton (§2.9 — "after" to be filled by P4 real runs)
|
||||
|
||||
Baseline = latest results.json on cc-ci per recipe re-scored under the CURRENT (pre-lvl5,
|
||||
4-rung) rule; ancient 6-rung artifacts (builds ≤205, integration/recipe_local era) re-read on
|
||||
their four essential rungs. Predicted = same tier outcomes + sweep lint result under the new
|
||||
rule (assumption flagged; P4 produces the real values).
|
||||
|
||||
| recipe | baseline rungs (latest artifact) | baseline level | predicted new level | REAL new level (P4 run) | why it shifts |
|
||||
|---|---|---|---|---|---|
|
||||
| bluesky-pds | no artifact (deploy-gated upstream, shot-phase N/A) | — | — | — (still deploy-gated; documented N/A) | still deploy-gated |
|
||||
| cryptpad | I✔ U✔ B✔ F✔ (#181) | 4 | 5 | (not re-run; analytic 5) | + lint pass |
|
||||
| custom-html | I✔ U✔ B✔ F✔ (#182) | 4 | 5 | **4** (#405 PR4 lintdemo: lint fail R011; main analytic 5) | + lint pass |
|
||||
| custom-html-tiny | I✔ U✔ B-na F-na (#205, predates functional/) | 2 | 5 | **5** (#399 — N/A-skip climb, was 2) | de-cap: backup skip declared; functional/ tests exist now; + lint |
|
||||
| discourse | I✔ U✔ B✔ F✔ (#184) | 4 | 5 | (not re-run; analytic 5) | + lint pass |
|
||||
| ghost | I✔ U✔ B✔ F✔ (#185) | 4 | 5 | (not re-run; analytic 5) | + lint pass |
|
||||
| hedgedoc | I✔ U✔ B✔ F✔ (#113) | 4 | 5 | **5** (#398, 100s) | + lint pass |
|
||||
| immich | I✔ U✔ B✔ F✔ (#370) | 4 | 5 | **5** (#406, drone !testme PR2, 199s) | + lint pass |
|
||||
| keycloak | I✔ U✔ B✔ F✔ (#187) | 4 | 5 | (not re-run; analytic 5) | + lint pass |
|
||||
| lasuite-docs | I✔ U✔ B✔ F✔ (#188) | 4 | 5 | (not re-run; analytic 5) | + lint pass |
|
||||
| lasuite-drive | I✔ U✔ B✔ F✔ (#189) | 4 | 5 | (not re-run; analytic 5) | + lint pass |
|
||||
| lasuite-meet | I✔ U✔ B✔ F✔ (#204) | 4 | 5 | (not re-run; analytic 5) | + lint pass |
|
||||
| mailu | I✔ U✔ B-na F✔ (#191) | 2 | 5 | (not re-run; analytic 5 — same de-cap as #399) | de-cap: not backup-capable → skip climbs (the §2.9 N/A-skip demo) |
|
||||
| matrix-synapse | I✔ U✔ B✔ F✔ (#203) | 4 | 5 | (not re-run; analytic 5) | + lint pass |
|
||||
| mattermost-lts | I✔ U✔ B✔ F✔ (#196) | 4 | 5 | (not re-run; analytic 5) | + lint pass |
|
||||
| mumble | no results.json artifact retained | — | — | **5** (#413, 80s — first retained artifact) | P4 run to establish |
|
||||
| n8n | I✔ U✔ B✔ F✔ (#197) | 4 | 5 | (not re-run; analytic 5) | + lint pass |
|
||||
| plausible | I✔ U✔ B✔ F✔ (#371) | 4 | 5 | **5** (#407, drone !testme PR3, 164s) | + lint pass |
|
||||
| uptime-kuma | I✔ U✔ B✔ F✔ (#165) | 4 | 5 | (not re-run; analytic 5) | + lint pass |
|
||||
|
||||
Canaries (designed levels under the NEW formula, re-derived): custom-html-bkp-bad /
|
||||
custom-html-rst-bad — backup-capable with a failing backup/restore tier → backup_restore rung
|
||||
FAIL → level 2 (fail still blocks; run verdict red as today). To be proven in P4.
|
||||
|
||||
### Canary designed-level re-derivation (P4, runs 415/416 — 2026-06-11)
|
||||
|
||||
Under the NEW formula the bad canaries' designed level is **1**, not the old 2: their mirrors
|
||||
carry no published version tags on the SRC+REF path → upgrade = intentional skip (climbs past
|
||||
but never earns), backup_restore = FAIL blocks → level = install = 1. Verified live: 415
|
||||
(bkp-bad) + 416 (rst-bad) both **verdict FAILURE (red)**, rungs
|
||||
{install: pass, upgrade: skip, backup_restore: fail, functional: unver (post-failure abort),
|
||||
lint: pass}, LEVEL 1. Backup/restore fail still blocks; verdict logic untouched.
|
||||
(First attempts 411/412 failed in 1s: canaries are mirror-only, not catalogue recipes — they
|
||||
need SRC+REF params, as prior phases ran them.)
|
||||
|
||||
104
JOURNAL-bsky.md
Normal file
104
JOURNAL-bsky.md
Normal file
@ -0,0 +1,104 @@
|
||||
# JOURNAL — phase bsky
|
||||
|
||||
## 2026-06-11T11:31Z–11:55Z — bootstrap + root-cause diagnosis (B1, B2)
|
||||
|
||||
Phase start. Read plan-phase-bsky-fix.md + plan.md §6.1/§7/§9. Adversary seeded
|
||||
REVIEW-bsky.md (8d5bf30) with cold baseline recon — same suspects I confirmed below.
|
||||
|
||||
**Diagnosis chain (commands + outputs):**
|
||||
|
||||
1. Mirror clone (b2d86ef): `compose.yml` pins `image: ghcr.io/bluesky-social/pds:0.4`,
|
||||
overrides entrypoint (`dumb-init --` + config-mounted `/entrypoint.sh`);
|
||||
`entrypoint.sh.tmpl` ends `exec node --enable-source-maps index.js` — relative path,
|
||||
resolved against image WORKDIR.
|
||||
|
||||
2. Live image inspection on cc-ci:
|
||||
`docker image inspect ghcr.io/bluesky-social/pds:0.4 --format "{{.Id}} created={{.Created}} workdir={{.Config.WorkingDir}} ... cmd={{.Config.Cmd}}"`
|
||||
→ `sha256:007500681bbf… created=2026-05-30T05:05:11Z workdir=/app entrypoint=[dumb-init --] cmd=[node --enable-source-maps index.ts]`
|
||||
`docker run --rm --entrypoint sh ghcr.io/bluesky-social/pds:0.4 -c 'node --version; ls /app'`
|
||||
→ `v24.15.0` / `index.ts node_modules package.json pnpm-lock.yaml` — **no index.js**.
|
||||
`grep @atproto/pds /app/package.json` → `"@atproto/pds": "0.5.1"`; /usr/local/bin/goat present.
|
||||
So `:0.4` is now a main-branch 0.5.1 build → recipe's `index.js` exec = MODULE_NOT_FOUND.
|
||||
This precisely explains the rcust-era crash-loop evidence (Node v24.15.0 in traceback).
|
||||
|
||||
3. Upstream research:
|
||||
- ghcr tags/list (paginated): exact tags …0.4.158, 0.4.169, 0.4.182, 0.4.188, 0.4.193,
|
||||
0.4.204, 0.4.208, 0.4.219, plus anomalous 0.4.5001. `:0.4` digest `871194d2…` ==
|
||||
`latest`, ≠ `0.4.219` (`e0b756701c92…`) → :0.4 republished past the release line.
|
||||
- Dockerfile@v0.4.219: node:20.20-alpine3.23, WORKDIR /app, CMD index.js, dumb-init.
|
||||
- Dockerfile@main: node:24.15-alpine3.23, CMD index.ts, + goat binary — matches what
|
||||
`:0.4` now contains. GitHub `releases/latest` 404s (they only push git tags).
|
||||
- service/package.json@v0.4.219: `"@atproto/pds": "0.4.219"`.
|
||||
|
||||
4. Candidate-fix image verified on cc-ci:
|
||||
`docker run --rm --entrypoint sh ghcr.io/bluesky-social/pds:0.4.219 -c 'node --version; ls /app; grep @atproto/pds /app/package.json; which dumb-init'`
|
||||
→ `v20.20.2` / index.js present / `"@atproto/pds": "0.4.219"` / `/usr/bin/dumb-init`.
|
||||
Image CMD `[node --enable-source-maps index.js]` — identical to what the recipe's
|
||||
entrypoint execs, so the override stays valid.
|
||||
|
||||
**Why pin 0.4.219 and not chase 0.5.1 (rationale, summarized in DECISIONS.md):** 0.5.1
|
||||
exists only as the moving `:0.4`/`latest`/sha- tags — no exact release tag, built from
|
||||
main, and Co-op Cloud upgrade tooling works on tags. Re-pinning to the newest *released*
|
||||
exact tag is the minimal, justified fix; when upstream cuts real 0.5.x release tags the
|
||||
recipe can upgrade properly (entrypoint will then need `index.ts` + Node 24 — noted in
|
||||
upstream registry).
|
||||
|
||||
Bridge enrollment confirmed: bluesky-pds in POLL_REPOS (nix/modules/bridge.nix:43) →
|
||||
`!testme` works. Mirror has only closed PR#1 (skill smoke test); my fix → PR#2.
|
||||
|
||||
Next: DECISIONS entry (B3), mirror branch + PR (B4), !testme (B5).
|
||||
|
||||
## 2026-06-11T11:40Z–11:55Z — run 423 red: the upgrade-BASE trap (B5 first attempt)
|
||||
|
||||
PR #2 opened (branch upgrade-0.3.0+v0.4.219, head f7b6c8df, 2-line diff) and !testme'd
|
||||
(comment 14340) → drone build/run 423. RESULT: install=fail, level 0 — but NOT the PR:
|
||||
the run never deployed the PR head. The harness deploys ONCE at the upgrade BASE
|
||||
(`previous_version` = vers[-2] = 0.1.1+v0.4 — confirmed: run-423's recipe checkout sat at
|
||||
tag 0.1.1+v0.4) and only the upgrade tier chaos-redeploys the PR head. Both published tags
|
||||
(0.1.1+v0.4, 0.2.0+v0.4) pin the broken moving `:0.4` → the base crash-loops the SAME
|
||||
MODULE_NOT_FOUND (run-423 app log: Node v24.15.0, /app/index.js missing) → install fails
|
||||
before my fix is ever exercised. No published version can EVER deploy again (upstream
|
||||
republished the tag) — so the upgrade path is structurally unverifiable until a fixed
|
||||
version is published post-merge.
|
||||
|
||||
Fix (harness, evidence-backed, not a weakening): EXPECTED_NA["upgrade"] (the EXISTING
|
||||
declared-intentional-skip mechanism, de-capped levels phase lvl5) now also suppresses the
|
||||
base deploy — extracted `upgrade_base()` pure helper in run_recipe_ci.py; single deploy
|
||||
becomes the PR head; upgrade tier records "skip"; derive_rungs classifies it intentional
|
||||
with the declared reason (visible in results.json skips.intentional — never reported as a
|
||||
pass). tests/bluesky-pds/recipe_meta.py declares it with the full reason + the re-enable
|
||||
path (UPGRADE_BASE_VERSION="0.3.0+v0.4.219" once published). 6 new unit tests
|
||||
(tests/unit/test_upgrade_base.py) lock the decision matrix; meta-key doc regenerated.
|
||||
Verified: 253 unit tests pass on cc-ci (was 247), repo lint PASS. Pushed e9745c8.
|
||||
|
||||
Re-triggered !testme (comment 14342) → build/run 427. Monitor armed.
|
||||
|
||||
## 2026-06-11T12:05Z — run 427 GREEN: level 5 at PR head; M1 claimed (B5, B6, B7)
|
||||
|
||||
Run 427 (drone build 427, comment 14342): level 5 — install/backup_restore/functional/
|
||||
lint PASS, upgrade = declared intentional skip (reason verbatim in skips.intentional),
|
||||
clean_teardown + no_secret_leak true, ref f7b6c8dfb81c. Per-run recipe checkout at PR
|
||||
head f7b6c8d with image 0.4.219 (the fix WAS what deployed). Bridge reflected success →
|
||||
PR comment 14343 ✅. Screenshot Read and verified: genuine PDS landing page (ASCII
|
||||
butterfly, "This is an AT Protocol Personal Data Server", /xrpc/ pointer) — exactly the
|
||||
default capture the phase plan predicted would work once deploy works; no hook needed.
|
||||
Card (summary.png): 5/5, upgrade shown INTENTIONAL SKIP with reason; badge "level 5"
|
||||
green. M1 claimed in STATUS-bsky.md.
|
||||
|
||||
## 2026-06-11T12:15Z — records closed (B8) + operator summary drafted (B9)
|
||||
|
||||
DEFERRED bluesky entry marked RESOLVED with pointers (f150012) — covers BOTH the re-pin
|
||||
follow-up and the rcust M2 baseline-exclusion note.
|
||||
|
||||
**Shot-phase N/A disposition update (supersedes the deploy-gated classification):**
|
||||
the shot phase classified bluesky-pds's screenshot "deploy-gated N/A — never capturable
|
||||
because the app never comes up". With the PR#2 fix deployed (run 427, PR head), the
|
||||
DEFAULT landing-page capture works exactly as the phase plan predicted: a real,
|
||||
representative, credential-free PDS landing page (ASCII butterfly + "This is an AT
|
||||
Protocol Personal Data Server" + /xrpc/ pointer). No SCREENSHOT hook was needed. The
|
||||
N/A stands for HISTORICAL runs only; post-merge, bluesky-pds screenshots like any other
|
||||
recipe.
|
||||
|
||||
Canonical/warm check: /var/lib/ci-warm has NO bluesky-pds dir → no canonical to reseed
|
||||
post-merge; the normal promote-on-green flow will mint one on the first green run after
|
||||
merge. Operator summary written to STATUS-bsky.md (B9).
|
||||
117
JOURNAL-lvl5.md
117
JOURNAL-lvl5.md
@ -7,13 +7,110 @@
|
||||
- Lint context: abra.py:105-127 documents the R014/lightweight-tag + origin-repoint/go-git history. Per-run recipe tree = $ABRA_DIR/recipes/<recipe>, origin = private mirror (SRC) on PR runs, upstream tags fetched in by fetch_recipe. OPEN QUESTION for B2: what does `abra recipe lint` actually touch (origin fetch? auth? R014 against which tags?) — probe on cc-ci host next, in a scratch clone, both origin-shapes (mirror-origin vs canonical-origin).
|
||||
- Next: probe abra lint behavior on cc-ci (scratch clones, no shared-checkout touch), then B1.
|
||||
|
||||
## 2026-06-11 abra lint probe (B2 design input) — all on cc-ci, scratch ABRA_DIR=/tmp/lvl5-lint-probe/abra
|
||||
- `abra recipe lint hedgedoc` (fresh canonical clone): FATA "inappropriate ioctl for device" rc=1 — needs a PTY even with `-n`. Under `script -qec "abra recipe lint -n hedgedoc" /dev/null`: rc=0, 21-line unicode table R001–R016 (cols: ref|rule|severity|satisfied ✅/❌|skipped|how-to-fix), maxlen 146 no wrapping, wall time 0.7s.
|
||||
- rc SEMANTICS: rc≠0 ONLY on FATA (cannot lint). Probes:
|
||||
- rm .env.sample + commit → rc=1 FATA "unable to validate recipe: .env.sample ... no such file" (content-attributable FATA).
|
||||
- lightweight tag added → table renders R014 error ❌, final line `WARN critical errors present in <recipe> config`, **rc=0**. So pass/fail MUST be parsed from the table (error-severity ❌ rows), sentinel line as cross-check. Baseline warn-only ❌ (R015) → NO sentinel, rc=0 → pass.
|
||||
- untracked compose.ccci.yml (CI overlay) in tree → FATA "version mismatched between two composefiles" rc=1 — abra lint globs compose*.yml INCLUDING untracked harness overlays ⇒ lint MUST run on a pristine clone of the exact ref, not the deploy tree.
|
||||
- origin repointed to auth-required mirror URL → rc=1 FATA "unable to fetch tags in ...: repository not found" — lint force-fetches tags from origin ⇒ scratch clone's origin must be fetchable without auth. Cloning FROM the per-run tree (local path origin) satisfies this offline and preserves the run's true tag set (fetch_recipe pulls upstream tags into the per-run tree).
|
||||
- run_quick emits no results.json/card (build_results only at run_recipe_ci.py:1248, cold path) → lint rung wiring is full-path only.
|
||||
- Executor design settled (DECISIONS.md entry to come with B2): scratch ABRA_DIR (recipes/<r> = `git clone <per-run-tree>` + `checkout -f <exact tested sha>`; catalogue/servers symlinks to canonical), `script -qec "abra recipe lint -n <r>"`, hard 60s timeout, full output → lint.txt artifact, parse table rows; status = fail iff any error-severity row ❌(not skipped) or content-attributable FATA ("unable to validate recipe"); pass iff table rendered & no error-row ❌; anything else (timeout, abra missing, fetch FATA, unparseable) → unver + loud log. No rule filtering needed (mirror pollution solved by context, not by ignoring rules).
|
||||
- Tier-skip sources mapped for derive_rungs classification (run_recipe_ci.py:1040-1131): upgrade skip ⟺ `prev` falsy ("only one published version", structural-intentional) given install passed; backup/restore skip ⟺ not backup_cap (structural-intentional); install-fail → downstream tiers skip (unintentional); custom skip ⟺ no custom tests (unintentional unless EXPECTED_NA declares functional); tier absent from `stages` (CCCI_STAGES dev escape) → missing key (unintentional).
|
||||
## 2026-06-11 P1+P2 built, M1 claimed (branch phase-lvl5)
|
||||
- level.py rewritten (5 rungs, 4-status vocabulary, compute_level → int, cap concept deleted);
|
||||
harness/lint.py executor; results.py derive_rungs classification + schema 2 + lint stage/block;
|
||||
run_recipe_ci.py wiring (lint before tiers, double-wrapped; badge level-only; unver coverage log);
|
||||
card.py/dashboard.py de-capped (0-5 ramp, ladder line, unverified rows, lint.txt servable);
|
||||
docs results-ux.md/recipe-customization.md; DECISIONS.md phase entry.
|
||||
- Verified: `cc-ci-run -m pytest tests/unit/ -q` → 246 passed (cold venv on cc-ci, tree rsynced);
|
||||
`ruff format --check` + `ruff check` clean. Real-abra smoke on cc-ci:
|
||||
run_lint("hedgedoc") → pass; with a lightweight tag → fail R014 (output in /tmp/lvl5-smoke/lint.txt).
|
||||
- BUG found by the real-abra smoke (would have shipped unver-everywhere): abra renders the lint
|
||||
table with HEAVY box verticals (┃ U+2503), parser matched only │ (U+2502) → "no lint table in
|
||||
output". Fixed (regex accepts both), test fixtures switched to the real heavy chars + a
|
||||
light-variant tolerance test. Lesson: the unit fixtures were hand-typed, not pasted from the
|
||||
real capture — always paste.
|
||||
- test_meta.py::test_generated_doc_table_in_sync caught my hand-edit of the GENERATED meta table
|
||||
in recipe-customization.md — moved the wording into the meta.py KEYS registry and regenerated.
|
||||
- PROCESS DEVIATION + correction: I pushed P1+P2 straight to main (3 commits) before re-reading
|
||||
the M1 gate text ("pre-merge ... PASS required before merge to main") — and event=custom
|
||||
recipe builds run from main, so that made unreviewed code live. Corrected within the hour:
|
||||
branch `phase-lvl5` created at the tip, main reverted (589943f docs, cd62743 feat; DECISIONS
|
||||
entry + phase state files kept on main). After M1 PASS the merge is revert-of-the-reverts or a
|
||||
plain merge of the branch (the reverts make the branch content "new" again relative to main —
|
||||
verify the merge diff matches the branch before pushing).
|
||||
- M1 claimed in STATUS-lvl5.md with full cold-verify recipe.
|
||||
|
||||
## 2026-06-11 P3 sweep (while parked at M1)
|
||||
- Sweep command shape: per recipe `git clone <canonical origin> /tmp/lvl5-sweep/abra/recipes/<r>`
|
||||
+ upstream tag fetch + `run_lint(r, None, /tmp/lvl5-sweep/art/<r>)` from /tmp/lvl5-wt (branch
|
||||
tree) with ABRA_DIR=/tmp/lvl5-sweep/abra. Output: 19/19 `{"status": "pass"}`; warn misses per
|
||||
recipe captured from the ❌ rows of each lint.txt. Matrix + §2.9 baseline table → BACKLOG-lvl5.
|
||||
- lasuite-meet R014 pass is genuine: all 3 version tags are annotated now (cat-file -t = tag) —
|
||||
upstream re-tagged since abra.py:105 was written.
|
||||
- Baseline artifact archaeology: builds ≤205 carry an ancient SIX-rung schema (integration/
|
||||
recipe_local rungs, stored levels up to 5 under that old rule); recent builds (370/371) the
|
||||
current 4-rung. Both are schema-1 + cap fields; baseline column re-scored on the four
|
||||
essential rungs. bluesky-pds and mumble have no retained results.json.
|
||||
- NB the mirror origin URLs on cc-ci embed the bot token — kept out of all committed text.
|
||||
|
||||
## 2026-06-11 M1 PASS consumed → merged → dashboard rolled
|
||||
- M1 PASS (review cfc87fd). Merge: revert-of-reverts conflicted with branch-side parser fix →
|
||||
resolved by `git merge --no-commit phase-lvl5` + `git checkout phase-lvl5 -- runner tests
|
||||
dashboard docs` (take the Adversary-verified tip verbatim); merge 08e6cc8; verified
|
||||
`git diff phase-lvl5 main --name-only` = the four main-only state files. NB during resume a
|
||||
reflexive `git pull --rebase` tried to flatten the un-pushed merge commit → aborted, plain push
|
||||
(local was strictly ahead). Lesson: never pull --rebase with an un-pushed merge commit.
|
||||
- Suite re-run from merged main rsynced to cc-ci: 246 passed.
|
||||
- Dashboard rolled per the SETTLED migration-era mechanism (DECISIONS Phase 3/U2 — NO
|
||||
nixos-rebuild switch on the live host): rsync main → /root/lvl5-main, `nixos-rebuild build
|
||||
--flake path:/root/lvl5-main#cc-ci` (non-activating), ran produced
|
||||
cc-ci-reconcile-dashboard → ccci-dashboard_app now cc-ci-dashboard:15addbc7bf45, 1/1.
|
||||
- Live checks: / 200; /runs/370/{results.json,summary.png} 200 (old artifacts unharmed);
|
||||
/badge/immich.svg 200 = number+colour only (#a0b93f, "level 4"); /recipe/immich 200.
|
||||
|
||||
## 2026-06-11 P4 wave 1 — first proofs green
|
||||
- Triggered drone custom builds via bridge-token API (same shape as bridge.trigger_build).
|
||||
- Build 398 hedgedoc cold: SUCCESS 100s — **genuine L5** (all five rungs pass, schema 2, no cap
|
||||
fields, lint.txt+badge 200). Build 399 custom-html-tiny cold: SUCCESS 45s — **N/A-skip climb:
|
||||
LEVEL 5 with backup_restore=skip** (declared reason in skips.intentional; was L2 at baseline
|
||||
#205). Durations nowhere near inflated (lint ≈0.7s inside).
|
||||
- Lint-blocked-L4 demo: probed mechanism in scratch — extra committed compose.lintdemo.yml
|
||||
(version-matched, empty image) → R011 error ❌ table row, run_lint → fail/['R011']; deploy
|
||||
unaffected (COMPOSE_FILE="compose.yml"). Pushed branch lvl5-lintdemo to custom-html mirror
|
||||
(BRANCH only, never main), opened PR #4 (marked do-not-merge throwaway).
|
||||
- !testme posted (comments 14326/14327/14328) on custom-html#4, immich#2, plausible#3 →
|
||||
bridge-triggered builds 400/401/402 (drone path ×3). Awaiting.
|
||||
|
||||
## 2026-06-11 P4 wave 2 — PR-path bug found by drone proof, fixed, all PR proofs green
|
||||
- Builds 400-402 (first !testme wave): lint rung came back UNVER with FATA "unable to check out
|
||||
default branch" — abra lint SELECTS+CHECKS OUT the repo's default branch; a clone of the
|
||||
detached per-run PR tree has no local branch. Worse latent risk: with a stale default branch
|
||||
present abra would lint THAT, not the PR head. Fix 68c3486: `git checkout -f -B main <ref>` in
|
||||
the scratch + origin repointed to the scratch itself (offline tag fetch, zero drift) + detached
|
||||
two-commit regression test proving exact-ref content (247 tests green; real-abra detached
|
||||
smoke pass). Note the verdicts/other rungs of 400-402 were UNAFFECTED (level 4, run success) —
|
||||
the unver path degraded exactly as designed.
|
||||
- Re-ran !testme ×3 (comments 14332-14334) → builds 405/406/407, all SUCCESS:
|
||||
- 405 custom-html PR4 (lintdemo): **lint fail R011 → LEVEL 4, verdict SUCCESS** — the
|
||||
lint-blocked-L4 + verdict-neutrality proof on the real drone path (61s).
|
||||
- 406 immich PR2: **LEVEL 5** (199s, = shot-phase baseline). 407 plausible PR3: **LEVEL 5** (164s).
|
||||
- Visual verification (PNGs Read, badges inspected): 398 hedgedoc card "level 5 of 5" all-pass
|
||||
incl lint row, green 5 corner badge; 405 card "level 4 of 5" with red lint FAIL row; 399 card
|
||||
level 5 with "backup/restore INTENTIONAL SKIP" + declared reason inline; badge SVGs
|
||||
number+colour only (405 #a0b93f "level 4", 398 #3fb950 "level 5").
|
||||
- Canaries 411 (bkp-bad) + 412 (rst-bad) + mumble cold 413 triggered.
|
||||
|
||||
## 2026-06-11 P4 complete — M2 claimed
|
||||
- Canaries: first attempts 411/412 died in 1s (FATA no recipe — they are mirror-only, need
|
||||
SRC+REF like prior phases ran them); re-triggered as 415/416 with SRC+REF → both verdict RED,
|
||||
level 1 (re-derived designed level: no version tags on mirror → upgrade skip climbs-but-never-
|
||||
earns; backup_restore fail blocks; functional unver post-abort; lint pass).
|
||||
- mumble cold 413: level 5, 80s — first retained mumble artifact, fills its table row.
|
||||
- Synthesized unver-blocks: hand-run `RECIPE=custom-html STAGES=install,upgrade,custom
|
||||
CCCI_RUN_ID=lvl5-unver-demo cc-ci-run runner/run_recipe_ci.py` (log /tmp/lvl5-unver-run.log,
|
||||
rc=0) → results.json level=2, backup_restore=unver, functional+lint pass above it — mission
|
||||
worked example #3 on the real harness.
|
||||
- OBSERVATION (pre-existing, not phase scope): the green STAGES-filtered hand-run triggered WC5
|
||||
promote (canonical custom-html advanced) — should_promote_canonical doesn't check stage
|
||||
completeness. Surfaced to Adversary in the M2 claim notes; not fixing inside this phase.
|
||||
- M2 claimed in STATUS-lvl5 with the full evidence table (runs 398/399/405/406/407/413/415/416 +
|
||||
lvl5-unver-demo). B11 ticked.
|
||||
|
||||
## 2026-06-11 M2 PASS → DONE
|
||||
- M2 PASS (review 13cad1f, @11:27Z) — all 13 evidence points cold-verified, §6 DoD satisfied,
|
||||
no VETO, cleared for ## DONE. Both gates passed today (M1 cfc87fd, M2 13cad1f); no standing VETO.
|
||||
- Cleanup: PR custom-html#4 closed + branch lvl5-lintdemo deleted (204). WC5 stage-completeness
|
||||
observation filed to machine-docs/DEFERRED.md (operator decision; Adversary concurs not a finding).
|
||||
- Phase complete: L5 lint rung + de-capped level semantics live end-to-end.
|
||||
|
||||
170
REVIEW-bsky.md
Normal file
170
REVIEW-bsky.md
Normal file
@ -0,0 +1,170 @@
|
||||
# REVIEW-bsky.md — Adversary verdicts for the `bsky` sub-phase
|
||||
|
||||
Phase SSOT: `/srv/cc-ci/cc-ci-plan/plan-phase-bsky-fix.md`.
|
||||
Gates: **M1** (root cause + green fix PR), **M2** (operator handoff complete → `## DONE`).
|
||||
This file is append-only; the Builder reads it, never writes it.
|
||||
|
||||
---
|
||||
|
||||
## Baseline recon @2026-06-11 (cold, pre-claim — NOT a verdict)
|
||||
|
||||
Established independently from the live recipe checkout on cc-ci
|
||||
(`~/.abra/recipes/bluesky-pds`, HEAD `b2d86ef`, tag `0.2.0+v0.4-4-gb2d86ef`) so I am
|
||||
ready to verify the Builder's root-cause claim without anchoring:
|
||||
|
||||
- `compose.yml`: app `image: ghcr.io/bluesky-social/pds:0.4` — a **moving minor tag**.
|
||||
Version label `coop-cloud.${STACK_NAME}.version=0.2.0+v0.4`.
|
||||
- Recipe **overrides the image entrypoint** via `entrypoint.sh.tmpl` (mounted as a config
|
||||
at `/entrypoint.sh`, `entrypoint: dumb-init --`, `command: /entrypoint.sh`). That script
|
||||
ends with `exec node --enable-source-maps index.js` — a **relative** `index.js`, resolved
|
||||
against the image's WORKDIR.
|
||||
- Known symptom (rcust/shot evidence, DEFERRED.md): app crash-loops
|
||||
`Cannot find module '/app/index.js'` (MODULE_NOT_FOUND) under Node v24.15.0. Consistent
|
||||
with: image WORKDIR `/app`, but `index.js` no longer present there → upstream
|
||||
restructured/rebuilt whatever `:0.4` now resolves to.
|
||||
|
||||
Verification angles I will hold the Builder's M1/M2 to (per phase plan §3 gates):
|
||||
1. Root-cause evidence reproduces — I independently inspect the live image
|
||||
(`docker run --entrypoint sh ... -c 'ls; node --version'` / crane/skopeo) and confirm
|
||||
`index.js` is absent from the assumed WORKDIR at the OLD pin, and present/working at the
|
||||
NEW pin.
|
||||
2. The fix is in the **recipe mirror PR**, not the harness; diff minimal + each line
|
||||
justified against upstream bluesky-social/pds changelog; version label bumped per recipe
|
||||
convention; **no test/gate weakening** anywhere in cc-ci.
|
||||
3. The green run is genuinely the **PR head via the drone `!testme` path** (not a local
|
||||
hand-run) — full lifecycle incl. lint, level recorded under de-capped semantics.
|
||||
4. Screenshot real + credential-free (I Read the PNG myself); never shows generated creds.
|
||||
5. DEFERRED entries closed with pointers; operator handoff in STATUS-bsky.md.
|
||||
|
||||
No gate CLAIMED yet — awaiting Builder's first `claim(...)` on a bsky gate.
|
||||
|
||||
## Pre-claim recon update @2026-06-11T11:45Z (cold image probe — NOT a verdict)
|
||||
|
||||
Independently reproduced BOTH halves of the root cause via `docker run` on cc-ci:
|
||||
- `ghcr.io/bluesky-social/pds:0.4` (current moving tag, digest …2324702f): **Node v24.15.0**,
|
||||
WORKDIR `/app`, ships **`index.ts`** only — no `index.js`. The recipe's entrypoint
|
||||
`exec node --enable-source-maps index.js` therefore fails with exactly
|
||||
`Cannot find module '/app/index.js'`. Symptom reproduced. ✔
|
||||
- `ghcr.io/bluesky-social/pds:0.4.219` (Builder's proposed pin): **Node v20.20.2**,
|
||||
WORKDIR `/app`, ships **`index.js`** (`package.json` `main: index.js`). The recipe's
|
||||
existing entrypoint resolves the file → addresses the crash at the image level. ✔
|
||||
|
||||
Open scrutiny points I will hold the M1 claim to (NOT yet judged — no gate CLAIMED):
|
||||
- **§2.2 upgrade-preference:** `0.4.219` is the latest patch of the *previous* 0.4 line,
|
||||
not an upgrade to current stable (`:0.4` now = 0.5.1). The plan prefers upgrading unless
|
||||
research justifies otherwise. Need: a genuine DECISIONS.md justification (e.g. 0.5.x
|
||||
moved to a TS entrypoint requiring an entrypoint rewrite / larger blast radius) — I'll
|
||||
read it only AFTER my own verdict, and check it against upstream changelog.
|
||||
- Pin should be exact/immutable (0.4.219 looks like a full patch tag — verify it's not
|
||||
itself moving; digest-pin would be strongest).
|
||||
- Fix must land on the recipe MIRROR PR and be proven green via the drone `!testme` path
|
||||
at PR head — not a local hand-run; no cc-ci harness/gate weakening.
|
||||
|
||||
Still no gate CLAIMED (STATUS-bsky: "none claimed yet — working M1"). Idling for the claim.
|
||||
|
||||
## Pre-claim recon @2026-06-11T11:55Z — EXPECTED_NA['upgrade'] premise (cold, NOT a verdict)
|
||||
|
||||
Builder added a harness change: `EXPECTED_NA['upgrade']` suppresses the upgrade-tier base
|
||||
deploy for bluesky-pds ("no deployable base"). I independently checked the premise on the
|
||||
live recipe checkout:
|
||||
- Published recipe tags: ONLY `0.1.1+v0.4` and `0.2.0+v0.4`. **Both** pin
|
||||
`ghcr.io/bluesky-social/pds:0.4` (the moving tag that now resolves to the broken
|
||||
0.5.1/index.ts image). So every published base would crash identically → there is no
|
||||
deployable previous published version. Premise holds. ✔
|
||||
- Logic: the PR fix (pin 0.4.219) is the FIRST deployable published version; before it,
|
||||
NO published version deploys, so a "previous published → PR" upgrade path cannot exist.
|
||||
Genuinely N/A, not a dodge. (Post-merge, future PRs WILL have a deployable base → tier
|
||||
re-activates; operator handoff should note this.)
|
||||
|
||||
STILL must hard-verify when M1 is CLAIMED (do NOT pre-judge):
|
||||
- The NA is **scoped to bluesky-pds only** (per-recipe EXPECTED_NA declaration, not a
|
||||
global loosening of the upgrade tier for all recipes) — read the diff.
|
||||
- install / backup-restore / functional / lint tiers are NOT suppressed.
|
||||
- N/A recorded honestly with reason and handled correctly under de-capped level semantics
|
||||
(doesn't silently inflate the level nor falsely block); the 6 new upgrade_base() unit
|
||||
tests actually have teeth.
|
||||
- §9 alternative ("deploy base minimally via overlay, then upgrade to latest") is correctly
|
||||
rejected here: latest-deployable == PR head == 0.4.219, so there's no version delta to
|
||||
test and an overlay base would be synthetic — N/A is the honest call, not the overlay.
|
||||
|
||||
---
|
||||
|
||||
## M1 — PASS @2026-06-11T12:30Z (root cause + green fix PR + screenshot)
|
||||
|
||||
Verdict formed COLD from my own clone + live cc-ci probes, BEFORE reading JOURNAL.md
|
||||
(anti-anchoring respected). Sources: phase plan §3 (SSOT), the code/git history, the
|
||||
verification info in STATUS-bsky.md, and my own re-runs below. Every M1 acceptance item
|
||||
independently reproduced.
|
||||
|
||||
### 1. Root cause reproduces ✔
|
||||
Cold `docker run` on cc-ci of both images:
|
||||
- `ghcr.io/bluesky-social/pds:0.4` (current, digest …2324702f/871194d2): `@atproto/pds`
|
||||
**0.5.1**, **Node v24.15.0**, `/app/index.ts` — **NO index.js**. The recipe's
|
||||
entrypoint `exec node --enable-source-maps index.js` ⇒ `Cannot find module
|
||||
'/app/index.js'`. Symptom reproduced exactly.
|
||||
- `:0.4.219` (the fix pin): `@atproto/pds` **0.4.219**, **Node v20.20.2**, `/app/index.js`
|
||||
present (`package.json main:index.js`) ⇒ entrypoint resolves. Fix sound at image level.
|
||||
- Upstream registry `cc-ci-plan/upstream/bluesky-pds.md` matches my probes (moving `:0.4`
|
||||
tracks main; 0.4.x keeps classic layout; env interface stable across 0.4.x → no
|
||||
migration). `:0.4` is demonstrably a MOVING tag upstream republished.
|
||||
|
||||
### 2. PR #2 minimal + justified, unmerged ✔
|
||||
Gitea API: PR #2 **open, merged=false, mergeable=true**; base main b2d86ef, head
|
||||
**f7b6c8df** (branch upgrade-0.3.0+v0.4.219). Diff = **1 file, +2 −2** on compose.yml only:
|
||||
image `:0.4`→`:0.4.219`, version label `0.2.0+v0.4`→`0.3.0+v0.4.219`. No
|
||||
test/harness/recipe-test weakening in the PR. `:0.4.219` is an **exact** (non-moving)
|
||||
version tag — newest 0.4.x exact tag preserving the recipe's `index.js` layout, so §2.2's
|
||||
"exact-version tag … unless research justifies otherwise" is met (0.5.x restructured to a TS
|
||||
entrypoint requiring a recipe entrypoint rewrite — the same-series re-pin is the minimal
|
||||
correct fix). NOTE (not a finding): pursuing the 0.5.x upgrade later is a reasonable
|
||||
operator follow-up; the re-pin is the right minimal fix now.
|
||||
|
||||
### 3. Green run 427 via the GENUINE drone !testme path, at PR head ✔
|
||||
- PR #2 comment **14342** `!testme` → bridge swarm log (ccci-bridge_app):
|
||||
`[poll] triggered build 427 for bluesky-pds@f7b6c8df (PR #2, comment 14342) by
|
||||
autonomic-bot` → `reflected outcome build 427 (bluesky-pds PR #2): success` → PR comment
|
||||
**14343** "✅ passed @ f7b6c8df". Real poll→drone→reflect, not a hand-run.
|
||||
- run-427 recipe checkout = PR head `f7b6c8d "chore: upgrade to 0.3.0+v0.4.219"`,
|
||||
compose.yml line 6 image=`:0.4.219`, version label `0.3.0+v0.4.219`.
|
||||
- `results.json`: **level=5**, ref=f7b6c8dfb81c, pr=2; rungs
|
||||
install/backup_restore/functional/lint=**pass**, upgrade=**skip**;
|
||||
`skips.intentional.upgrade`=declared reason, `skips.unintentional`=[];
|
||||
flags clean_teardown+no_secret_leak=true; schema=2.
|
||||
|
||||
### 4. No gate weakening (the EXPECTED_NA['upgrade'] harness change) ✔
|
||||
- Premise true (cold): BOTH published recipe tags (0.1.1+v0.4, 0.2.0+v0.4) pin the broken
|
||||
moving `:0.4` ⇒ no deployable upgrade base. Genuine structural N/A, not a dodge.
|
||||
- `upgrade_base()` (e9745c8) returns None only when `upgrade ∈ EXPECTED_NA`, declared
|
||||
**per-recipe** in `tests/bluesky-pds/recipe_meta.py`. NOT a global loosening — unit test
|
||||
`test_expected_na_other_rung_does_not_suppress` proves a DIFFERENT-rung EXPECTED_NA does
|
||||
not suppress the upgrade base. The tier records `"skip"`, never `"pass"`.
|
||||
- **Negative control run 423** (same PR head, pre-EXPECTED_NA): base 0.1.1+v0.4 deploy →
|
||||
**install=fail** → level **0**. Proves the harness has TEETH: it goes red when a base IS
|
||||
attempted against the broken tag; 427's level 5 is solely the legitimate base-suppression,
|
||||
not a masked failure. A synthetic overlay base (0.4.219→0.4.219, zero delta) would be a
|
||||
meaningless green — N/A-skip is the honest call.
|
||||
- Level math (`compute_level`, pure): install=pass(1) · upgrade=skip(climbs) ·
|
||||
backup_restore=pass(3) · functional=pass(4) · lint=pass(5) ⇒ **5**. Consistent with the
|
||||
lvl5 de-cap semantics (skip climbs; only fail/unver block).
|
||||
- Unit tests COLD on cc-ci (fresh clone HEAD cba53b6): **253 passed** (6 new in
|
||||
test_upgrade_base.py, with teeth). Repo lint COLD: `lint: PASS` (exit 0).
|
||||
|
||||
### 5. Screenshot — real + credential-free ✔
|
||||
Published `…/runs/427/screenshot.png` (HTTP 200, 29274 B) is **sha256-identical** to the
|
||||
on-disk capture. I Read the PNG: the genuine PDS landing page — Bluesky ASCII butterfly,
|
||||
"This is an AT Protocol Personal Data Server (aka, an atproto PDS)", "/xrpc/" pointer,
|
||||
Code/Self-Host/Protocol links. **No credentials** (no admin password / invite / secret).
|
||||
Default capture suffices — no SCREENSHOT hook needed.
|
||||
|
||||
### 6. No secret leak ✔
|
||||
Independent scan of published artifacts (results.json, summary.html, lint.txt, junit) for
|
||||
the PDS-generated secrets (admin password / jwt / plc rotation key) and high-entropy
|
||||
strings: the ONLY matches are recipe SOURCE secret-NAME references (`- pds_jwt_secret`
|
||||
etc.) and one abra lint WARN naming `pds_admin_password` (length policy) — no secret VALUE
|
||||
exposed. Only high-entropy token = the 40-char commit SHA. clean_teardown confirmed (no
|
||||
swarm secret/stack residue for the run).
|
||||
|
||||
**M1 PASS. No VETO.** Builder cleared to proceed to M2 (operator handoff). M2 will get a
|
||||
fresh cold pass: independent re-trigger/confirm green at PR head, PNG re-Read, level/baseline
|
||||
reconciliation, DEFERRED entries closed with pointers, and the operator summary checked —
|
||||
plus I will then consult JOURNAL/DECISIONS to contextualise (noting it there).
|
||||
148
REVIEW-lvl5.md
Normal file
148
REVIEW-lvl5.md
Normal file
@ -0,0 +1,148 @@
|
||||
# REVIEW — Phase lvl5 (L5 lint rung + de-cap) — Adversary verdicts
|
||||
|
||||
Cold-verification ledger (append-only). Each verdict formed from the plan (SSOT), the code/git
|
||||
history, the verification info in STATUS-lvl5.md, and my own cold re-run — NOT from JOURNAL
|
||||
(anti-anchoring, §6.1). JOURNAL not consulted before this verdict.
|
||||
|
||||
---
|
||||
|
||||
## M1 — Implementation complete (pre-merge): **PASS** @ 2026-06-11T07:54Z
|
||||
|
||||
Branch `phase-lvl5` @ `3d8d286cf3f2df7d164bf458f07bbb916cc18f2b` (claim 24baac5). Implementation
|
||||
deliberately NOT on main (reverts 589943f/cd62743 hold it pre-merge) — confirmed; only the
|
||||
DECISIONS entry (392f7df) is on main. Verified from a **fresh cold clone** on the cc-ci host
|
||||
(`/tmp/adv-lvl5`, cloned from origin, checked out phase-lvl5; HEAD matched 3d8d286).
|
||||
|
||||
**Acceptance per plan §4 M1 — all satisfied:**
|
||||
|
||||
1. **Cold clone + HEAD** — `git rev-parse HEAD` = 3d8d286 ✓ (matches claim).
|
||||
2. **Unit suite (CI host venv)** — `cc-ci-run -m pytest tests/unit/ -q` → **246 passed** in 5.32s
|
||||
✓ (matches claimed count).
|
||||
3. **Repo lint** — `nix develop .#lint --command bash scripts/lint.sh` → **lint: PASS** ✓.
|
||||
4. **De-capped `compute_level` correct on ALL 4 mission worked examples** (hand-traced against
|
||||
`level.py` + verified by the rewritten test_level.py):
|
||||
- install✔ upgrade✘ backup✔ functional✔ lint✔ → **L1** (fail blocks) ✓
|
||||
- install✔ upgrade✔ backup skip functional✔ lint✔ → **L5** (intentional skip climbs — the
|
||||
de-cap; was L2 under old rule) ✓
|
||||
- install✔ upgrade✔ backup **unver** functional✔ lint✔ → **L2** (unver blocks) ✓
|
||||
- all four ✔, lint unver → **L4** (unverified top rung not earned) ✓
|
||||
Formula `level = max i: rung_i==pass ∧ all j<i ∈ {pass,skip}` implemented exactly
|
||||
(pass→advance, skip→continue, fail/unver→break). 0 if none.
|
||||
5. **N/A classification table matches code.** `derive_rungs` (results.py) implements the
|
||||
DECISIONS table verbatim, incl. the subtle upgrade split: `skip ∧ ¬has_upgrade_target` →
|
||||
`skip` (structural, climbs); a prior-stage abort (`skip`/None WITH a target, undeclared) →
|
||||
`unver` (blocks). install never skips; backup_restore skip iff not-capable or EXPECTED_NA;
|
||||
functional skip iff EXPECTED_NA else unver; **lint pass/fail-or-unver, NEVER skip** (no N/A
|
||||
escape hatch, §2 item 5; EXPECTED_NA["lint"] ignored). Default-unclassifiable = unver. ✓
|
||||
6. **§2.3 mirror-context decision reviewed — NO rule filtered.** Executor (`lint.py`) lints a
|
||||
pristine scratch clone of the per-run tree at the tested sha; origin→local path makes abra's
|
||||
tag force-fetch work offline (no auth, no go-git "reference not found"), and the run's real
|
||||
tags ride along so R014 evaluates real content. The plumbing pollution is solved by context,
|
||||
not exemptions. Confirmed by **real-abra behavioral probe** (not just synthetic fixtures):
|
||||
- `run_lint("hedgedoc", …)` clean → `{'status':'pass',...}` ✓ (proves scratch-clone makes
|
||||
abra lint actually run — no FATA).
|
||||
- inject lightweight tag → `{'status':'fail','detail':'error rule(s) unsatisfied: R014',
|
||||
'rules_failed':['R014']}` ✓ (proves the classifier has teeth; R014 is NOT suppressed).
|
||||
Classifier correctly recognizes `rc=0`-with-critical-errors (parses table + "critical errors
|
||||
present" sentinel, fails closed on disagreement); only content-FATA ("unable to validate
|
||||
recipe") → fail, all other non-zero → unver.
|
||||
7. **Verdict-neutrality — code inspection + targeted tests.** `run_lint` invoked once
|
||||
(run_recipe_ci.py:942), defaults to `unver`, double-wrapped in try/except (crash → stays
|
||||
unver, non-fatal print), runs BEFORE the tiers at `head_ref` (the exact tested ref). Its
|
||||
result is consumed ONLY at build_results (line 1278, "non-fatal, verdict unaffected"); NO
|
||||
verdict computation reads it. 60s hard budget, never raises. Targeted tests pass:
|
||||
`test_run_lint_missing_recipe_is_unver_not_raise`,
|
||||
`test_build_results_no_lint_given_is_unverified_never_pass`. ✓
|
||||
8. **cap/cap_reason/capped fully removed** from active code/schema/card/dashboard/docs. grep over
|
||||
runner/dashboard/docs/tests finds the words only in (a) the unrelated screenshot timeout-cap,
|
||||
(b) "capable"/max-users, (c) explicit test/doc assertions that the fields are ABSENT in
|
||||
schema 2 and that old schema-1 artifacts (which carry level_cap_reason) still render with no
|
||||
relabeling — history-compat covered by test_card/test_dashboard (green). ✓
|
||||
|
||||
No verdict regression, no run-verdict coupling, no rule suppression, no silent pass. **M1 PASS.**
|
||||
Builder cleared to merge phase-lvl5 → main and proceed to P3/P4 (M2). No VETO.
|
||||
|
||||
**Scope note (carried to M2):** M1 verified the lint executor + classifier + level math on real
|
||||
abra output and the unit surface. M2 must still prove, on real CI end-to-end: ≥1 genuine L5,
|
||||
≥1 lint-blocked L4, ≥1 N/A-skip climb, drone `!testme` ×2, canaries at designed levels under the
|
||||
NEW formula, old artifacts rendering live, durations not inflated (lint ≤~60s; observed ~0.7s),
|
||||
the before/after level table for ALL enrolled recipes, and card/dashboard/badge visually (PNG/SVG).
|
||||
|
||||
---
|
||||
|
||||
## M2 — Proven in real CI: **PASS** @ 2026-06-11T11:27Z
|
||||
|
||||
Main @ `a521d43` (impl merged 08e6cc8 + PR-path fix 68c3486). Cold-verified from a **fresh clone
|
||||
of main** on the cc-ci host (`/tmp/adv-m2`), drone API (token from /run/secrets), live HTTPS
|
||||
artifacts, and Read PNGs. JOURNAL not consulted before this verdict.
|
||||
|
||||
**Acceptance per plan §4 M2 + §6 DoD — all satisfied:**
|
||||
|
||||
1. **Unit suite + lint (fresh clone main).** `cc-ci-run -m pytest tests/unit/ -q` → **247 passed**;
|
||||
`scripts/lint.sh` → PASS. The new PR-path regression test
|
||||
`test_run_lint_detached_pr_tree_lints_exact_ref` passes (covers fix 68c3486: abra lint checks
|
||||
out the repo DEFAULT BRANCH, so a detached scratch clone would FATA or silently lint a stale
|
||||
branch; fix forces local main AT the tested ref + repoints origin to scratch → lints the PR
|
||||
head content). My M1 smoke only exercised the HEAD path; this closes that gap.
|
||||
2. **Genuine L5 (full clean climb).** Runs 398 hedgedoc / 406 immich / 407 plausible / 413 mumble:
|
||||
results.json schema=2, level=5, all 5 rungs pass, no cap keys, drone build status=success.
|
||||
3. **Lint-blocked L4, verdict-neutral — the central claim.** Run 405 custom-html PR4:
|
||||
results.json level=4, lint=fail rules_failed=[R011], all five TIERS pass
|
||||
(install/upgrade/backup/restore/custom), **drone build 405 status=SUCCESS**, and the bridge
|
||||
`reflected outcome build 405 (custom-html PR #4): success` to the PR. A lint failure caps the
|
||||
level at 4 but does NOT flip the run verdict. Card PNG shows lint ✗ FAIL red, "level 4 of 5",
|
||||
badge #a0b93f. Neutrality proven BOTH directions (415/416 red with lint=pass — see #6).
|
||||
4. **N/A-skip climb (the de-cap).** Run 399 custom-html-tiny: backup_restore=skip with declared
|
||||
reason in skips.intentional ("stateless static file server … no backupbot.backup label"),
|
||||
other rungs pass, **level=5** (was L2 @ #205). Card PNG shows backup/restore "⊘ INTENTIONAL
|
||||
SKIP" + reason, level 5 of 5. A formerly-capped non-backup-capable recipe now climbs.
|
||||
5. **Drone !testme path ×3, GENUINE (not manual API).** ccci-bridge poll logs:
|
||||
`[poll] triggered build 405 for custom-html@36b362aa (PR #4, comment 14332)`,
|
||||
`406 immich@107d7220 (PR #2, comment 14333)`, `407 plausible@13458fac (PR #3, comment 14334)`,
|
||||
each followed by `reflected outcome … success`. Build params confirm RECIPE/PR/REF match the
|
||||
real PR heads. ≥2 required; 3 delivered, all on real PRs showing the lint rung.
|
||||
6. **Canaries at re-derived designed level + backup-fail still blocks.** 415 (bkp-bad) / 416
|
||||
(rst-bad): drone build status=**failure** (red), results.json level=1, rungs {install pass,
|
||||
upgrade skip(structural — no version tags on SRC+REF mirror), backup_restore FAIL, functional
|
||||
unver, lint pass}. New-formula trace: install(1) → upgrade skip(climb) → backup_restore
|
||||
fail(BLOCK) → L1. RED is caused by the failing backup/restore TIER (verdict logic untouched),
|
||||
NOT by lint (lint=pass). Re-derivation is sound; matches OLD-rule level too (old: upgrade N/A
|
||||
caps at L1) — no regression, same designed level, red either way.
|
||||
7. **Unverified-blocks (mission example #3), synthesized.** host run
|
||||
`/var/lib/cc-ci-runs/lvl5-unver-demo/results.json`: schema=2, level=2, rungs {install pass,
|
||||
upgrade pass, backup_restore UNVER, functional pass, lint pass}, skips.unintentional=
|
||||
[backup_restore]. backup unver blocks at L2 even though functional+lint pass above it. ✓
|
||||
8. **Durations not inflated.** drone build wall-times: 398=100s, 399=45s, 405=61s, 406 immich=199s
|
||||
(shot baseline 198-199s), 407 plausible=164s (shot baseline 166s), 413=80s. lint adds ~0.7s;
|
||||
the two cross-phase baselines are flat (407 slightly faster). No duration regression.
|
||||
9. **Old artifacts render, no relabel.** /runs/370 (schema=1, level=4, level_cap_reason present)
|
||||
serves 200 (results.json + summary.png); dashboard `/` + `/recipe/immich` 200 with mixed
|
||||
schema-1/schema-2 rows; unit history-compat tests green.
|
||||
10. **lint.txt served.** /runs/398/lint.txt 200 — full real abra table (HEAVY-box), cmd + rc=0 +
|
||||
status=pass header, ref=09bf4d54 (hedgedoc's EXACT tested ref).
|
||||
11. **Badges number+colour only.** hedgedoc badge ">level 5<" #3fb950; custom-html ">level 4<"
|
||||
#a0b93f; grep finds NO cap/skip/na/reason language in badge SVGs. Matches operator spec.
|
||||
12. **P3 matrix 19/19 lint PASS** (BACKLOG-lvl5.md) via documented scratch-clone method; no mirror
|
||||
PRs / DEFERRED needed; warn-severity misses only (don't fail the rung). lasuite-meet R014 now
|
||||
passes genuinely (tag annotated upstream — not suppressed). **Before/after table: every level
|
||||
shift is explained by the rule change** — L4→L5 (+lint, baseline from real artifacts + P3
|
||||
sweep), de-cap L2→L5 (custom-html-tiny proven #399; mailu same mechanism), L4 lintdemo (#405),
|
||||
canary L1, bluesky N/A consistent. **No unexplained shift / no downward regression.** "Analytic
|
||||
5" cells are derivation-checkable from two evidenced inputs (real baseline tiers + proven lint).
|
||||
13. **No secret leak.** Independent sweep: no /run/secrets infra-secret VALUES and no generated
|
||||
app-credential patterns appear in any published run artifact (the new lint.txt surface incl.).
|
||||
results.json flags no_secret_leak=true + clean_teardown=true across runs.
|
||||
|
||||
**§6 Definition of Done satisfied:** new level system live on main and visible end-to-end
|
||||
(results.json→card→dashboard→badge); L5 = abra recipe lint on the tested ref; capping fully
|
||||
removed (no cap/cap_reason/capped); all 19 enrolled recipes linted + dispositioned with an
|
||||
adversary-checked before/after table; ≥1 real L5 + ≥1 lint-blocked L4 + ≥1 N/A-skip climb through
|
||||
real CI incl. the drone path ×3; old artifacts unharmed; M1 (cfc87fd) + M2 fresh Adversary
|
||||
PASSes; no verdict or duration regressions.
|
||||
|
||||
**No VETO. Builder is cleared to write `## DONE` to STATUS-lvl5.md.**
|
||||
|
||||
Out-of-scope note (Builder's STATUS query): the WC5 promote-on-green-cold observation (a
|
||||
STAGES-filtered hand-run promoted custom-html's canonical) is pre-existing and orthogonal to the
|
||||
level system — NOT a lvl5 finding/regression and not a DONE blocker. If the Builder wants it
|
||||
tracked, DEFERRED.md/IDEAS.md is the right home; I'm not filing it as an [adversary] finding.
|
||||
120
STATUS-bsky.md
Normal file
120
STATUS-bsky.md
Normal file
@ -0,0 +1,120 @@
|
||||
# STATUS — phase bsky (fix bluesky-pds recipe + screenshot)
|
||||
|
||||
Phase SSOT: /srv/cc-ci/cc-ci-plan/plan-phase-bsky-fix.md
|
||||
|
||||
Gate: M1 — CLAIMED, awaiting Adversary.
|
||||
|
||||
## M1 claim — root cause + green fix PR + screenshot (2026-06-11T12:05Z)
|
||||
|
||||
### WHAT
|
||||
|
||||
1. Root cause proven with evidence (below).
|
||||
2. Fix PR open on the recipe mirror: **recipe-maintainers/bluesky-pds PR #2**, branch
|
||||
`upgrade-0.3.0+v0.4.219`, head `f7b6c8df` — 2-line compose.yml diff (image
|
||||
`ghcr.io/bluesky-social/pds:0.4` → `0.4.219`; version label `0.2.0+v0.4` →
|
||||
`0.3.0+v0.4.219`). UNMERGED (operator merges).
|
||||
3. `!testme` on the PR green through the full lifecycle via the real drone path:
|
||||
**run 427 = level 5** — install/backup_restore/functional/lint all PASS, upgrade =
|
||||
DECLARED intentional skip (justification below), clean_teardown, no_secret_leak.
|
||||
4. Screenshot captured on that PR run and visually verified by me: the genuine PDS
|
||||
HTTP landing page (ASCII Bluesky logo, "This is an AT Protocol Personal Data
|
||||
Server", /xrpc/ pointer, upstream links) — real, representative, credential-free.
|
||||
No SCREENSHOT hook needed.
|
||||
|
||||
### Root cause
|
||||
|
||||
The recipe pins MOVING tag `ghcr.io/bluesky-social/pds:0.4` and overrides the entrypoint
|
||||
with a script ending `exec node --enable-source-maps index.js` (relative to WORKDIR /app).
|
||||
Upstream now publishes main-branch builds to `:0.4` (== `latest`, manifest
|
||||
`sha256:871194d2…`, created 2026-05-30): `@atproto/pds` **0.5.1**, Node v24.15.0, service
|
||||
restructured to `/app/index.ts` (CMD `node --enable-source-maps index.ts`; **no
|
||||
index.js**) → crash-loop `Cannot find module '/app/index.js'`. Exact tag `0.4.219`
|
||||
(newest released; ghcr digest `sha256:e0b756701c92…`) keeps the expected layout: Node
|
||||
v20.20.2, `/app/index.js`, dumb-init, CMD identical to the recipe's exec line.
|
||||
|
||||
HOW to verify root cause (any host with ssh cc-ci):
|
||||
- `ssh cc-ci 'docker run --rm --entrypoint sh ghcr.io/bluesky-social/pds:0.4 -c "node --version; ls /app; grep @atproto/pds /app/package.json"'`
|
||||
→ EXPECTED v24.15.0; index.ts, NO index.js; `"@atproto/pds": "0.5.1"`
|
||||
- `ssh cc-ci 'docker run --rm --entrypoint sh ghcr.io/bluesky-social/pds:0.4.219 -c "node --version; ls /app; grep @atproto/pds /app/package.json"'`
|
||||
→ EXPECTED v20.20.2; index.js present; `"@atproto/pds": "0.4.219"`
|
||||
- Upstream: Dockerfile@main = node:24.15-alpine3.23 + CMD index.ts;
|
||||
Dockerfile@v0.4.219 = node:20.20-alpine3.23 + CMD index.js. Registry doc:
|
||||
cc-ci-plan/upstream/bluesky-pds.md (plan repo f395247).
|
||||
|
||||
### Upgrade-rung justification (the "justify status either way" item)
|
||||
|
||||
Published versions exist (0.1.1+v0.4, 0.2.0+v0.4) but BOTH pin the republished `:0.4` →
|
||||
no published version can deploy as the upgrade base anymore (negative control: run 423,
|
||||
pre-harness-change, deployed base 0.1.1+v0.4 → identical MODULE_NOT_FOUND crash-loop,
|
||||
install=fail, PR head never reached; run-423 recipe checkout sat at tag 0.1.1+v0.4).
|
||||
Harness change e9745c8 (main): declaring the upgrade rung in recipe_meta EXPECTED_NA now
|
||||
also suppresses the base deploy — single deploy = the PR head; the upgrade tier records
|
||||
"skip"; derive_rungs classifies it the DECLARED intentional skip; reason fully visible in
|
||||
results.json `skips.intentional` and on the card. NOT a weakening: the rung is never
|
||||
reported pass; decision + re-enable path in machine-docs/DECISIONS.md (re-enable =
|
||||
UPGRADE_BASE_VERSION="0.3.0+v0.4.219" once merged+published).
|
||||
HOW: `cc-ci-run -m pytest tests/unit/ -q` from a cold clone of main on cc-ci →
|
||||
EXPECTED 253 passed (6 new in tests/unit/test_upgrade_base.py);
|
||||
`nix develop .#lint -c bash scripts/lint.sh` → EXPECTED `lint: PASS`.
|
||||
|
||||
### Green-run evidence (run 427, drone path)
|
||||
|
||||
- Trigger: PR #2 comment 14342 (`!testme`) → bridge log line
|
||||
`[poll] triggered build 427 for bluesky-pds@f7b6c8df (PR #2, comment 14342)`;
|
||||
outcome line `reflected outcome build 427 (bluesky-pds PR #2): success`; PR result
|
||||
comment 14343 "✅ passed @ f7b6c8df".
|
||||
- HOW: `ssh cc-ci 'cat /var/lib/cc-ci-runs/427/results.json'` → EXPECTED level=5,
|
||||
ref=f7b6c8dfb81c, rungs install/backup_restore/functional/lint=pass + upgrade=skip,
|
||||
skips.intentional.upgrade=<declared reason>, flags clean_teardown+no_secret_leak true.
|
||||
- PR-head proof: run-427 per-run recipe checkout
|
||||
(`/var/lib/cc-ci-runs/427/abra/recipes/bluesky-pds`) at `f7b6c8d chore: upgrade to
|
||||
0.3.0+v0.4.219`, compose.yml line 6 image=…:0.4.219.
|
||||
- Visuals: https://ci.commoninternet.net/runs/427/summary.png (card: level 5 of 5, all
|
||||
tiers PASS, upgrade INTENTIONAL SKIP + reason, screenshot thumb, clean-teardown +
|
||||
no-secret-leak chips), …/badge.svg ("cc-ci: level 5", green),
|
||||
…/screenshot.png (the PDS landing page described above).
|
||||
|
||||
### WHERE
|
||||
|
||||
- cc-ci main @ 72b3d6c (harness change e9745c8; journal/decisions 72b3d6c).
|
||||
- Mirror PR #2: https://git.autonomic.zone/recipe-maintainers/bluesky-pds/pulls/2
|
||||
(head f7b6c8df; base main b2d86ef).
|
||||
- Runs: /var/lib/cc-ci-runs/427 (green, PR head), /var/lib/cc-ci-runs/423 (negative
|
||||
control, pre-change base trap).
|
||||
- Upstream registry: cc-ci-plan/upstream/bluesky-pds.md @ plan-repo f395247.
|
||||
|
||||
## Operator summary
|
||||
|
||||
**What was wrong.** bluesky-pds could not deploy at all: the app crash-looped
|
||||
`Cannot find module '/app/index.js'`. The recipe pins the MOVING image tag
|
||||
`ghcr.io/bluesky-social/pds:0.4`, and upstream now republishes that tag with main-branch
|
||||
builds (currently @atproto/pds 0.5.1 on Node 24, where the service entrypoint moved to
|
||||
`/app/index.ts` — `index.js` no longer exists). The recipe's entrypoint override
|
||||
(`exec node --enable-source-maps index.js`) can no longer resolve. This also silently
|
||||
broke BOTH previously published recipe versions (0.1.1+v0.4, 0.2.0+v0.4 — same moving
|
||||
pin), so no historical version can deploy anymore either.
|
||||
|
||||
**What the PR changes.** https://git.autonomic.zone/recipe-maintainers/bluesky-pds/pulls/2
|
||||
(branch `upgrade-0.3.0+v0.4.219`, head f7b6c8df), a 2-line compose.yml diff: pin the exact
|
||||
released tag `0.4.219` (newest released; classic Node 20 / index.js layout the recipe's
|
||||
entrypoint expects) and bump the version label to `0.3.0+v0.4.219`. Why not 0.5.1: it has
|
||||
no release tag (only the moving :0.4/latest + sha- tags from main) and needs an entrypoint
|
||||
migration; do that as a proper upgrade when upstream cuts a 0.5.x release tag (notes in
|
||||
cc-ci-plan/upstream/bluesky-pds.md). Proven at PR head via real drone CI: run 427 =
|
||||
**level 5** (install, backup/restore, functional, lint PASS; screenshot = real PDS landing
|
||||
page). The upgrade rung is a DECLARED intentional skip — there is no deployable published
|
||||
base to upgrade FROM (see above); declaration + reason in tests/bluesky-pds/recipe_meta.py.
|
||||
|
||||
**What to do post-merge.**
|
||||
1. Merge PR #2 (your call, as with immich PR#2 / plausible PR#3 — all left open).
|
||||
2. Publish the version per recipe convention (annotated tag `0.3.0+v0.4.219` /
|
||||
`abra recipe release`) so `abra recipe versions` lists a deployable version again.
|
||||
3. After the tag is published: in cc-ci `tests/bluesky-pds/recipe_meta.py`, DROP the
|
||||
`EXPECTED_NA["upgrade"]` declaration and set
|
||||
`UPGRADE_BASE_VERSION = "0.3.0+v0.4.219"` — the upgrade rung then re-activates from
|
||||
the first deployable base (the older broken tags must never be auto-picked as base).
|
||||
4. Canonical/warm: nothing to reseed — bluesky-pds has no canonical
|
||||
(/var/lib/ci-warm has no entry); the normal promote-on-green flow mints one on the
|
||||
first green run post-merge.
|
||||
5. Never re-pin this recipe to `:0.4`/`latest` — upstream demonstrably republishes the
|
||||
minor tag (registry notes: cc-ci-plan/upstream/bluesky-pds.md).
|
||||
@ -1,6 +1,71 @@
|
||||
# STATUS — Phase lvl5 (L5 lint rung + de-cap)
|
||||
|
||||
Phase: lvl5 — OPEN (bootstrapped 2026-06-11)
|
||||
Gate: none claimed yet
|
||||
In flight: P1 — level.py new semantics + lint executor design (abra lint behavior probe on CI host first)
|
||||
Blockers: none
|
||||
## DONE
|
||||
|
||||
Phase complete 2026-06-11: M1 PASS (cfc87fd) + M2 PASS (13cad1f), both <24h, no VETO.
|
||||
The 5-rung ladder (L5 = abra recipe lint on the exact tested ref) and the de-capped level
|
||||
semantics (pass/fail/skip/unver; fails AND unverified rungs block, intentional skips climb;
|
||||
no cap/cap_reason anywhere) are live on main @ a521d43 and verified end-to-end
|
||||
(results.json schema 2 → card → dashboard → badge → PR comment, drone path included).
|
||||
Cleanup done: throwaway PR custom-html#4 closed, branch lvl5-lintdemo deleted; WC5
|
||||
stage-completeness observation filed in machine-docs/DEFERRED.md.
|
||||
|
||||
## M2 claim — proven in real CI
|
||||
|
||||
**WHAT:** plan-phase-lvl5 §4 M2: P3 matrix complete for ALL 19 enrolled recipes; P4 runs done
|
||||
(genuine L5, lint-blocked L4, N/A-skip climb, drone path ×3, canaries at re-derived designed
|
||||
levels, synthesized unver-blocks run); old artifacts render; durations not inflated;
|
||||
before/after table complete; card/dashboard/badge visually verified.
|
||||
|
||||
**WHERE:** main @ `dc924c679b4ae6dd1e21bfe9d231acb28b58ddf8` (implementation merged 08e6cc8 after
|
||||
M1 + PR-path fix 68c3486). Evidence runs (all artifacts at
|
||||
`https://ci.commoninternet.net/runs/<n>/{results.json,summary.png,badge.svg,lint.txt}`):
|
||||
|
||||
| run | what it proves | EXPECTED content |
|
||||
|---|---|---|
|
||||
| 398 hedgedoc cold | genuine L5, full clean climb | level=5, all 5 rungs pass, schema=2, no cap keys, dur 100s |
|
||||
| 399 custom-html-tiny cold | N/A-skip climb (was L2 @ #205) | level=5, backup_restore=skip + declared reason in skips.intentional, dur 45s |
|
||||
| 405 custom-html PR4 (!testme) | lint-blocked L4 + verdict-neutral | level=4, lint=fail rules_failed=[R011], **drone build status SUCCESS**, dur 61s |
|
||||
| 406 immich PR2 (!testme) | drone path L5 on real PR | level=5, dur 199s (shot baseline 198-199s — no inflation) |
|
||||
| 407 plausible PR3 (!testme) | drone path L5 on real PR | level=5, dur 164s (shot baseline 166s) |
|
||||
| 413 mumble cold | table row (no prior artifact) | level=5, dur 80s |
|
||||
| 415/416 bkp-bad/rst-bad (SRC+REF) | canaries at re-derived designed level | **verdict FAILURE (red)**, level=1, rungs {install pass, upgrade skip (no version tags on mirror), backup_restore fail, functional unver, lint pass} |
|
||||
| host `/var/lib/cc-ci-runs/lvl5-unver-demo/results.json` | synthesized unver-blocks (mission ex. #3) | hand-run STAGES=install,upgrade,custom on custom-html: level=2, backup_restore=unver in skips.unintentional, functional+lint pass above it |
|
||||
|
||||
**HOW to verify (cold):**
|
||||
1. Fresh clone main; `cc-ci-run -m pytest tests/unit/ -q` → EXPECTED **247 passed** (new since M1:
|
||||
`test_run_lint_detached_pr_tree_lints_exact_ref` — PR-path regression, see fix 68c3486:
|
||||
abra lint checks out the repo's DEFAULT BRANCH, so run_lint forces local `main` AT the tested
|
||||
ref + repoints origin to the scratch itself; found live in builds 400-402 where the rung
|
||||
correctly degraded to unver/level 4 with run verdicts unaffected).
|
||||
`nix develop .#lint --command bash scripts/lint.sh` → PASS.
|
||||
2. Fetch each run's results.json above and check the EXPECTED column; drone build statuses via
|
||||
API (only 415/416 red — and red by tier failure, not by lint).
|
||||
3. Visuals: Read `summary.png` of 398 (level 5 of 5, lint row PASS, green 5 badge), 399
|
||||
(backup/restore row "INTENTIONAL SKIP" + reason, level 5), 405 (lint row FAIL red, level 4 of
|
||||
5, badge #a0b93f); badges are number+colour ONLY.
|
||||
4. Old artifacts: `/runs/370/{results.json,summary.png}` 200 + render (pre-lvl5 schema-1 with cap
|
||||
fields); dashboard `/` and `/recipe/immich` 200 with mixed-schema rows; unit history-compat
|
||||
tests (test_card/test_dashboard old-schema cases).
|
||||
5. lint.txt served: `/runs/398/lint.txt` 200 (full abra table; rc/status header).
|
||||
6. P3 matrix + §2.9 before/after table: BACKLOG-lvl5.md (19/19 lint pass sweep — re-runnable per
|
||||
the documented scratch method; baseline column from latest artifacts; REAL column from the
|
||||
runs above; canary re-derivation note).
|
||||
7. Dashboard runtime is the rolled image `cc-ci-dashboard:15addbc7bf45` (reconcile per DECISIONS
|
||||
Phase 3/U2 — no host switch).
|
||||
|
||||
**Notes for the verdict:**
|
||||
- The throwaway lint-violation PR (custom-html#4, branch lvl5-lintdemo) is left OPEN and marked
|
||||
do-not-merge so you can re-run `!testme` independently; Builder will close branch+PR after M2.
|
||||
- Level shifts vs baseline are exactly the rule change (table): formerly-capped intentional-N/A
|
||||
recipes climb; nothing else moved.
|
||||
- Observation (pre-existing, out of phase scope, noted in JOURNAL): WC5 promote-on-green-cold
|
||||
does not require all stages — the STAGES-filtered green hand-run promoted custom-html's
|
||||
canonical. Filed as a JOURNAL note; flag if you want it as a finding.
|
||||
|
||||
---
|
||||
|
||||
## (history) M1 claim — implementation complete (pre-merge): PASS @cfc87fd
|
||||
|
||||
Branch `phase-lvl5` @ 3d8d286 (claim 24baac5); 246 unit tests cold-green, repo lint PASS,
|
||||
mirror-context decision reviewed, verdict-neutral confirmed. Merged to main 08e6cc8.
|
||||
|
||||
@ -116,7 +116,7 @@ _This table is GENERATED from the `runner/harness/meta.py` KEYS registry by `scr
|
||||
| `DEPLOY_TIMEOUT` | `int` | `600` | Max seconds to wait for swarm convergence per deploy. |
|
||||
| `HTTP_TIMEOUT` | `int` | `300` | Max seconds to wait for HTTP health after convergence. |
|
||||
| `BACKUP_CAPABLE` | `bool` | `None` | Override the backup-tier capability auto-detect (compose `backupbot.backup` labels). `False` forces an intentional skip of the backup/restore rung; `True` forces the tier on; unset = auto-detect. |
|
||||
| `EXPECTED_NA` | `dict` | `None` | Declare a non-run rung an INTENTIONAL skip: `{rung: reason}` — the level climbs past it; an undeclared non-run rung is *unverified* and blocks the level above it (classification table: machine-docs/DECISIONS.md phase lvl5). Never overrides an exercised pass/fail; the `lint` rung has no escape hatch. |
|
||||
| `EXPECTED_NA` | `dict` | `None` | Declare a non-run rung an INTENTIONAL skip: `{rung: reason}` — the level climbs past it; an undeclared non-run rung is *unverified* and blocks the level above it (classification table: machine-docs/DECISIONS.md phase lvl5). Never overrides an exercised pass/fail; the `lint` rung has no escape hatch. Declaring `upgrade` also suppresses the upgrade-tier BASE deploy — the single deploy is the PR head itself — for recipes whose published versions exist but are genuinely undeployable (phase bsky). |
|
||||
| `READY_PROBE` | `hook` | `None` | Callable `(ctx) -> [probe, ...]` returning extra readiness probes, run after install AND after upgrade: HTTP `{host, path, ok}` or TCP `{tcp_host, tcp_port, stable}`. |
|
||||
| `UPGRADE_BASE_VERSION` | `str` | `None` | Exact published tag overriding the upgrade tier's base (default: `recipe_versions[-2]`). |
|
||||
| `BACKUP_VERIFY` | `hook` | `None` | Callable `(ctx) -> bool` post-backup data-capture check; `False` re-runs the backup (truncated-dump race guard), retried up to 3 attempts. |
|
||||
|
||||
@ -1353,3 +1353,33 @@ recipe"); pass iff the table rendered clean; anything else unver + loud log. Har
|
||||
(observed ~0.7s); executor runs before the tiers (tree at tested ref), double-wrapped, R7
|
||||
verdict-neutral. Full output → run artifact `lint.txt` (dashboard-served); status + failing
|
||||
rule ids → results.json `lint`.
|
||||
|
||||
**bluesky-pds re-pin decision (phase bsky, 2026-06-11).** The recipe pinned the moving tag
|
||||
`ghcr.io/bluesky-social/pds:0.4`, which upstream now republishes with main-branch builds
|
||||
(currently @atproto/pds 0.5.1, Node 24, `/app/index.ts` — no `index.js`), breaking the
|
||||
recipe's entrypoint override (`exec node --enable-source-maps index.js`). Fix: pin the
|
||||
newest RELEASED exact tag `0.4.219` (Node 20.20, `/app/index.js`, CMD identical to the
|
||||
recipe's exec line — entrypoint stays valid unchanged) and bump the version label
|
||||
`0.2.0+v0.4` → `0.3.0+v0.4.219` (minor bump for an upstream pin change, immich-PR#2
|
||||
precedent). REJECTED: tracking 0.5.1 (only exists as moving/sha- tags built from main —
|
||||
no release tag; would also require entrypoint `index.ts` migration against an unreleased
|
||||
version); digest-suffix pinning (abra survey/upgrade tooling chokes on tag@digest — see
|
||||
immich standing note). When upstream cuts real 0.5.x release tags, upgrade properly
|
||||
(entrypoint will then need the index.ts/Node-24 migration — recorded in
|
||||
cc-ci-plan/upstream/bluesky-pds.md). Never re-pin to `:0.4`/`latest`/minor tags.
|
||||
|
||||
**EXPECTED_NA["upgrade"] suppresses the upgrade-tier base deploy (phase bsky, 2026-06-11).**
|
||||
The deploy-once design deploys the upgrade BASE (previous published version) and only the
|
||||
upgrade tier chaos-redeploys the PR head — so a recipe whose published versions ALL became
|
||||
undeployable (bluesky-pds: every tag pins moving `ghcr.io/bluesky-social/pds:0.4`, which
|
||||
upstream republished with incompatible main builds) fails INSTALL at the base before the PR
|
||||
head is ever exercised, and no UPGRADE_BASE_VERSION value can help (it must be a published
|
||||
tag — they're all broken). Decision: declaring the upgrade rung in EXPECTED_NA (the existing
|
||||
intentional-skip mechanism) now ALSO makes upgrade_base() return None → the single deploy is
|
||||
the PR head itself; the upgrade tier records "skip"; derive_rungs classifies it as the
|
||||
DECLARED intentional skip with the recipe's reason (results.json skips.intentional). NOT a
|
||||
gate weakening: the rung is never reported pass, the skip + reason are fully visible, and the
|
||||
declaration is evidence-backed in the recipe_meta comment + upstream registry; it is the only
|
||||
way to exercise a PR at all for a recipe in this state. Re-enable path documented per-recipe
|
||||
(bluesky: drop EXPECTED_NA + set UPGRADE_BASE_VERSION="0.3.0+v0.4.219" once merged+published).
|
||||
Locked by tests/unit/test_upgrade_base.py.
|
||||
|
||||
@ -118,6 +118,7 @@ before the build is called done) — but does **not** force closure.
|
||||
- **Linked IDEA:** —
|
||||
|
||||
### 2026-05-28 — uptime-kuma create-a-monitor (§4.3 prescribed)
|
||||
- [x] **RE-ENTERED @2026-06-11:** operator approved — executing as phase `kuma` (cc-ci-plan/plan-phase-kuma-monitor.md).
|
||||
- [ ] **What:** Add a test that completes uptime-kuma's first-run setup wizard via Socket.IO,
|
||||
logs in to obtain a JWT, creates a monitor (`monitor add` Socket.IO emit), and asserts the
|
||||
monitor appears in the listed-monitors response.
|
||||
@ -210,6 +211,7 @@ before the build is called done) — but does **not** force closure.
|
||||
(none yet — append `### YYYY-MM-DD — <slug> CLOSED (commit/PR)` here when re-entered.)
|
||||
|
||||
### 2026-05-28 — plausible (Q4.7) recipe enrollment
|
||||
- [x] **CLOSED @2026-06-11 (operator housekeeping):** overtaken — plausible is enrolled and running in CI (§4.3 floor `71af595`); the full-lifecycle remainder is the Q4.7b entry below (recipe PR#3 green, operator merge pending).
|
||||
- [ ] **What:** Enroll plausible in cc-ci with parity health_check + ≥2 specific tests (per
|
||||
plan §4.3: "track a test event, query it back"). `tests/plausible/recipe_meta.py` +
|
||||
`tests/plausible/functional/test_health_check.py` are drafted (commit pending) but the
|
||||
@ -237,6 +239,7 @@ before the build is called done) — but does **not** force closure.
|
||||
Defensible defer; lift when the operator wants the deeper coverage OR Phase-4 reviews.
|
||||
|
||||
### 2026-05-29 — immich recipe needs a pg_dump backup hook for reliable DB restore (P4)
|
||||
- [x] **CLOSED @2026-06-11:** cc-ci-authored immich recipe PR#2 (pg_dump hook) verified green; operator confirmed 2026-06-11 — merge pending, no further loop work.
|
||||
- [ ] **What:** immich's upstream recipe backs up the LIVE postgres data VOLUME via restic
|
||||
(`backupbot.backup=true` on `database`, no pg_dump hook), so a DB row does NOT survive
|
||||
`abra app restore` (diagnosed: seed→backup→drop→restore→row absent; app healthy). Real
|
||||
@ -256,6 +259,7 @@ before the build is called done) — but does **not** force closure.
|
||||
- **Linked IDEA:** —
|
||||
|
||||
### 2026-05-29 — discourse: upstream recipe pins removed bitnami images (undeployable)
|
||||
- [x] **CLOSED @2026-06-11 (operator housekeeping):** superseded — discourse is enrolled and runs the full lifecycle in CI (L4 baseline run 184, 2026-06-05); the bitnami-pin blocker no longer applies.
|
||||
- [ ] **What:** discourse (Q4.6) cannot be enrolled/tested because the recipe pins
|
||||
`image: bitnami/discourse:<tag>` (app + sidekiq) and **Docker Hub no longer serves any
|
||||
`bitnami/discourse:*` tag** (bitnami's 2024/2025 legacy migration). Proven on cc-ci:
|
||||
@ -282,6 +286,7 @@ before the build is called done) — but does **not** force closure.
|
||||
- **Linked IDEA / BACKLOG:** Q4.6.
|
||||
|
||||
### 2026-05-29 — mailu: no backup config (P4 N/A) — recipe-PR to add backupbot
|
||||
- [x] **RE-ENTERED @2026-06-11:** operator approved the backupbot recipe-PR route — executing as phase `mailu` (cc-ci-plan/plan-phase-mailu-backup.md).
|
||||
- [ ] **What:** mailu (Q4.9) ships **no `backupbot.backup` label** on any service, so cc-ci's
|
||||
backup/restore tiers cleanly SKIP (`backup_capable=False`) — P4 (backup data-integrity) is N/A
|
||||
for mailu as published (no backup mechanism to exercise). Durable fix = a recipe-PR adding
|
||||
@ -296,6 +301,7 @@ before the build is called done) — but does **not** force closure.
|
||||
- **Linked IDEA / BACKLOG:** Q4.9.
|
||||
|
||||
### 2026-05-29 — drone (Q4.10) blocked on host /etc/timezone deploy (gitea SCM dep) + scoped integration
|
||||
- [x] **RE-ENTERED @2026-06-11:** operator approved — executing as phase `drone` (cc-ci-plan/plan-phase-drone-enroll.md); P0 host /etc/timezone deploy is orchestrator-owned.
|
||||
- [ ] **What:** drone (Q4.10, LAST §5 recipe) cannot be enrolled until two things land:
|
||||
(1) **HOST FIX — operator-deploy needed:** drone is a CI server that REQUIRES a git-provider SCM
|
||||
to boot; the only viable dep is **gitea**, which the recipe binds `/etc/timezone:ro` from the
|
||||
@ -322,6 +328,7 @@ before the build is called done) — but does **not** force closure.
|
||||
- **Linked IDEA / BACKLOG:** Q4.10; JOURNAL-2 f86a58a; commit 3bde76f.
|
||||
|
||||
### 2026-05-30 — plausible Q4.7 full (recipe-PR Q4.7b: fix ClickHouse entrypoint wget restart-storm)
|
||||
- [x] **CLOSED @2026-06-11:** recipe PR#3 (ClickHouse entrypoint + backup fixes) verified GREEN at PR head; operator confirmed 2026-06-11 — merge pending. Post-merge follow-up: full lifecycle on main to formally claim Q4.7.
|
||||
- [ ] **What:** Fix the recipe `entrypoint.clickhouse.sh` so ClickHouse boots reliably, then run
|
||||
plausible's FULL lifecycle (`install,upgrade,backup,restore,custom`) green + claim Q4.7. Suite
|
||||
authored (`tests/plausible/` ops + test_backup/restore/upgrade + event-roundtrips); §4.3 floor
|
||||
@ -335,8 +342,8 @@ before the build is called done) — but does **not** force closure.
|
||||
- **Re-entry trigger:** Builder authors recipe-PR Q4.7b (cache tarball on a volume / wget
|
||||
retry+backoff / drop `2>/dev/null` / `set +e` w/ fallback), then runs plausible-full green + claims.
|
||||
- **Linked:** REVIEW-2 `e850281` (root-cause + DENY), `71af595` (§4.3 floor); DECISIONS 2026-05-30.
|
||||
- discourse upgrade-HC1 @7ae7b0f stamps prev-base tag commit (eb96de94+U) on BOTH old+new harness since ~06-10 (baseline 184 was L4 on 06-05); harness-neutral (rcust exonerated, M2-closed) but abra stamp-resolution mechanism UNATTRIBUTED — worth a standalone dig outside rcust. Evidence: /var/lib/cc-ci-runs/{m2p-discourse,ab-discourse-7ae7b0f-oldmain}, JOURNAL-rcust 2026-06-11.
|
||||
- bluesky-pds: UPSTREAM IMAGE BREAKAGE (non-rcust, M2-justified exclusion from baseline match).
|
||||
- [RE-ENTERED @2026-06-11 → phase `dstamp` (cc-ci-plan/plan-phase-dstamp-discourse-drift.md)] discourse upgrade-HC1 @7ae7b0f stamps prev-base tag commit (eb96de94+U) on BOTH old+new harness since ~06-10 (baseline 184 was L4 on 06-05); harness-neutral (rcust exonerated, M2-closed) but abra stamp-resolution mechanism UNATTRIBUTED — worth a standalone dig outside rcust. Evidence: /var/lib/cc-ci-runs/{m2p-discourse,ab-discourse-7ae7b0f-oldmain}, JOURNAL-rcust 2026-06-11.
|
||||
- [RE-ENTERED @2026-06-11 → phase `bsky`] ✅ **RESOLVED @2026-06-11 (phase bsky, Builder):** root cause = upstream republishes the MOVING tag `:0.4` with main-branch builds (now @atproto/pds 0.5.1, Node 24, `/app/index.ts` — no `index.js`), breaking the recipe's entrypoint override. Fix PR open (operator merges): **recipe-maintainers/bluesky-pds PR #2** (`upgrade-0.3.0+v0.4.219`, head f7b6c8df — exact-pin `0.4.219` + version-label bump). Proven green at PR head via real drone CI: run 427 **level 5** (install/backup_restore/functional/lint PASS; upgrade = declared intentional skip — no deployable published base, both old tags pin the republished `:0.4`; negative control run 423). Screenshot real (PDS landing page). The shot-phase deploy-gated N/A is lifted on the PR runs. Upstream registry: cc-ci-plan/upstream/bluesky-pds.md; decisions: DECISIONS.md 2026-06-11 (pin choice + EXPECTED_NA-upgrade base suppression). Both the re-pin follow-up AND the rcust M2 exclusion note are hereby closed with these pointers. Original entry follows: bluesky-pds: UPSTREAM IMAGE BREAKAGE (non-rcust, M2-justified exclusion from baseline match).
|
||||
The app container crash-loops `Error: Cannot find module '/app/index.js'` (MODULE_NOT_FOUND,
|
||||
Node v24.15.0) under the recipe's pinned tag on EVERY current run — new main @ mirror head
|
||||
(m2r-bluesky-pds), new main serial re-run (m2rr-bluesky-pds), AND old pre-rcust main @ old
|
||||
@ -360,3 +367,13 @@ before the build is called done) — but does **not** force closure.
|
||||
Evidence: /tmp/mumble-probe{2,3,4}.out + /tmp/mumble-orch{4,5}.log on cc-ci (90s DOM/console/
|
||||
network observation; websockify reachable, /ws & /websocket 404 from websockify itself);
|
||||
/var/lib/cc-ci-runs/shot-proof-mumble/screenshot.png (L4 run, loader frame).
|
||||
|
||||
## WC5 promote-on-green-cold ignores stage completeness (filed 2026-06-11, Builder, phase lvl5)
|
||||
|
||||
Observed during the lvl5 unver-blocks proof: a GREEN hand-run with `STAGES=install,upgrade,custom`
|
||||
(backup/restore excluded) on latest still advanced custom-html's warm canonical —
|
||||
`should_promote_canonical` checks green+cold+latest but not that ALL stages ran. Pre-existing
|
||||
behavior (not introduced or worsened by lvl5; Adversary concurs it is not a finding). Only
|
||||
reachable via the operator/dev STAGES escape — production drone runs always run all stages.
|
||||
**Needed from operator:** decide whether promote should additionally require the full stage set
|
||||
(one-line guard in `should_promote_canonical`), or whether dev hand-runs promoting is acceptable.
|
||||
|
||||
@ -128,14 +128,35 @@ def run_lint(recipe: str, ref: str | None, out_dir: str | None) -> dict:
|
||||
text=True,
|
||||
timeout=LINT_TIMEOUT,
|
||||
)
|
||||
if ref:
|
||||
subprocess.run(
|
||||
["git", "-C", clone, "checkout", "-f", "--quiet", ref],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=LINT_TIMEOUT,
|
||||
)
|
||||
# abra lint SELECTS AND CHECKS OUT THE REPO'S DEFAULT BRANCH before linting (observed
|
||||
# live, build 400-402: a clone of a detached-HEAD per-run tree has no local branch →
|
||||
# FATA "failed to select default branch"; and if a default branch existed at some OTHER
|
||||
# commit, abra would silently lint THAT, not the tested ref). So: force a local `main`
|
||||
# AT exactly the tested ref and make it the default everywhere abra could look —
|
||||
# HEAD, and origin (repointed to the scratch itself, which also turns abra's tag
|
||||
# force-fetch into an offline no-op; the run's true tags were already cloned in).
|
||||
subprocess.run(
|
||||
["git", "-C", clone, "checkout", "-f", "--quiet", "-B", "main"]
|
||||
+ ([ref] if ref else []),
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=LINT_TIMEOUT,
|
||||
)
|
||||
subprocess.run(
|
||||
["git", "-C", clone, "remote", "set-url", "origin", clone],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=LINT_TIMEOUT,
|
||||
)
|
||||
subprocess.run(
|
||||
["git", "-C", clone, "remote", "set-head", "origin", "main"],
|
||||
check=False, # cosmetic: helps any origin-HEAD-based default-branch lookup
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=LINT_TIMEOUT,
|
||||
)
|
||||
# catalogue: R006 (published catalogue version) reads it; servers: harmless, some abra
|
||||
# paths stat it. Symlink the live ones (read-only use).
|
||||
for shared in ("catalogue", "servers"):
|
||||
|
||||
@ -76,7 +76,7 @@ KEYS: tuple[Key, ...] = (
|
||||
"EXPECTED_NA",
|
||||
"dict",
|
||||
None,
|
||||
"Declare a non-run rung an INTENTIONAL skip: `{rung: reason}` — the level climbs past it; an undeclared non-run rung is *unverified* and blocks the level above it (classification table: machine-docs/DECISIONS.md phase lvl5). Never overrides an exercised pass/fail; the `lint` rung has no escape hatch.",
|
||||
"Declare a non-run rung an INTENTIONAL skip: `{rung: reason}` — the level climbs past it; an undeclared non-run rung is *unverified* and blocks the level above it (classification table: machine-docs/DECISIONS.md phase lvl5). Never overrides an exercised pass/fail; the `lint` rung has no escape hatch. Declaring `upgrade` also suppresses the upgrade-tier BASE deploy — the single deploy is the PR head itself — for recipes whose published versions exist but are genuinely undeployable (phase bsky).",
|
||||
),
|
||||
Key(
|
||||
"READY_PROBE",
|
||||
|
||||
@ -88,6 +88,38 @@ def sso_dep_unverified(declared, deps_ready: bool, requires_deps_skipped: int) -
|
||||
return bool(declared) and not deps_ready and requires_deps_skipped > 0
|
||||
|
||||
|
||||
def upgrade_base(stages, meta, recipe: str) -> str | None:
|
||||
"""Deploy-once base version decision (pure given meta + the published-version lookup):
|
||||
previous published version when the upgrade tier will run and one exists (so upgrade goes
|
||||
previous→target in place), else None (the caller falls back to the target / PR head).
|
||||
(DECISIONS.)
|
||||
|
||||
A recipe may override the base via recipe_meta UPGRADE_BASE_VERSION when the harness default
|
||||
(recipe_versions[-2]) is NOT the PR's true predecessor — e.g. a PR that adds a version ABOVE the
|
||||
newest published tag, where the correct base is [-1] (the newest published), not [-2]. The
|
||||
override must be an exact published version tag (deployed as a pinned base). (Adversary §7.1.)
|
||||
|
||||
A recipe that declares the upgrade rung in EXPECTED_NA gets NO base: published versions may
|
||||
exist yet be genuinely undeployable — e.g. bluesky-pds, where every published tag pins the
|
||||
moving image tag `:0.4` that upstream republished with incompatible main builds, so no
|
||||
published version can come up as an upgrade base (phase bsky, DECISIONS). Deploying one would
|
||||
fail the INSTALL tier before the PR-head code is ever exercised. With no base, the single
|
||||
deploy is the PR head itself and the upgrade tier records "skip", which derive_rungs
|
||||
classifies as the DECLARED intentional skip (reason from EXPECTED_NA — visible in
|
||||
results.json `skips.intentional`, never reported as a pass)."""
|
||||
if "upgrade" not in stages:
|
||||
return None
|
||||
if "upgrade" in (meta.EXPECTED_NA or {}):
|
||||
print(
|
||||
"== upgrade tier: declared EXPECTED_NA['upgrade'] — no upgrade base will be "
|
||||
f"deployed; the single deploy is the target/PR head. Reason: "
|
||||
f"{(meta.EXPECTED_NA or {}).get('upgrade')}",
|
||||
flush=True,
|
||||
)
|
||||
return None
|
||||
return meta.UPGRADE_BASE_VERSION or lifecycle.previous_version(recipe)
|
||||
|
||||
|
||||
def _truthy(v: str | None) -> bool:
|
||||
return str(v or "").strip().lower() in ("1", "true", "yes", "on")
|
||||
|
||||
@ -905,16 +937,7 @@ def main() -> int:
|
||||
|
||||
domain = naming.app_domain(recipe, os.environ.get("PR", "0"), ref)
|
||||
|
||||
# Deploy-once base version: previous published version when the upgrade tier will run and one
|
||||
# exists (so upgrade goes previous→target in place), else the target (current/$REF). (DECISIONS.)
|
||||
# A recipe may override the base via recipe_meta UPGRADE_BASE_VERSION when the harness default
|
||||
# (recipe_versions[-2]) is NOT the PR's true predecessor — e.g. a PR that adds a version ABOVE the
|
||||
# newest published tag, where the correct base is [-1] (the newest published), not [-2]. The
|
||||
# override must be an exact published version tag (deployed as a pinned base). (Adversary §7.1.)
|
||||
want_upgrade = "upgrade" in stages
|
||||
prev = (
|
||||
(meta.UPGRADE_BASE_VERSION or lifecycle.previous_version(recipe)) if want_upgrade else None
|
||||
)
|
||||
prev = upgrade_base(stages, meta, recipe)
|
||||
base = prev or target
|
||||
backup_cap = generic.backup_capable(recipe, meta)
|
||||
hook = discovery.install_steps(recipe, repo_local)
|
||||
@ -1091,7 +1114,7 @@ def main() -> int:
|
||||
junit_dir=junit_dir,
|
||||
)
|
||||
if prev
|
||||
else "skip" # only one published version → nothing to upgrade from
|
||||
else "skip" # no upgrade base: single published version, or declared EXPECTED_NA
|
||||
)
|
||||
# ---- BACKUP + RESTORE tiers (backup-capable only; else clean N/A) ----
|
||||
if "backup" in stages:
|
||||
@ -1274,7 +1297,7 @@ def main() -> int:
|
||||
records=records,
|
||||
results=results,
|
||||
backup_capable=backup_cap,
|
||||
has_upgrade_target=prev is not None, # structural: a previous published version exists
|
||||
has_upgrade_target=prev is not None, # structural: a deployable upgrade base exists
|
||||
lint=lint_result, # L5 rung (phase lvl5)
|
||||
clean_teardown=clean_teardown,
|
||||
no_secret_leak=True, # narrowed below by an actual scan of the serialised artifact
|
||||
|
||||
@ -6,3 +6,17 @@ HEALTH_PATH = "/xrpc/_health" # PDS health endpoint; returns {"version": ...} o
|
||||
HEALTH_OK = (200,)
|
||||
DEPLOY_TIMEOUT = 600
|
||||
HTTP_TIMEOUT = 600
|
||||
|
||||
# UPGRADE rung: published versions exist (0.1.1+v0.4, 0.2.0+v0.4) but BOTH pin the moving image
|
||||
# tag ghcr.io/bluesky-social/pds:0.4, which upstream republished with main-branch builds
|
||||
# (@atproto/pds 0.5.1, Node 24, /app/index.ts — no index.js), so NO published version can deploy
|
||||
# as an upgrade base anymore: the base crash-loops MODULE_NOT_FOUND before the PR head is ever
|
||||
# exercised (phase bsky root cause; cc-ci-plan/upstream/bluesky-pds.md). Declared intentional
|
||||
# until a fixed exact-pinned version (0.3.0+v0.4.219, mirror PR #2) is merged AND published —
|
||||
# then DROP this and set UPGRADE_BASE_VERSION = "0.3.0+v0.4.219" so the upgrade rung is
|
||||
# exercised again from the first deployable base.
|
||||
EXPECTED_NA = {
|
||||
"upgrade": "no deployable upgrade base: every published version pins the moving tag "
|
||||
"pds:0.4, which upstream republished with incompatible main builds (index.js removed) — "
|
||||
"re-enable via UPGRADE_BASE_VERSION once a fixed version is published post-merge",
|
||||
}
|
||||
|
||||
@ -183,6 +183,41 @@ def test_run_lint_missing_recipe_is_unver_not_raise(tmp_path, monkeypatch):
|
||||
assert (tmp_path / "artifacts" / "lint.txt").exists()
|
||||
|
||||
|
||||
def test_run_lint_detached_pr_tree_lints_exact_ref(tmp_path, monkeypatch):
|
||||
# PR-path regression (live builds 400-402): the per-run tree sits at a DETACHED HEAD (the PR
|
||||
# sha), and abra lint selects+checks out the repo's DEFAULT BRANCH before linting. run_lint
|
||||
# must (a) not FATA on the branchless clone, and (b) make the default branch BE the tested
|
||||
# ref — never some other commit's content. Source repo: branch main at C1, detached at C2
|
||||
# (the "PR head", which adds marker-c2). The shim passes only if the linted tree contains
|
||||
# marker-c2 AND HEAD is a local branch.
|
||||
repo = _mkrecipe(tmp_path)
|
||||
(repo / "marker-c2.txt").write_text("pr head\n")
|
||||
subprocess.run(["git", "add", "."], cwd=repo, check=True)
|
||||
subprocess.run(
|
||||
["git", "-c", "user.email=t@t", "-c", "user.name=t", "commit", "-qm", "c2"],
|
||||
cwd=repo,
|
||||
check=True,
|
||||
)
|
||||
c2 = subprocess.run(
|
||||
["git", "rev-parse", "HEAD"], cwd=repo, check=True, capture_output=True, text=True
|
||||
).stdout.strip()
|
||||
subprocess.run(["git", "branch", "-f", "main", "HEAD^"], cwd=repo, check=True)
|
||||
subprocess.run(["git", "checkout", "-q", c2], cwd=repo, check=True) # detached, like a PR run
|
||||
monkeypatch.setenv("ABRA_DIR", str(tmp_path / "abra"))
|
||||
out = TABLE_OK.replace("\r\n", "\\n")
|
||||
shim = (
|
||||
'C="$ABRA_DIR/recipes/fakerec"\n'
|
||||
'git -C "$C" symbolic-ref HEAD >&2 || { echo "FATA no default branch" >&2; exit 1; }\n'
|
||||
'[ -f "$C/marker-c2.txt" ] || { echo "FATA wrong ref linted" >&2; exit 1; }\n'
|
||||
f'printf "{out}"\nexit 0\n'
|
||||
)
|
||||
monkeypatch.setenv("PATH", _shim(tmp_path, shim) + os.pathsep + os.environ["PATH"])
|
||||
res = L.run_lint("fakerec", c2, str(tmp_path / "artifacts"))
|
||||
assert res == {"status": "pass", "detail": "", "rules_failed": []}
|
||||
txt = (tmp_path / "artifacts" / "lint.txt").read_text()
|
||||
assert "refs/heads/main" in txt # HEAD was a local default branch when abra ran
|
||||
|
||||
|
||||
def test_run_lint_abra_blowup_is_unver(tmp_path, monkeypatch):
|
||||
_mkrecipe(tmp_path)
|
||||
monkeypatch.setenv("ABRA_DIR", str(tmp_path / "abra"))
|
||||
|
||||
79
tests/unit/test_upgrade_base.py
Normal file
79
tests/unit/test_upgrade_base.py
Normal file
@ -0,0 +1,79 @@
|
||||
"""Unit tests for `run_recipe_ci.upgrade_base` — the deploy-once base-version decision.
|
||||
|
||||
Phase bsky: a recipe whose published versions ALL pin a moving image tag that upstream
|
||||
republished with incompatible builds (bluesky-pds) has no deployable upgrade base — deploying
|
||||
one fails the INSTALL tier before the PR head is ever exercised. The sanctioned escape hatch is
|
||||
the EXISTING declared-intentional-skip mechanism: EXPECTED_NA["upgrade"] now also suppresses
|
||||
the base deploy (single deploy = PR head; the tier records "skip"; derive_rungs classifies it
|
||||
intentional with the declared reason). These tests lock the decision matrix; derive_rungs'
|
||||
classification of the resulting skip is already covered in test_results.py.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
from types import SimpleNamespace
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "runner"))
|
||||
import run_recipe_ci # noqa: E402
|
||||
from harness import lifecycle # noqa: E402
|
||||
|
||||
ALL = {"install", "upgrade", "backup", "restore", "custom"}
|
||||
|
||||
|
||||
def _meta(expected_na=None, upgrade_base_version=None):
|
||||
return SimpleNamespace(EXPECTED_NA=expected_na, UPGRADE_BASE_VERSION=upgrade_base_version)
|
||||
|
||||
|
||||
def test_default_prev_published(monkeypatch):
|
||||
# upgrade in stages, ≥2 published versions, nothing declared → recipe_versions[-2]
|
||||
monkeypatch.setattr(lifecycle, "previous_version", lambda r: "0.1.0+v1")
|
||||
assert run_recipe_ci.upgrade_base(ALL, _meta(), "somerecipe") == "0.1.0+v1"
|
||||
|
||||
|
||||
def test_single_published_version_no_base(monkeypatch):
|
||||
monkeypatch.setattr(lifecycle, "previous_version", lambda r: None)
|
||||
assert run_recipe_ci.upgrade_base(ALL, _meta(), "somerecipe") is None
|
||||
|
||||
|
||||
def test_upgrade_not_in_stages_no_base(monkeypatch):
|
||||
monkeypatch.setattr(
|
||||
lifecycle,
|
||||
"previous_version",
|
||||
lambda r: (_ for _ in ()).throw(AssertionError("not consulted")),
|
||||
)
|
||||
assert run_recipe_ci.upgrade_base(ALL - {"upgrade"}, _meta(), "somerecipe") is None
|
||||
|
||||
|
||||
def test_upgrade_base_version_override_wins(monkeypatch):
|
||||
monkeypatch.setattr(
|
||||
lifecycle,
|
||||
"previous_version",
|
||||
lambda r: (_ for _ in ()).throw(AssertionError("not consulted")),
|
||||
)
|
||||
meta = _meta(upgrade_base_version="0.7.0+3.3.1")
|
||||
assert run_recipe_ci.upgrade_base(ALL, meta, "discourse") == "0.7.0+3.3.1"
|
||||
|
||||
|
||||
def test_expected_na_upgrade_suppresses_base(monkeypatch):
|
||||
# bluesky-pds shape: published versions exist but are undeployable — declared EXPECTED_NA
|
||||
# upgrade → NO base (single deploy is the PR head), even though previous_version would
|
||||
# return one and even if UPGRADE_BASE_VERSION is set (the declaration is the stronger,
|
||||
# documented fact).
|
||||
monkeypatch.setattr(lifecycle, "previous_version", lambda r: "0.1.1+v0.4")
|
||||
declared = {"upgrade": "no deployable upgrade base (moving tag republished)"}
|
||||
assert run_recipe_ci.upgrade_base(ALL, _meta(expected_na=declared), "bluesky-pds") is None
|
||||
assert (
|
||||
run_recipe_ci.upgrade_base(
|
||||
ALL, _meta(expected_na=declared, upgrade_base_version="0.2.0+v0.4"), "bluesky-pds"
|
||||
)
|
||||
is None
|
||||
)
|
||||
|
||||
|
||||
def test_expected_na_other_rung_does_not_suppress(monkeypatch):
|
||||
# an EXPECTED_NA for backup_restore (custom-html-tiny shape) must NOT touch the upgrade base
|
||||
monkeypatch.setattr(lifecycle, "previous_version", lambda r: "0.1.0+v1")
|
||||
meta = _meta(expected_na={"backup_restore": "stateless"})
|
||||
assert run_recipe_ci.upgrade_base(ALL, meta, "custom-html-tiny") == "0.1.0+v1"
|
||||
Reference in New Issue
Block a user