Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
0438a60
feat(gate): adapt route and adapt regression in the regen gate
MarkusNeusinger Oct 23, 2026
4b710e2
feat(review): record-only adaptability check and the review-free adap…
MarkusNeusinger Oct 23, 2026
416065f
feat(core): perturbation smoke runner and missing-value perturbations
MarkusNeusinger Oct 23, 2026
3425ac0
feat(workflows): record-only adaptability smoke run in impl-generate …
MarkusNeusinger Oct 23, 2026
eb3d074
feat(prompts): the Data block convention in the generator and all 15 …
MarkusNeusinger Oct 23, 2026
894d999
Merge pr2/gate into feat/adaptable-code-rules
MarkusNeusinger Oct 23, 2026
b95efac
Merge pr2/generate into feat/adaptable-code-rules
MarkusNeusinger Oct 23, 2026
765da94
Merge pr2/review into feat/adaptable-code-rules
MarkusNeusinger Oct 23, 2026
df4465a
Merge pr2/prompts into feat/adaptable-code-rules
MarkusNeusinger Oct 23, 2026
db86589
fix(pr2): integrate the four parts: pinned Python in impl-review, doc…
MarkusNeusinger Oct 23, 2026
733d226
docs(changelog): reference #12261 in the adaptable-code fragment
MarkusNeusinger Oct 23, 2026
05ec4a8
fix(pr2): Copilot review: fail closed on adapt records without static…
MarkusNeusinger Oct 23, 2026
602dd40
fix(pr2): Copilot round two: ECharts init order, loud-failure wording…
MarkusNeusinger Oct 23, 2026
4fd7f7c
fix(gate): the adapt route needs a pair rendered from the checked sou…
MarkusNeusinger Oct 23, 2026
1e75be4
fix(gate): cross-check the adapt record status and require the pair's…
MarkusNeusinger Oct 23, 2026
826347b
fix(gate): a static-only adapt regression needs no smoke run
MarkusNeusinger Oct 23, 2026
2a92fff
feat(gate): record the pre-review adapt route and label overruled routes
MarkusNeusinger Oct 23, 2026
16693de
fix(review): re-dispatch a review-free run that failed before its ver…
MarkusNeusinger Oct 23, 2026
0396dae
fix(generate): stage adapt/ with gsutil rsync so new/ and prev/ keep …
MarkusNeusinger Oct 23, 2026
ee321dd
fix(smoke-run): keep the Actions runtime out of model-written executions
MarkusNeusinger Oct 23, 2026
8da6249
fix(smoke-run): own skip token for a table sample with nothing to per…
MarkusNeusinger Oct 23, 2026
a423e7b
docs(prompts): one palette name, IMPRINT_PALETTE, in the convention
MarkusNeusinger Oct 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions .github/workflows/impl-generate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -850,6 +850,65 @@ jobs:
echo "::notice::Created metadata file: $METADATA_FILE (py${PYTHON_VERSION}, ${LIBRARY}==${LIBRARY_VERSION})"
echo "::notice::Implementation verified present, metadata committed"

# ========================================================================
# Adaptability smoke run (record-only, Python only)
# ========================================================================
# docs/concepts/adaptable-code.md, "Perturbation smoke run" and "The adapt
# route": the new source runs on perturbed tables (core/smoke_run.py run);
# on a regeneration the predecessor from origin/main is rendered in this
# same runner next to a fresh render of the new source (pair: both
# themes, `python -P {library}.py` with the theme env, as the generator's
# Step 3 renders), so impl-review's gate can measure render identity on
# raw renders from one environment, before "Process plot images"
# quantizes anything. Results land in $RUNNER_TEMP/adapt and "Upload to
# GCS Staging" stages them privately under adapt/. Record-only: no exit
# code gates, continue-on-error keeps a crash or a timeout from failing
# the job, and a missing artifact reads as `missing` in impl-review (no
# repair trigger, design "No repair trigger"). The other languages have
# no runner yet (design "Rollout" step 6), so the step skips them.
- name: Adaptability smoke run (record-only)
id: adapt_smoke
if: steps.inputs.outputs.language == 'python'
continue-on-error: true
timeout-minutes: 20
env:
SPEC_ID: ${{ steps.inputs.outputs.specification_id }}
LIBRARY: ${{ steps.inputs.outputs.library }}
run: |
OUT="$RUNNER_TEMP/adapt"; TOOLS="$RUNNER_TEMP/adapt-tools"
rm -rf "$OUT" "$TOOLS"; mkdir -p "$OUT/prev-src" "$TOOLS"
# The runner comes from this workflow's ref, never from the branch the
# generator wrote (the pattern of the regen_gate_header copy in "Create
# library metadata file"). Without it nothing runs and nothing is staged.
if ! git archive "$GITHUB_SHA" core | tar -x -C "$TOOLS" || [ ! -f "$TOOLS/core/smoke_run.py" ]; then
echo "::warning::adapt_smoke could not copy core/ from ${GITHUB_SHA}; no adaptability record is staged"
rm -rf "$OUT"
exit 0
fi
RUN=(env PYTHONPATH="$TOOLS" .venv/bin/python -P -m core.smoke_run)
IMPL="plots/${SPEC_ID}/implementations/python/${LIBRARY}.py"
SPEC="plots/${SPEC_ID}/specification.md"
"${RUN[@]}" run "$IMPL" --spec "$SPEC" --spec-id "$SPEC_ID" --library "$LIBRARY" \
--out "$OUT/new-smoke.json" || true
# A regeneration is the implementation file on origin/main, the test
# impl-review uses. The predecessor is written under its library's file
# name, so the runner applies that library's setup calls.
if git cat-file -e "origin/main:${IMPL}" 2>/dev/null; then
git show "origin/main:${IMPL}" > "$OUT/prev-src/${LIBRARY}.py"
# New first, then the predecessor: the generated code has exited
# before the predecessor's PNGs exist.
"${RUN[@]}" pair --new "$IMPL" --prev "$OUT/prev-src/${LIBRARY}.py" --library "$LIBRARY" \
--out-dir "$OUT" --out "$OUT/pair.json" || true
"${RUN[@]}" run "$OUT/prev-src/${LIBRARY}.py" --spec "$SPEC" --spec-id "$SPEC_ID" --library "$LIBRARY" \
--skip-if-static-fails --out "$OUT/prev-smoke.json" || true
fi
# The generator's Step 3c attempt log, when it ran the self-check.
if [ -f /tmp/anyplot-selfcheck.jsonl ]; then
cp /tmp/anyplot-selfcheck.jsonl "$OUT/selfcheck.jsonl"
fi
rm -rf "$OUT/prev-src"
echo "::notice::adapt_smoke spec=${SPEC_ID} lib=${LIBRARY} staged=$(cd "$OUT" && find . -type f | sed 's|^\./||' | sort | tr '\n' ' ')"

# ========================================================================
# Process images: optimize + thumbnail
# ========================================================================
Expand Down Expand Up @@ -1014,6 +1073,23 @@ jobs:
STAGING_PATH="gs://anyplot-images/staging/${SPEC_ID}/${LANGUAGE}/${LIBRARY}"
PUBLIC_URL="https://storage.googleapis.com/anyplot-images/staging/${SPEC_ID}/${LANGUAGE}/${LIBRARY}"

# Adaptability artifacts from "Adaptability smoke run (record-only)".
# An earlier run of this pair is cleared first, unconditionally, so a
# kept regeneration's prev-* and pair.json never outlive it. They stay
# private (no AllUsers ACL) and impl-merge never promotes them.
# rsync, not `cp -r dir/*`: copying a directory into a destination
# subdirectory that does not exist yet drops the directory's name
# (gsutil's naming rule), so new/plot-light.png and
# prev/plot-light.png could both land as adapt/plot-light.png.
# rsync names every object by its path under $ADAPT.
gsutil -m rm -r "${STAGING_PATH}/adapt" 2>/dev/null || true
ADAPT="$RUNNER_TEMP/adapt"
rm -rf "$ADAPT/prev-src" # left behind only when the smoke step was cut off
if [ -d "$ADAPT" ] && [ -n "$(ls -A "$ADAPT")" ]; then
gsutil -m -h "Cache-Control:no-store" rsync -r "$ADAPT" "${STAGING_PATH}/adapt" \
|| echo "::warning::adapt artifacts not staged; impl-review records them as missing"
fi

# Require both theme renders
if [ ! -f "$IMPL_DIR/plot-light.png" ] || [ ! -f "$IMPL_DIR/plot-dark.png" ]; then
echo "::error::Missing plot-light.png and/or plot-dark.png — cannot upload"
Expand Down
8 changes: 6 additions & 2 deletions .github/workflows/impl-merge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -413,8 +413,12 @@ jobs:
STAGING="gs://anyplot-images/staging/${SPEC_ID}/${LANGUAGE}/${LIBRARY}"
PRODUCTION="gs://anyplot-images/plots/${SPEC_ID}/${LANGUAGE}/${LIBRARY}"

# Copy from staging to production
gsutil -m -h "Cache-Control:public, max-age=604800" cp -r "${STAGING}/*" "${PRODUCTION}/" 2>/dev/null || echo "No staging files to promote"
# Copy from staging to production: the renders only (`plot-*`, no
# recursion). staging/.../adapt/ holds the smoke runs and the
# predecessor's raw renders that impl-generate staged for
# impl-review; they stay private, and the cleanup below removes
# them with the rest.
gsutil -m -h "Cache-Control:public, max-age=604800" cp "${STAGING}/plot-*" "${PRODUCTION}/" 2>/dev/null || echo "No staging files to promote"

# Make production files public
gsutil -m acl ch -r -u AllUsers:R "${PRODUCTION}/" 2>/dev/null || true
Expand Down
41 changes: 41 additions & 0 deletions .github/workflows/impl-repair.yml
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,33 @@ jobs:
echo "::notice::Processed both themes after repair (light + dark)"
ls -la "$IMPL_DIR/"

# The perturbation smoke run of the repaired file (core/smoke_run.py run),
# as impl-generate runs it on the generated one; "Upload repaired plot to
# GCS staging" replaces the staged adapt/new-smoke.json with it. No pair
# and no predecessor run: a gated regeneration never enters repair.
# Record-only, like impl-generate's step: no exit code gates, and
# continue-on-error keeps a crash or a timeout from failing the repair.
- name: Adaptability smoke run (record-only)
id: adapt_smoke
if: success() && steps.lang.outputs.language == 'python'
continue-on-error: true
timeout-minutes: 20
env:
SPEC_ID: ${{ inputs.specification_id }}
LIBRARY: ${{ inputs.library }}
run: |
OUT="$RUNNER_TEMP/adapt"; TOOLS="$RUNNER_TEMP/adapt-tools"
rm -rf "$OUT" "$TOOLS"; mkdir -p "$OUT" "$TOOLS"
# The runner comes from this workflow's ref, never from the PR branch.
if ! git archive "$GITHUB_SHA" core | tar -x -C "$TOOLS" || [ ! -f "$TOOLS/core/smoke_run.py" ]; then
echo "::warning::adapt_smoke could not copy core/ from ${GITHUB_SHA}; no adaptability record is staged"
exit 0
fi
IMPL="plots/${SPEC_ID}/implementations/python/${LIBRARY}.py"
env PYTHONPATH="$TOOLS" .venv/bin/python -P -m core.smoke_run run "$IMPL" \
--spec "plots/${SPEC_ID}/specification.md" --spec-id "$SPEC_ID" --library "$LIBRARY" \
--out "$OUT/new-smoke.json" || true

- name: Authenticate to GCP
if: success()
uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3
Expand Down Expand Up @@ -331,6 +358,20 @@ jobs:
fi
done

# The smoke run of the repaired file replaces the staged one (private,
# never promoted). Without a new one the stale record from before the
# repair goes, so impl-review reads `missing`, never a result for code
# that no longer exists.
if [ "$LANGUAGE" = "python" ]; then
SMOKE="$RUNNER_TEMP/adapt/new-smoke.json"
if [ -s "$SMOKE" ] && gsutil -h "Cache-Control:no-store" cp "$SMOKE" "${STAGING_PATH}/adapt/new-smoke.json"; then
echo "::notice::Staged the repaired file's adapt/new-smoke.json"
else
[ -s "$SMOKE" ] && echo "::warning::adapt/new-smoke.json not staged; impl-review records it as missing"
gsutil rm "${STAGING_PATH}/adapt/new-smoke.json" 2>/dev/null || true
fi
fi

rm -f /tmp/gcs-key.json

- name: Post repair status
Expand Down
Loading
Loading