From 8c249ea8af435a68dd33bbd7f6775a2c37ccef4e Mon Sep 17 00:00:00 2001 From: monkey-w1n5t0n Date: Tue, 21 Jul 2026 11:57:32 +0200 Subject: [PATCH] =?UTF-8?q?ci:=20restore=20verification=20=E2=80=94=20reac?= =?UTF-8?q?hable=20submodule=20pin,=20codegen=20+=20WASM=20freshness=20gat?= =?UTF-8?q?es?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 0 of the 2026-07 simplification audit (plan §1). CI has been 100% red on main since 2026-07-13 and every "gates green" claim since rested on local runs. - S7 / critic gap 2: push memllib `feat/nisps-core-swap` (3 commits incl. the pin b37fc53) to monkey-w1n5t0n/memllib and repoint .gitmodules at the fork. Those commits existed on exactly one disk; `git ls-remote` now resolves the pin, so `submodules: recursive` checkout and fresh clones work again. Drops the compensating unreachable-pin error paragraph in build-firmware-arch.sh. - S24 / S31: the manifold-tests job regenerates from schemas/, runs the codegen golden test, and fails on a dirty diff — the "schema changes ship with both generated outputs" rule is now enforced rather than assumed. - S32: a WASM freshness gate runs the parity harness against the *committed* manifold/public/nisps.{js,wasm} before the CI rebuild overwrites it. That artifact is what the webhook ships to production, so a stale commit now fails loudly instead of shipping. - critic gap 3 / operator decision §7.4: the VPS webhook (~/.config/webhooks/meml-deploy.sh, not in this repo) waits for the `CI` workflow to conclude success on the pushed SHA before building. Fail-closed; MEML_SKIP_CI_GATE=1 for an emergency hand-deploy. Verified the gate query returns `failure` for fa37047, i.e. it would have blocked that deploy. - S31: corrected run-all-tests.sh's false "single command CI invokes" header. Docs moved with the code: ALIGNMENT defect 1 deleted (resolved) and the rest renumbered; MAP.md's unreachable-pin warning replaced with the fork pin and a pointer to the §7.5 vendoring decision; ONBOARDING documents the deploy gate and the tracked-WASM-ships-to-prod hazard; plan §1 marked burned down. Gates: scripts/run-all-tests.sh ALL GREEN (ctest 4/4, parity 1273 floats within 1e-5, lint, typecheck, 33 Playwright specs). --- .github/workflows/ci.yml | 29 +++++++++++++++++++++++++ .gitmodules | 2 +- ALIGNMENT.md | 24 +++++++------------- MAP.md | 2 +- docs/specs/plans/simplification-plan.md | 8 +++++++ manifold/ONBOARDING.md | 14 +++++++++--- scripts/build-firmware-arch.sh | 13 ++++------- scripts/run-all-tests.sh | 6 +++-- 8 files changed, 66 insertions(+), 32 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4f6530f..87fcd86 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -59,6 +59,16 @@ jobs: CMAKE_BUILD_PARALLEL_LEVEL: '4' run: bash scripts/build-cpp-tests.sh + # Freshness gate. manifold/public/nisps.{js,wasm} are TRACKED artifacts: + # the VPS webhook builds only manifold/, so vite copies whatever is + # committed into the production bundle. Prove the committed artifact + # still agrees with the C++ we just built, BEFORE the rebuild below + # overwrites it. A stale commit fails here instead of silently shipping. + - name: WASM freshness gate (committed artifact vs native) + env: + NISPS_PARITY_NO_BUILD: '1' + run: bash scripts/parity-check.sh + - name: Build WASM env: # The script defaults to /usr/lib/emscripten/emcc; the runner gets @@ -97,6 +107,25 @@ jobs: with: bun-version: latest + # Enforces the "schema changes ship with regenerated C++ AND TypeScript + # in the same change" rule. Regenerate from schemas/ and fail if the + # committed outputs differ; then run the codegen golden test. + - name: Codegen is committed and idempotent + run: | + cd codegen + bun install --frozen-lockfile + bun run generate.ts + bun run generate-midi-devices.ts + bun run tests/golden_test.ts + cd .. + GEN_DIRS="nisps/modes/generated nisps/midi/generated \ + manifold/src/modes/generated manifold/src/midi-devices/generated" + git add -N $GEN_DIRS + if ! git diff --exit-code -- $GEN_DIRS; then + echo "::error::Generated outputs are stale. Run codegen and commit the result." >&2 + exit 1 + fi + - name: Install manifold deps working-directory: manifold run: bun install --frozen-lockfile diff --git a/.gitmodules b/.gitmodules index cf1a5eb..30e1c13 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "src/memllib"] path = src/memllib - url = https://github.com/MusicallyEmbodiedML/memllib.git + url = https://github.com/monkey-w1n5t0n/memllib.git diff --git a/ALIGNMENT.md b/ALIGNMENT.md index f22467a..1d582de 100644 --- a/ALIGNMENT.md +++ b/ALIGNMENT.md @@ -12,15 +12,7 @@ The clean-slate rewrite (2026-04-29) consolidated everything into one C++20 code ## Top defects (ranked by mission impact) -### 1. The verification story is void: CI red for a month, unpushed load-bearing commits, ungated deploys (2026-07-21) - -**What.** CI has been 100% failing on main since 2026-07-13: the memllib submodule pin (`b37fc53`, local branch `feat/nisps-core-swap`) is reachable from no remote, so GitHub checkout dies before any gate runs — and those three firmware-critical commits exist only on this one disk. Meanwhile push-to-main deploys straight to `meml.lnfinitemonkeys.org/next/` via webhook with no gate, shipping the *committed* `manifold/public/nisps.wasm`, which has no freshness check. All P4–P6 "gates green" claims rest on local runs. - -**Why it blocks the mission.** "Verifiable without hardware" and "confident agentic changes" are the mission's operating premises; both are currently fiction at the remote/deploy boundary. One disk failure loses firmware-critical code. - -**Rough cost.** Half a day (plan §1): push the branch to the `monkey-w1n5t0n/memllib` fork, repoint `.gitmodules`, confirm green, add codegen + WASM-freshness steps; deploy gating is an operator decision (plan §7.4). - -### 2. The mode layer is not shared: WASM re-orchestrates modes by hand (2026-07-21) +### 1. The mode layer is not shared: WASM re-orchestrates modes by hand (2026-07-21) **What.** `nisps/modes/` — the CRTP layer binding ML config, engine, voice-space and I/O — compiles only into firmware. `nisps/wasm/bindings.cpp` includes engines and ML primitives but zero mode headers, and Manifold re-assembles mode behaviour (jolt stepping, OU, routing) in TS. "Firmware and WASM share the same modes" is true only at the engine level; every ModeBase behaviour must be mirrored browser-side by hand. @@ -28,7 +20,7 @@ The clean-slate rewrite (2026-04-29) consolidated everything into one C++20 code **Rough cost.** Spec first, then ~a week: storage-policy the ModeBase orchestration the way P2 did MLPCore (verified shape in plan §6.5a — *not* binding monolithic mode objects, which would contradict the locked two-instance RT architecture). Related honesty gap: Manifold currently catalogues 4 modes that structurally cannot run in the browser (no mic input, event-only engines) — plan §6.5b (absorbs the old C15/mic-input defect; C15 itself lives on `archive/playground-solidjs`). -### 3. No curated/advanced split and no in-UI mode picker — the UI fights vision 3 (2026-07-21) +### 2. No curated/advanced split and no in-UI mode picker — the UI fights vision 3 (2026-07-21) **What.** Manifold is 100% dev-maximalist: five drawers of everything, no preset data model to author against, and mode switching exists only via the debug hook — there is no instrument picker in the UI at all (the plumbing, `ctx.modes`/`setModeId`, already exists unused). A stratum of decorative controls (training-param sliders, master volume, bpm, A/B, snapshots, fabricated gradient health) renders real-looking UI that drives nothing. @@ -36,7 +28,7 @@ The clean-slate rewrite (2026-04-29) consolidated everything into one C++20 code **Rough cost.** Product-model decision first (plan §7.6), then incremental: picker is days; the curated-preset model seeds from `backends/presets.ts` + schemas; disclosure via per-drawer depth levels. Deleting the decorative stratum is part of the Phase-1 sweep. -### 4. Arduino-CLI build machinery is actively hostile — vision 4 unstarted (2026-07-21) +### 3. Arduino-CLI build machinery is actively hostile — vision 4 unstarted (2026-07-21) **What.** The build script sed-mutates the committed `.ino` to select variants (polluting history), the mode list is triple-bookkept (a `NISPS_ST_*` token-paste table is already silently missing the currently-active SLPWorkshop variant), a symlink forest works around Arduino's include rules, and the toolchain globally mutates the installed TFT_eSPI library. Firmware compilation is in no automated gate anywhere. @@ -44,7 +36,7 @@ The clean-slate rewrite (2026-04-29) consolidated everything into one C++20 code **Rough cost.** 2–3 days, one cut (plan §5): env-per-variant `platformio.ini`, delete ~400 lines of hackery, then a firmware CI job. Gated on the memllib ownership decision (plan §7.5). -### 5. Manifold-as-hardware-editor is a facade (2026-07-21) +### 4. Manifold-as-hardware-editor is a facade (2026-07-21) **What.** Vision bullet 5 exists as a 237-line Web Serial shell: sound connect lifecycle, zero protocol (`saveModel`/`restoreModel`/`getSettings` are literal stubs), and firmware has no serial command surface or on-device persistence to talk to. @@ -52,7 +44,7 @@ The clean-slate rewrite (2026-04-29) consolidated everything into one C++20 code **Rough cost.** Week+, spec-first (plan §6.5d). The right discipline already exists in-repo: useq-celium's C-header wire truth + TS mirror + parity test; settings payloads should derive from schema codegen. -### 6. Dead mass and registry sprawl across every layer (2026-07-21) +### 5. Dead mass and registry sprawl across every layer (2026-07-21) **What.** The audit's aggregate: ~40% of `feedback.hpp` is legacy modes nothing reaches; a dead four-way focus/altitude UI system; ~25 unconsumed ConsoleCtx fields; a dozen dead WASM API entries threaded through a 5-file registration chain; daisysp compiled into every firmware build with zero consumers; retired-playground artifacts and dead planning relics tracked at root; mode identity spread across ~6 hand-maintained registries with demonstrated drift; assorted stale specs presenting a deleted world as present tense. @@ -60,19 +52,19 @@ The clean-slate rewrite (2026-04-29) consolidated everything into one C++20 code **Rough cost.** Plan phases 1–3 (~a week total, mostly mechanical deletions with green gates). Behaviour bugs found en route (dataset-cap divergence 100 vs 128 + OOB read, VCV 2-D input truncation, VCV audio-thread race + JSON) are plan §3. -### 7. No performance measurement despite a performance-defined mission (2026-07-21) +### 6. No performance measurement despite a performance-defined mission (2026-07-21) **What.** The "super performance-sensitive" constraint is enforced only by static discipline (no-heap lint — itself with proven false negatives — and section attrs, 3/5 of which are dead macros). No benchmark, no CPU-load assertion, no flash/RAM size report on either target; the 16 KB dead buffer was found by reading, not by any gate. **Rough cost.** ~A day for a host-side blocks-per-second benchmark + a per-variant size report in `build-firmware.sh` (plan §6.5f). -### 8. Training-health telemetry: one product decision fragmented into four half-features (2026-07-21) +### 7. Training-health telemetry: one product decision fragmented into four half-features (2026-07-21) **What.** A 16 KB loss-history buffer in every firmware MLP that nothing reads; a WASM worker faking a 1-element loss history; decorative gradient-health UI; and a real `get_layer_stats` API plumbed end-to-end and consumed by nobody. **Why it blocks the mission.** "Is the network learning?" is a core research affordance — currently it *looks* answered while being fake. Decide feature-or-delete once (plan §7.3) and collapse all four limbs accordingly. -### 9. RMSProp still deferred from `nisps/ml/` (2026-04-29; reaffirmed 2026-07-21) +### 8. RMSProp still deferred from `nisps/ml/` (2026-04-29; reaffirmed 2026-07-21) **What.** `training.hpp` ships SGD only; the legacy firmware used RMSProp for `TrainBatch`. Optimizer choice is a research axis. Not blocking current fits; will matter for harder loss landscapes. Port target: upstream MusicallyEmbodiedML `memlp` (the in-repo `src/memlp` copy is deleted; use the GitHub remote or archive branch). diff --git a/MAP.md b/MAP.md index 788c990..cf2bc6f 100644 --- a/MAP.md +++ b/MAP.md @@ -122,7 +122,7 @@ includes; no `nisps-core`. - `ci.yml` — GitHub Actions: cmake build + ctest + WASM build + parity check + lint + Playwright (cpp-tests + manifold-tests jobs). Firmware compile is documented as manual. ### `src/` — submodule + vendored trees -- `src/memllib/` — hardware abstraction (audio driver, peripherals, MIDI), the only true submodule. **Not auto-initialized** — fresh clones need `git submodule update --init --recursive`. ⚠ The pinned commit currently lives on no remote (ALIGNMENT defect 1 / plan §1). +- `src/memllib/` — hardware abstraction (audio driver, peripherals, MIDI), the only true submodule. **Not auto-initialized** — fresh clones need `git submodule update --init --recursive`. Pinned to `monkey-w1n5t0n/memllib` branch `feat/nisps-core-swap` (the operator's fork; upstream is `MusicallyEmbodiedML/memllib`). Ownership decision — vendor the load-bearing subset into this repo — lands with the PlatformIO migration (plan §5, §7.5). - `src/daisysp/` — vendored plain files (NOT a submodule). Zero remaining consumers — nisps replaced its PitchShifter with a custom granular impl; deletion planned (plan S8). ### Top-level docs diff --git a/docs/specs/plans/simplification-plan.md b/docs/specs/plans/simplification-plan.md index 7bebae0..9e0d179 100644 --- a/docs/specs/plans/simplification-plan.md +++ b/docs/specs/plans/simplification-plan.md @@ -17,6 +17,14 @@ status: active ## §1 Phase 0 — Restore verification (URGENT, ~half day) — S7, S24, S31, S32; critic gaps 1–3 +**BURNED DOWN 2026-07-21.** All five items landed: memllib `feat/nisps-core-swap` pushed to +`monkey-w1n5t0n/memllib` and `.gitmodules` repointed (pin `b37fc53` now reachable — data-loss risk +closed); the compensating error paragraph in `build-firmware-arch.sh` deleted; codegen dirty-diff + +golden test added to the `manifold-tests` CI job; a WASM freshness gate (parity harness vs the +*committed* artifact) added to `cpp-tests` before the rebuild; and the VPS webhook +(`~/.config/webhooks/meml-deploy.sh`, not in this repo) now blocks the deploy unless the `CI` +workflow concluded `success` on that exact SHA — fail-closed, `MEML_SKIP_CI_GATE=1` to override. + CI has been 100% red on main since 2026-07-13; the cause and the data-loss risk are the same object. 1. **Push `feat/nisps-core-swap` to the `monkey-w1n5t0n/memllib` fork** (3 commits incl. pin `b37fc53` currently existing only on this disk); repoint `.gitmodules` to the fork URL; verify a fresh clone + `submodules: recursive` checkout succeeds; delete the compensating error-message paragraph in `scripts/build-firmware-arch.sh` (S7). diff --git a/manifold/ONBOARDING.md b/manifold/ONBOARDING.md index dbad8b6..58ec56c 100644 --- a/manifold/ONBOARDING.md +++ b/manifold/ONBOARDING.md @@ -40,9 +40,17 @@ bun run test:e2e # Playwright smoke (needs `bun run build` first; runs again Chromium): `PLAYWRIGHT_BROWSERS_PATH=/home/w1n5t0n/snap/bun-js/87/.cache/ms-playwright node node_modules/.bin/playwright test`. Preview via bun is fine. The smoke spec (`tests/e2e/smoke.spec.ts`) asserts: engine WASM loads, spine invariant (setInputs → outputs change), feedback runs, console renders, **no "C15" in the bundle**, no console errors. `shot.spec.ts` takes screenshots. -- **Deploy is automatic on push to GitHub `main`** → webhook → builds `manifold/` → rsyncs to the - live `/next/` subdir. See the `manifold-deploy-pipeline` memory for the full chain and gotchas - (the `cp index.html a-immersive.html` 403 workaround; git-ignored `bun.lock`). +- **Deploy is automatic on push to GitHub `main`, but gated on CI** → webhook → waits for the + `CI` workflow to conclude `success` on that exact SHA → builds `manifold/` → rsyncs to the live + `/next/` subdir. A red or missing CI run aborts the deploy (fail-closed, 20 min timeout); + `MEML_SKIP_CI_GATE=1` bypasses it for an emergency hand-deploy. The gate lives VPS-side in + `~/.config/webhooks/meml-deploy.sh` (not in this repo) — added 2026-07-21 per the simplification + audit. See the `manifold-deploy-pipeline` memory for the full chain and gotchas (the + `cp index.html a-immersive.html` 403 workaround; git-ignored `bun.lock`). +- **`manifold/public/nisps.{js,wasm}` are tracked artifacts that ship to production** — the webhook + builds only `manifold/`, so vite copies whatever is committed. CI's *WASM freshness gate* runs + the parity harness against the committed artifact before rebuilding it, so a stale commit fails + loudly. Rebuild with `scripts/build-wasm.sh` and commit it whenever `nisps/` changes. - **`?debug=1`** installs `window.__nisps` (synchronous probe for Playwright/console — see `src/debug/probe.ts`). **`base: './'`** in `vite.config.ts` keeps asset URLs relative so one `dist/` mounts at both `/` and `/next/`. **WASM URLs must resolve via `document.baseURI`**, never diff --git a/scripts/build-firmware-arch.sh b/scripts/build-firmware-arch.sh index 14e434a..66d7a1c 100755 --- a/scripts/build-firmware-arch.sh +++ b/scripts/build-firmware-arch.sh @@ -66,17 +66,12 @@ if ! command -v arduino-cli >/dev/null 2>&1; then fi fi -# ---- 3. submodule sanity (the orphan-pin footgun) ----------------------- -# memllib provides the audio/synth/hardware tree; if its pin is unreachable the -# build cannot be assembled. Fail loudly with the fix rather than 100 cryptic -# missing-header errors deep in the compile. +# ---- 3. submodule bring-up ---------------------------------------------- +# memllib provides the audio/synth/hardware tree the sketch compiles against. log "Checking the memllib submodule" -if ! git -C "$REPO_ROOT" submodule update --init --recursive 2>/tmp/mf-submod.err; then - cat /tmp/mf-submod.err >&2 || true - die "git submodule update failed. If this is the 'not our ref' / unreachable-pin error, the recorded memllib commit is on an unpushed branch (feat/nisps-core-swap). Push that memllib branch to a reachable remote, or bump the submodule pointer to a published commit, then re-run." -fi +git -C "$REPO_ROOT" submodule update --init --recursive if [[ ! -e "$REPO_ROOT/firmware/MEMLNaut-NISPS/src/memllib/hardware/memlnaut" ]]; then - die "src/memllib looks empty after submodule init — the memllib pin is unreachable. See the message above." + die "src/memllib looks empty after submodule init — check the submodule pin and remote." fi # ---- 4. toolchain bring-up (delegates the heavy lifting) ---------------- diff --git a/scripts/run-all-tests.sh b/scripts/run-all-tests.sh index a79c547..d7bc5af 100755 --- a/scripts/run-all-tests.sh +++ b/scripts/run-all-tests.sh @@ -1,6 +1,8 @@ #!/usr/bin/env bash -# scripts/run-all-tests.sh — master entrypoint that exercises every check -# stream 11 owns. Designed to be the single command CI invokes. +# scripts/run-all-tests.sh — master local entrypoint that exercises every +# check the repo owns. CI (.github/workflows/ci.yml) does NOT invoke this +# script; it re-lists the same stages as discrete steps so failures are +# attributable per-step. Keep the two in sync when adding a stage. # # Stages (each fails fast): # 1. C++ build + ctest → scripts/build-cpp-tests.sh