Skip to content

Backpropagate v1.4.0

Choose a tag to compare

@github-actions github-actions released this 25 May 10:39
· 53 commits to main since this release
f5242af

Fixed

  • Multi-run validation no longer leaks eval-mode into the next run after an OOM. v1.3.x left the model stuck in eval() after a CUDA out of memory (or any exception escaping the validation loop) in MultiRunTrainer._compute_validation_loss. The very next training pass silently produced no gradient updates — operators saw "training completed" but the model didn't learn anything new. v1.4 wraps the validation body in try ... finally: model.train() so the train-mode invariant is restored even on exception. Operators hit by this on v1.3.x: re-run the affected multi-run from a clean checkpoint on v1.4. (BACKEND-B-003)
  • Trainer.save() warns instead of silently writing untrained weights. v1.3.x let an operator who did Trainer(...).load_model() then .save() write init-weight LoRA adapters to disk — same filename / adapter_config.json shape as a real fine-tune, no signal it was untrained. v1.4 tracks an internal _has_trained flag (set by train() on success) and emits one structured WARN log line at save() when the flag is False, naming the file path + the missing train() call. The save still completes (no gate / no exception) so existing tooling that pre-creates output dirs doesn't break, but operators get a visible "this adapter was never trained" cue. (BACKEND-B-004)
  • backprop replay + train + multi-run --override flags now thread through under MagicMock-patched test stubs. v1.3 added an introspection filter so the CLI wouldn't pass a kwarg that the constructor didn't declare. The filter looked only at named parameters and silently dropped every override when the constructor used **kwargs (which the real Trainer.__init__ does not, but MagicMock(spec=None) does). Under unittest.mock.MagicMock-patched Trainer/MultiRunTrainer tests, --override lora_r=32 would silently no-op. v1.4 extends the filter to detect VAR_KEYWORD and degrade to pass-through. Bare CLI calls behaved correctly; the bug only surfaced under MagicMock patching. (BRIDGE-B-001 + sibling fixes in cmd_train / cmd_multi_run via [[grep-all-instances-when-fixing-pattern]] doctrine)
  • /runs/<run_id> page no longer strands the "Run deleted" success message behind a not-found error chrome. v1.3.x set both not_found=True AND action_result="Run deleted" after a successful delete; the template rendered the _not_found() chrome because that branch fired first, hiding the success copy. v1.4 adds a was_deleted state field and a dedicated success chrome with a "Back to runs list" navigation button, so operators get a clear "yes, that worked, click here for what's next" cue rather than a confusing 404. (FRONTEND-B-001)
  • CI failure-observability paths (nightly smoke + post-publish smoke + mutmut baseline) now self-heal on label-deleted repos. gh issue create --label X and gh label create silently no-op when the named label doesn't exist on the repo. A freshly-cloned fork, an accidentally-deleted label, or a renamed label would silently break the failure-issue surface. v1.4 each issue-creation workflow runs gh label create <name> --force idempotently just before gh issue create, so the surface stays self-healing. Labels bootstrapped: ci, nightly-smoke, post-publish-smoke, mutmut-baseline. (CIDOCS-B-001)
  • mutmut baseline file is now persisted across CI runs. v1.3.x's .github/workflows/mutmut.yml wrote the baseline file inside the runner sandbox and discarded it on job exit — the baseline was reset on every run and the "mutants killed since last baseline" delta was unbounded. v1.4 opens a small PR (branch ci/mutmut-baseline-<timestamp>) when the runner-written baseline differs from origin/main. Auto-merge intentionally avoided per advisor decision — human review preserved on test-quality data. (CIDOCS-B-002)
  • Handbook quality-preset defaults are aligned across all 5 pages. While fixing training.md:51 lora_r | 16 drift (CIDOCS-B-008), the [[grep-all-instances-when-fixing-pattern]] doctrine surfaced 4 sibling drift sites across beginners.md, getting-started.md, env-vars.md, reference.md — all showing the v1.2.x rank-16 / alpha-32 footprint instead of the v1.3 rank-256 / alpha-512 default. All five pages now agree with LoRAConfig.r=256 + LoRAConfig.lora_alpha=512 ground truth. (CIDOCS-B-008 + 4 grep-doctrine siblings)
  • README hero example no longer references non-existent backprop ollama register subcommand. The opening 3-line copy-pasteable bash block (the FIRST runnable snippet operators read) was advertising a subcommand that does not exist in backpropagate/cli.py — operators hit argparse error: argument command: invalid choice: 'ollama' on the first invocation. Replaced with the working backprop export ... --ollama --ollama-name my-model form already documented at README line 270. Translated READMEs (README.{es,fr,hi,it,ja,pt-BR,zh}.md) defer to Phase 10 polyglot-mcp re-translation. (CIDOCS-A-001)
  • PyPI metadata propagation: hatchling pinned to >=1.27. v1.3.0's PyPI page rendered Author: None and License: None because the license = "MIT" PEP 639 SPDX-expression shape requires hatchling>=1.27. Older hatchling silently downgraded to Metadata-Version 2.1 and emitted an empty License-Expression, which PyPI then renders as None. The pin closes this; verify post-build with python -m build && twine check dist/* && tar -xOf dist/backpropagate-*.tar.gz '*/PKG-INFO' | grep -E 'License\|Author'. (CIDOCS-A-002)
  • publish.yml now fires on the release.yml-completed chain. The prior release: types: [published] trigger never fired when the release was created by release.yml using GITHUB_TOKEN (well-documented GH platform behavior to prevent workflow loops). v1.3.0 required manual gh workflow run publish.yml --ref v1.3.0. Added workflow_run: workflows: [Release] types: [completed] trigger gated on github.event.workflow_run.conclusion == 'success' (mirrors the pattern post-publish-smoke.yml already uses). Manual release: published + workflow_dispatch retained for hand-cut releases. (CIDOCS-A-003)
  • llms.txt env-var name corrected: BACKPROPAGATE_UI_RATE_LIMIT_HTTP_PER_MIN (was ..._REQ_PER_MIN). LLM agents consuming llms.txt to drive backpropagate were setting an env var that does not exist; the rate-limit cap silently reverted to default. The actual var is BACKPROPAGATE_UI_RATE_LIMIT_HTTP_PER_MIN (defined in backpropagate/ui_app/middleware/rate_limit.py:58). Also added the WebSocket cap (BACKPROPAGATE_UI_RATE_LIMIT_WS_PER_MIN, default 10). (CIDOCS-A-006)
  • Handbook cli-reference.md --lora-r default corrected: 256 (was stale 16). v1.3 shipped the quality-preset rank-256 default in cli.py:4097 and in LoRAConfig.r (config.py:268); the handbook table still showed the v1.2.x footprint rank. (CIDOCS-A-004)
  • Handbook env-vars.md BACKPROPAGATE_LORA__R default corrected: 256 (was stale 16). Mirrors the --lora-r argparse default and the LoRAConfig.r Pydantic default. All three surfaces (env-vars, cli-reference, README) now agree. (CIDOCS-A-005)
  • error-codes.md INPUT_AUTH_REQUIRED row renamed to RUNTIME_UI_AUTH_NOT_ENFORCED with corrected fix advice. The prior row advised BACKPROPAGATE_SECURITY__REQUIRE_AUTH_FOR_SHARE=false opt-out — that env var is a no-op under the v1.1+ Reflex UI (held only for forward-compat with the Gradio era). Operators trying to use it would silently no-op. New fix advice: pass --auth user:password or use SSH port-forwarding (see handbook/security.md). The actual error code raised in v1.2.0+ is RUNTIME_UI_AUTH_NOT_ENFORCED. (CIDOCS-A-007)

Changed

  • GitHub Pages deploy split into dedicated pages-deploy.yml workflow. The prior build-site + deploy-site jobs lived in ci.yml and combined the workflow-level paths: filter with a job-level if: gate, creating a silent chain-reaction failure: pushes touching non-site files would not run build-site, so deploy-site (which depends on it) would skip. Site drift between the repo and deployed pages could persist for weeks unnoticed. The dedicated workflow has paths: [site/**] at the workflow level (cleaner gating) plus workflow_dispatch for manual self-heal redeploys. (CIDOCS-A-008)
  • doc-drift.yml actions SHA-pinned. The prior actions/checkout@v4 + actions/setup-python@v6 references bypassed the v1.1.0-era "every third-party GitHub Action SHA-pinned" convention. Now matches the canonical pins used in ci.yml and publish.yml. (CIDOCS-A-009)
  • AQLM 2-bit experimental opt-in (quant_method="aqlm") deferred from v1.4 to v1.5. The aqlm library state was verified shippable during Wave 5 (1.1.7 on PyPI 2025-04-16; PEFT-documented AQLM+LoRA integration), but the Wave 6b implementation budget prioritized full fine-tuning support for ≤3B models (mode="full") per V1_4_BRIEF P0 ordering. v1.5 will land AQLM + LoRA Mixtral-8x7B support; see V1_5_BRIEF when posted. The 16GB capability envelope row in README.md was updated to reflect the v1.5 status. Translated READMEs (README.{es,fr,hi,it,ja,pt-BR,zh}.md) defer to Phase 10 polyglot-mcp re-translation. (BACKEND-F-001)

Added

  • mode="full" for full fine-tuning of ≤3B models on consumer 16GB GPUs. v1.3 made LoRA the default + ranked the rank-256 quality preset against full FT. v1.4 lands the opposite case: operators who genuinely want every weight updated (rather than a low-rank adapter) can now pass Trainer(..., mode="full") (or --mode=full on backprop train / backprop multi-run). A hard gate refuses the mode for models > 3B parameters with RUNTIME_FULL_FT_MODEL_TOO_LARGE, naming mode="lora" (the default) + the three sub-3B presets that DO work (Phi-4-mini-3.8B / Qwen-3.5-4B / SmolLM3-3B) as the recovery options. The gate fires at Trainer.__init__ (preset-table lookup) AND a second time at load_model() (authoritative model.num_parameters() check) so silent fall-through can't smuggle a 7B into the full-FT codepath. The mode='full' SFTConfig assembly is folded into the same _build_sft_config helper as mode='lora' (no fork): gradient_checkpointing=True (sqrt(L) activation memory), paged_adamw_8bit optimizer (consumer-card memory ceiling), 10× lower learning rate by default. Operators reading the README's "what backpropagate is NOT for" anti-pitch section saw the v1.4 promise that landed here. The Biderman 2024 + Thinking Machines 2025 quality math (LoRA matches full FT on most post-training tasks at 67% of the compute) is still the recommendation for most operators — mode="full" exists for the cases where the operator has measured a quality gap and decided to spend the extra compute. (BACKEND-F-008)
  • backprop ollama register|list|rm triad — dedicated Ollama-workflow subparser. v1.3.x's README hero example was advertising backprop ollama register but the runtime had no such subcommand — CIDOCS-A-001 fixed the README to point at the working backprop export --ollama --ollama-name <name> form. v1.4 closes the loop the other way: the missing subcommand is now real, modeled as a nested subparser group (backprop ollama <action>) to match the upstream ollama CLI's operator mental model 1:1. New surface: backprop ollama register <path> [--name <name>] [--modelfile <path>] (register a previously-exported GGUF or LoRA adapter with the local Ollama daemon, no re-export), backprop ollama list (enumerate currently-registered model names), backprop ollama rm <name> (unregister a model — pass-through to ollama rm via the daemon HTTP API). Architectural deviation from backpropagate's flat-subparser convention is intentional: operators who already know ollama create / ollama list / ollama rm get the same grammar one prefix deeper. The existing backprop export --ollama --ollama-name one-shot path stays untouched as the canonical "I just trained, register in one command" surface; the new triad is for the "I already exported earlier, just register" case. Closes the operator-trap loop opened in CIDOCS-A-001. (BRIDGE-F-001)
  • Trainer.estimate_vram() — pre-flight VRAM estimator. Today operators learn a config is too big at FIRST OOM, after pulling the model + dataset + running steps before the failure fires. v1.4 lands Trainer.estimate_vram(mode=..., lora_r=..., batch_size=..., max_seq_length=..., ...) returning a structured VRAMEstimate carrying total_gb + per-consumer breakdown (model_weights_gb / lora_adapter_gb / optimizer_state_gb / activations_gb / kv_cache_gb / overhead_gb) + reproducibility inputs. Math is back-of-envelope (15% overhead margin) but accurate within ~10-20% of empirical peak for the v1.3 canonical 7B QLoRA configs. The same math feeds backprop estimate-vram (which was already on the CLI as BRIDGE-F-008's tier-table helper; the new Python surface complements it with per-config detail). Sample usage: Trainer("Qwen/Qwen2.5-7B-Instruct").estimate_vram(mode="lora", lora_r=256, batch_size=2, max_seq_length=2048)VRAMEstimate(total_gb=13.4, fits_on_card(16.0)=True, ...). (BACKEND-F-002)
  • MultiRunTrainer callback parity restored — 4 callbacks now actually fire. v1.3.x exposed on_step, on_run_start, on_run_complete, on_gpu_status on MultiRunTrainer but MultiRunTrainer._execute_run never invoked them — they were structurally dead. Operators wiring a TrainingCallback into a multi-run got a successful run with zero callback events; debugging surfaces (loss tracking, GPU-status logging, progress bars) silently no-op'd. The Wave 5 BACKEND-F-003 audit surfaced the regression. v1.4 threads the four callbacks through _execute_run (matches the single-run Trainer surface): on_run_start(run_index) fires before each inner Trainer.train(), on_step(step, loss) fires per training step, on_gpu_status(snapshot) fires from the GPU monitor on each poll, on_run_complete(run_index, result) fires after each inner run lands. Tests pin all four fire-counts against a known 5-run workload. Pre-fix consumers see additional events suddenly delivered — fully additive, no behavior change for operators who didn't wire callbacks. (BACKEND-F-003)
  • CheckpointManager atomic-write parity with RunHistoryManager. v1.3.x's CheckpointManager._save_manifest wrote manifest.json via plain open + write, while RunHistoryManager._save_history already used safe_write_json_with_lock (atomic-rename + filelock). On cross-host NFS / SMB / Lustre filesystems, two concurrent processes calling Trainer.save() could race on the manifest write — last writer wins, prior writer's checkpoint manifest is silently clobbered, the on-disk lineage becomes incoherent. v1.4 aligns _save_manifest with the run-history pattern: write to manifest.json.tmp under filelock, fsync, rename atomically. Same BACKPROPAGATE_FILELOCK_* env knobs govern lock timeout. Pre-fix consumers on local POSIX filesystems were never bit (single-writer, lock-free was fine); the fix is load-bearing for the multi-process training-on-shared-storage operator pattern. (BACKEND-F-004)
  • CLI-wide --log-level / --log-format / --log-file flags on the root parser. v1.3.x routed all logging configuration through env vars (BACKPROPAGATE_LOG_LEVEL / BACKPROPAGATE_LOG_JSON / BACKPROPAGATE_LOG_FILE). Operators wanting one-off log tuning for a single invocation had to either edit .env or prefix every command with BACKPROPAGATE_LOG_LEVEL=DEBUG. v1.4 adds root-parser flags --log-level=DEBUG|INFO|WARNING|ERROR (default INFO), --log-format=json|console (default console), --log-file=<path> (default unset — stderr only). CLI flag wins when both flag and env var are set, matching the v1.3 pattern for --verbose vs BACKPROPAGATE_DEBUG. Each flag binds to the same configure_logging(...) call site used by the env-var path so the structlog wiring stays in one place. (BRIDGE-F-002)
  • --json output on backprop validate and backprop replay. v1.3 added schema_version-tagged JSON output to backprop runs, backprop show-run, backprop diff-runs, backprop export-runs, and backprop estimate-vram. v1.4 extends the same shape to backprop validate <dataset> (emits the dataset, format_detected, total_samples, errors[] shape under schema_version so CI consumers can grep without parsing the human-readable banner) and backprop replay <run-id> (emits the new run_id + the resolved config the replay used + each override that was applied). Both subcommands keep the human-readable surface as the default; --json is the explicit opt-in. (BRIDGE-F-007)
  • backprop info --json now exposes a logging block. The existing --json output covered Python / PyTorch / CUDA / GPU + feature flags but never named the active logging config — operators triaging "why aren't my logs being JSON-formatted" had to grep env vars manually. v1.4 adds logging: {level, format, file, has_handler} to the info --json payload, surfacing what configure_logging(...) actually wired up for this process. Pairs with the new root-parser --log-* flags above so an operator can run backprop --log-format=json info --json | jq .logging and see exactly what state the next subcommand will inherit. (BRIDGE-F-010)
  • Root --help epilog rewritten to group 15 subcommands by workflow. v1.3.x's epilog listed five example commands and the exit-code table but never enumerated every subcommand — operators reading backprop --help cold had to scroll through argparse's auto-generated subcommand list at the top to find what existed. v1.4 rewrites the epilog with workflow-grouped headings: TRAINING (train / multi-run / resume / validate), MULTI-RUN ANALYSIS (runs / show-run / diff-runs / replay / export-runs), EXPORT + DEPLOY (export / push / ollama), UI + INFRA (ui / info / config), DIAGNOSTICS (estimate-vram). 15 subcommands × one-line description each, plus the existing exit-code table and the sysexits.h overlay. Operators get a 30-line scannable workflow map instead of an argparse-auto-generated alphabetical dump. (BRIDGE-F-011)
  • backprop push --hub-revision + --hub-commit-message — HF Hub revision targeting. v1.3's backprop push always pushed to the default branch of the target repo with the default upload commit message ("Upload model"). Operators pushing into a multi-branch HF repo (e.g. per-experiment branches, or pushing to a non-default dev branch for review-before-promote) had no way to target the alternate branch from the CLI. v1.4 adds --hub-revision <branch> (passes through to huggingface_hub.HfApi.upload_folder(revision=...)) and --hub-commit-message <msg> (overrides the default upload commit subject; useful for tying a CI-pushed model to the workflow run that produced it). Both flags are opt-in; omitting them preserves the v1.3.x byte-identical push behavior. (BRIDGE-F-014)
  • backprop info --subcommand-tiers — exposes the stability registry. The SUBCOMMAND_TIERS dict in cli.py:151 has been the source of truth for which subcommands are stable / experimental / deprecated-prefer-X since v1.3 Stage C, but no CLI flag surfaced it — operators learned a subcommand was experimental only via the inline deprecation hint when they invoked it. v1.4 adds backprop info --subcommand-tiers, which prints the registry as a two-column table (subcommand → tier) on the human-readable surface and as the existing SUBCOMMAND_TIERS dict under the info --json payload. Pre-fix operators see the same info as before (registry contents unchanged); the new flag is the introspection surface for it. (BRIDGE-F-015)
  • Web UI push-to-Hub form now exposes --include-base + --token-file. v1.3.x's /push-to-hub page collected the target repo + inline token but never offered the --include-base toggle (push merged-base + adapter) or the --token-file path (read from disk vs paste inline). The CLI supported both; the UI fell behind. v1.4 adds an --include-base checkbox + a --token-file path field to the push form, plumbed into the existing cmd_push shellout. The token-paste field still works for backwards compat; the file path takes precedence when both are set (matches the CLI semantics). (FRONTEND-F-004)
  • /datasets page Stats grid now shows real counts. v1.3.x's Dataset Stats grid was hardcoded to display the em-dash literal '—' for every cell — Avg tokens, Record count, Dedup hits all rendered as regardless of the actual dataset. Operators looking at the page got no signal whether the dataset had been loaded, validated, or processed. v1.4 wires the grid to the real DatasetStatsState fields populated by the validate handler; the cells now show numeric values (or a "Not validated yet" placeholder when the dataset hasn't been processed). (FRONTEND-F-005)
  • 6 regression-pinning tests added for prior-wave behavior. v1.4 Wave 6b feature-audit closed six prior-wave coverage gaps: (1) Stage C save_merged warning when save() is called before train() succeeded, (2) Wave 3.5 untrained-save warning shape pinned, (3) Wave 3.5 model.eval() try/finally invariant restored on validation exception, (4) FRONTEND-B-001 was_deleted state field + Stage C action_in_flight mutual-exclusion contract, (5) schema_version pinning across the 6 JSON-output subcommands so a future shape change can't silently drop the version field, (6) _read_hub_token_file mode-0600 warning + missing-file INPUT_AUTH_INVALID_SHAPE shape. Each test pins a known regression source that the v1.4 swarm earned by reading the post-fix code; pre-fix the same logic was untested and the next refactor could have re-introduced any of the six. (TESTS-F-006)
  • Web UI now stamps Content-Security-Policy + clickjacking + sniff-protection headers on every HTTP response. v1.3.x relied on Reflex's defaults (no CSP, no X-Frame-Options, no X-Content-Type-Options, no Referrer-Policy, no Permissions-Policy) — the GHSA-f65r-h4g3-3h9h advisory closure was authentication-only, leaving the defense-in-depth headers absent. v1.4 wires a new security_headers_middleware as the 5th ASGI layer (rate_limit → basic_auth → request_logging → security_headers → Reflex). Every response carries the documented baseline; the default CSP is script-src 'self' 'unsafe-inline' (Reflex inlines hydration scripts; nonce migration tracked for v1.5 against upstream Reflex). Report-only mode + nonce migration deferred — see WAVE_6A_TODO.md for the follow-ups. (FRONTEND-A-003)
  • backprop export --hub-token-file + backprop push --token-file keep HF tokens out of shell history. Inline --token / --hub-token flags work but leave the token in ps aux and shell history. v1.4 mirrors the v1.3 --auth-file pattern: a one-line credential file (chmod 600 recommended) read by the CLI and validated like the inline flag. Existing --token / --hub-token users see a one-time WARN line at handler entry pointing at the safer alternative; no behavior change beyond the warning. (BRIDGE-A-004)
  • --share cloudflared subprocess shutdown is now clean on Ctrl+C. v1.3.x's cmd_ui invoked cloudflared.terminate() and cloudflared.wait(...) in a single try block; a TimeoutExpired from wait would crash through the finally block, leaving zombie cloudflared processes. v1.4 nests terminatewaitkillwait with inner timeout handling so a non-responsive cloudflared gets SIGKILL'd cleanly. Six new regression tests (TestCloudflaredShutdown) lock the contract. (TESTS-A-002)
  • pytest -n auto parallel-execution now safe across global-state tests. v1.3.x didn't mark which tests mutated process-global state (logging config, Settings.apply_windows_fixes(), structlog config, SecurityLogger / SessionManager singletons). Running the suite under pytest-xdist would race on these surfaces and produce flaky failures. v1.4 marks 5 known-mutating test classes with @pytest.mark.serial, registers the marker in pyproject.toml, and pins all serial-marked tests to the same xdist worker via a pytest_collection_modifyitems hook. CI-side opt-in via BACKPROPAGATE_PYTEST_PARALLEL=1; serial tests still take the same wall-clock time, but the rest of the suite parallelizes safely. (TESTS-A-007)
  • /runs Diff button now wires to a working backend. v1.3.x's run-detail page rendered a Diff button but the form was a producer-without-consumer — operators clicking it saw nothing happen. v1.4 wires the button to the diff_against handler and shows the diff output inline. (FRONTEND-A-001)
  • Auth badge shows @username chip. v1.3.x's footer auth badge displayed mode (Local · Basic) but never the resolved username. v1.4 adds the auth_user field to AuthBadgeState and renders it as a @username chip — useful on shared training hosts where multiple operators have separate --auth user:pass credentials. (FRONTEND-A-002)
  • Recovery banner + structured-error callout chrome consolidated. v1.3.x had three different "something went wrong" presentations across train.py, runs.py, models.py, run_detail.py. v1.4 lands BpRecoveryBanner (for OOM-recovery success / pause-on-overheat states) and BpErrorCallout (for structured BackpropagateError failures) and consolidates the per-page chrome onto the same components. Same data, same colors, same operator mental model across pages. (FRONTEND-A-004)

Security

  • Transitive-dependency CVE sweep (Wave 6a.0 dep-sweep). Bumped the site/ Astro/Starlight stack to close 18 of 21 Dependabot alerts surfaced post-v1.3.0 ship (8 HIGH + 7 MEDIUM + 3 LOW). Major bumps: astro 5.17.3 → 6.3.7, @astrojs/starlight 0.37.6 → 0.39.2. The astro 6 cascade transitively bumped picomatch (4 alerts closed), h3 (4 alerts), devalue (3 alerts), defu (1 alert), svgo (1 alert), smol-toml (1 alert), astro (3 alerts). Final npm audit post-bump: 0 vulnerabilities in the site/ ecosystem. Per the [[astro-6-upgrade-gotchas]] doctrine (earned in the Sovereign repo 2026-05-19), three companion changes shipped together: (a) site/package.json overrides: { vite: "^7.3.2" } to fix Vite 8 hoist breaking @tailwindcss/vite under Rolldown; (b) starlight 0.39's removal of top-level autogenerate on sidebar groups (the config now lives inside items) — site/astro.config.mjs sidebar block updated accordingly; (c) Node 22 was already in pages-deploy.yml from v1.4 Wave 2 (CIDOCS-A-008), no workflow change needed. Site build verified locally (npm run build → 16 pages built, pagefind index built, sitemap generated).
  • Three Python CVEs deferred to v1.5 (upstream blockers + dismiss-with-reason). diffusers 0.37.1 → 0.38.0 stays held because safetensors>=0.8.0rc0 is a pre-release; enabling pre-releases for a security bump trades one risk for another. CVE-2026-44513 + CVE-2026-45804 will close when safetensors 0.8.0 GA lands OR when unsloth loosens its safetensors floor. transformers 4.57.6 → 5.0.0rc3 stays held by the same pre-release policy (the v1.3 deferral reasoning applies unchanged — the bump requires major-version-compat work across trainer.py + datasets.py that the v1.5 dispatch will scope). diskcache CVE-2025-69872 dismissed-with-reason: no first_patched_version in the advisory feed, no newer release exists on PyPI, and diskcache is transitive via llama-cpp-python (only pulled in by the [export] extra) — the vulnerable codepath is not reachable from backpropagate/**/*.py. Same v1.3 reasoning carried forward; all three tracked in V1_5_BRIEF when posted.
  • 5 superseded Dependabot PRs closed. The sweep landed directly in the wave-6a.0/dep-sweep branch as a single coordinator-driven change (matches the v1.3 Wave 6a.0 pattern of one batch PR instead of per-package merges). The 5 individual Dependabot PRs (#106 devalue, #108 transformers, #109 astro+starlight, #113 defu, #114 picomatch) are closed as superseded — #108 stays deferred (pre-release; matches the v1.5 transformers carryforward), the other 4 are now covered by the astro 6 cascade. (Wave 6a.0)