Skip to content

Feature model: manifest, generator, gates, and the P2 ratchet - #168

Merged
angeloINTJ merged 6 commits into
mainfrom
feat/features-model
Sep 26, 2026
Merged

angeloINTJ merged 6 commits into
mainfrom
feat/features-model

Conversation

@angeloINTJ

@angeloINTJ angeloINTJ commented Sep 26, 2026 •

Copy link
Copy Markdown
Owner

What

docs/analysis/MODELO_DE_RECURSOS.md lays out a model for making SIMUT's
features switchable: a single manifest describes what each firmware profile
turns on, a generator derives the PlatformIO config from it, and gates keep the
two from drifting. This PR is the P1 foundation of that model, the first
P0 cleanups it enables, and the P2 ratchet that will discipline the
seam extractions to come.

  • tools/features.toml — the manifest: the six firmware profiles, their
    display choice, toggles, source-file cuts and lib_ignore, with the measured
    rationale for each (the byte counts that lived in platformio.ini's env
    blocks moved here).
  • tools/gen_features.py — derives tools/generated/profiles.ini (the
    PlatformIO config) and features_model.json (for the future configurator).
  • tools/check_features.py — proves, via PlatformIO's own resolver, that
    the environments the build uses resolve — flag for flag and translation-unit
    for translation-unit — to what the manifest describes.

platformio.ini now includes the generated profiles via extra_configs and no
longer defines [env:pico_w_*]; it keeps pico_base and the native_* test
environments.

Why it is safe

The switch is proven byte-identical: each of the six firmware.bin built
from the switched platformio.ini has the same sha256 as the image built before
the switch (clean builds, both sides). The generated source-filter order was
written to match the hand-written one, so link order does not move the image.

Verified locally, everything CI runs:

  • the eleven gates-job checks (secrets, log codes, authorization, licence,
    Ângulo, fsguard, Air consistency, the two feature gates, the entanglement
    watermark, history merge);
  • the seven native suites (185 / 63 / 33 / 45 / 39 / 16 / 29 cases);
  • the six firmware images build byte-identical.

Both feature gates run in the CI gates job, so the manifest and the build
cannot drift apart. Each gate has a negative control (a mutation is caught).

P0 cleanups included

  • Dropped the dead SIMUT_FACTORY_RESET flag (read nowhere in src/); the six
    images rebuild byte-identical, which confirms it was dead.
  • Corrected three comments that disagreed with the project's own measured
    numbers: the Bluetooth cost (classic SPP, 64,732 B + 16,416 B, not "BLE UART,
    ~22 KB"), the mDNS cost (~15 KB, not "negligible"), and the OTA snapshot
    margin (1,430 B on v25, not the v21 "~3.4 KiB").
  • Added pico_w_air to SHIPPING_ENVS so a page diet cannot leak into the one
    published image that was missing from it (unchanged, it declares no diet).
  • Retargeted the Air C6 consistency gate — which read the air env from
    platformio.ini — to the manifest where the flag now lives.

P2 ratchet included

  • tools/check_feature_sprawl.py counts the #if SIMUT_ sites per feature
    macro and per file in src/ and holds them to a high-water mark in
    tools/feature_sprawl.json — the same mechanism as the flash budget, for
    entanglement. A site added to a shared file fails; extracting a feature into
    its own translation unit (the P2 goal) lowers the mark. The starting
    scoreboard is 283 sites across 24 macros (SIMUT_AIR 41/15,
    SIMUT_DISPLAY_TFT 39/20, SIMUT_SENSOR_DS18B20 35/13, SIMUT_CLI_FULL
    34/9, SIMUT_PANEL_PIN 27/13). It runs in the CI gates job.

This establishes the discipline; the actual seam extractions (a translation
unit per feature type, which will make that scoreboard drop) are follow-up PRs.

Not in this PR (deliberately)

  • simut_features.h (feature macros as a generated header for the code's
    #if) — belongs with the first P2 seam.
  • The dead runtime knobs s_int and loggingEnabled — removing a user-facing
    control is a product decision.
  • Guarding the ghost routes (/api/reset_touch_cal, /api/themes) under TFT —
    changes the alpha/air image, better reviewed on its own.
  • The P2 seam extractions themselves — each its own PR, each dropping the
    entanglement scoreboard.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Jq4auDQppGzmAx1gLKn1z3

Ângelo Moisés Alves and others added 5 commits September 25, 2026 22:55
The firmware is switchable today through ~11 hand-maintained -D flags,
per-environment source filters and a web-page cut, described in prose across
platformio.ini and seven analysis documents. docs/analysis/MODELO_DE_RECURSOS.md
lays out a model that turns that into a single source; this is its P1 foundation:

  tools/features.toml       the manifest: what each firmware profile turns on
  tools/gen_features.py     derives tools/generated/profiles.ini and a JSON model
  tools/check_features.py   proves the derived environments reproduce today's

check_features.py resolves both the hand-written platformio.ini and the generated
profiles with PlatformIO's own resolver (pio project config) and asserts, per
firmware environment, the same compiler flags, the same compiled translation
units (the filter resolved against src/), the same lib_ignore, custom_web_omit,
custom_fs_pages and build_type. All six match: release/test/test_https/asserts
67 units, alpha/air 57. The gate can fail — a mutation to the manifest is caught,
including its propagation through `extends`.

Byte-identical build proof: each of the six images, built from platformio.ini and
from the generated profiles (pio run -c, clean both sides), produces an identical
firmware.bin sha256. The generated filter order was written to match today's, so
link order (which has cost 427 B in this project) does not move the image.

platformio.ini and src/ are untouched: this is the fidelity proof that lets the
switchover happen next, not the build source yet. Both checks run in the CI gates
job. SIMUT_FACTORY_RESET is modelled as-is (a dead flag, noted by the gate); P0
removes it from both sides, byte-identical.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jq4auDQppGzmAx1gLKn1z3
platformio.ini now includes tools/generated/profiles.ini via extra_configs and
no longer defines [env:pico_w_*]; it keeps only pico_base and the native_* test
environments. The six firmware environments are the ones the manifest
(tools/features.toml) describes, generated by tools/gen_features.py. The
per-profile rationale that lived in the removed env blocks — the measured byte
counts, why mDNS is off in test, why test_https diets three pages, why the Air
carries the full CLI since the v2.4.9-beta diet — moved into the manifest beside
each profile. The note on why there is no debug environment stayed in
platformio.ini.

Proven byte-identical: each of the six firmware.bin built from the switched
platformio.ini has the same sha256 as the image built before the switch (clean
builds, recorded baselines). The generated filter order was written to match the
hand-written one, so link order does not move the image. `pio test -e native`
still passes 185/185 — the native environments are untouched.

check_features.py keeps its meaning: it now compares the profiles the build uses
(the committed profiles.ini, included via extra_configs) against the manifest
generated on the fly, so it catches drift between the manifest, the generated
file and the build. gen_features.py --check keeps the generated files in sync.

This completes P1 of docs/analysis/MODELO_DE_RECURSOS.md except simut_features.h
(feature macros as a generated header for the code's #if), which belongs with
the P2 seams. SIMUT_FACTORY_RESET is still modelled (a dead flag) to keep the
image identical; P0 removes it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jq4auDQppGzmAx1gLKn1z3
SIMUT_FACTORY_RESET was set by release/test/test_https (and asserts, via release)
and read nowhere in src/ — check_features flagged it and the survey in
docs/analysis/MODELO_DE_RECURSOS.md had already found it. Removed from the
manifest; after the P1 switchover the flag reached the build through the
generated profiles.ini, so the manifest is the only place left to remove it.

All six firmware.bin rebuild byte-identical to before, which confirms the image
never read the flag — removing a live -D would have moved the image.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jq4auDQppGzmAx1gLKn1z3
…iets (P0)

Three comments disagreed with the measured numbers the project itself records,
which the house treats as a defect:
- src/simut_config.h called SIMUT_BLUETOOTH "BLE UART, ~22 KB". It is classic
  SPP (SerialBT/RFCOMM) and costs 64,732 B of flash and 16,416 B of .bss,
  measured (the [pico_base] note in platformio.ini).
- The same block called SIMUT_MDNS "Negligible flash cost". LEAmDNS is ~15 KB
  (15,376 B measured).
- src/ota/config_snapshot.cpp gave the snapshot margin as "~3.4 KiB", a v21
  figure. On v25 it is 1,430 B (SystemConfig 6738 B + 4 of CRC against 8172
  usable).

tools/build_webui_gz.py adds pico_w_air to SHIPPING_ENVS. Air is published by
release-ota.yml and was the one published image missing from the set, so
nothing refused a page diet from leaking into it. It declares no
custom_fs_pages, so this is a guard: all six images rebuild byte-identical.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jq4auDQppGzmAx1gLKn1z3
The switchover moved the six firmware environments out of platformio.ini into
the generated tools/generated/profiles.ini, so check_air_consistency.py's C6
check — which read the [env:pico_w_air] comment block from platformio.ini to
confirm it agreed with -DSIMUT_CLI_FULL (finding F18) — could no longer find the
env and failed. Retargeted C6 to tools/features.toml, where the cli_full setting
and the rationale that explains it now live; the check is unchanged (the two
must agree) and still fails on a mismatch (negative control confirmed). Also
refreshed the gen_features.py docstring.

All ten CI gates-job checks and the seven native suites pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jq4auDQppGzmAx1gLKn1z3
@github-actions github-actions Bot added documentation Improvements or additions to documentation ci Continuous integration and automation tools Build tools, scripts, and developer tooling code Firmware source changes (src/, WebUI.h) labels Sep 26, 2026
P2 of docs/analysis/MODELO_DE_RECURSOS.md extracts each feature into its own
translation unit; before moving any code, this establishes the ratchet that
makes the work measurable and keeps it from going backwards.

tools/check_feature_sprawl.py counts the `#if SIMUT_` sites per feature macro
and per file in src/ and holds them to a high-water mark in
tools/feature_sprawl.json — the same mechanism as the flash budget, for
entanglement. A site added to a shared file fails the gate; extracting a
feature into its own TU lowers the mark. The starting scoreboard is 283 sites
across 24 macros (SIMUT_AIR 41/15, SIMUT_DISPLAY_TFT 39/20,
SIMUT_SENSOR_DS18B20 35/13, SIMUT_CLI_FULL 34/9, SIMUT_PANEL_PIN 27/13). It runs
in the CI gates job and knows how to fail: an injected `#if SIMUT_AIR` in a
shared file is caught (negative control verified). simut_config.h is excluded —
it defines the macros, it does not spread them.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jq4auDQppGzmAx1gLKn1z3
@angeloINTJ angeloINTJ changed the title Feature model (P1): a manifest that drives the build Feature model: manifest, generator, gates, and the P2 ratchet Sep 26, 2026
@angeloINTJ
angeloINTJ merged commit 6d04074 into main Sep 26, 2026
10 checks passed
@angeloINTJ
angeloINTJ deleted the feat/features-model branch September 26, 2026 02:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci Continuous integration and automation code Firmware source changes (src/, WebUI.h) documentation Improvements or additions to documentation tools Build tools, scripts, and developer tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant