Skip to content

fix(build): build the backend registry, and stop system.linux eating rtos - #118

Merged
srpatcha merged 4 commits into
embeddedos-org:masterfrom
Kartikey1306:fix/build-the-backend-registry
Sep 8, 2026
Merged

srpatcha merged 4 commits into
embeddedos-org:masterfrom
Kartikey1306:fix/build-the-backend-registry

Conversation

@Kartikey1306

@Kartikey1306 Kartikey1306 commented Sep 1, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #113 and #114. Review the last commit.

Three findings, one chain — the second was hidden behind the first, and the third behind the second.

1. backends/ was in no add_subdirectory()

Its 12 sources were compiled by nothing, so libeos_backends.a never existed. eos_systems' firmware.c calls eos_backend_find() and eos_backend_find_by_type(), so anything linking eos_systems failed to link.

2. Adding it didn't fix it — and the reason was not what it looked like

backends/src/backend.c:9: error: unknown type name 'EosResult'
backends/src/backend.c:9: error: use of undeclared identifier 'EOS_MAX_BACKENDS'
... 30 errors

That reads as a subsystem that has drifted out of date with the API. It has not. There are two headers named eos/backend.h:

include/eos/backend.h 1316 bytes, int-returning
backends/include/eos/backend.h 3280 bytes, EosResult-returning

include/ comes first on the global include path, so every translation unit got the wrong one. Put backends/include ahead of it and all 12 sources compile clean.

The copy in include/ is the stale one:

  • it declares eos_backend_init_all(), which no .c defines
  • it omits eos_backend_registry_init() and the eos_backend_*_init() functions that cmd/eos/main.c calls
  • the two agree exactly on eos_backend_find() and eos_backend_find_by_type() — all that firmware.c and scheduler.c use

So removing it changes no caller. That also closes one of the undefined-declaration findings from #114.

3. With backends built, test_firmware links — and immediately aborts

It was excluded in #114 for exactly this reason. Registered, it fails at once:

Assertion failed: (cfg.system.rtos_count >= 1), test_firmware.c:179

against examples/industrial-gateway/eos.yaml, which plainly declares one:

system:
  kind: hybrid
  linux:
    provider: buildroot
  rtos:
    - provider: freertos
      core: m4

Parsing system.linux moves to SEC_SYSTEM_LINUX, which handles provider, kernel and rootfs and has no way back to the parent — so the sibling rtos: is swallowed. rtos_count stays 0, and a hybrid build produces no RTOS firmware while reporting nothing.

SEC_SYSTEM_KERNEL already popped back for one specific sibling (indent <= 1 && key == "rootfs"). This pops back for any key at system's own child indent, which is what the indentation means — indent_level() counts 2-space units, so <= 1 is exactly system:'s direct children and never an rtos entry's keys at indent 3.

Verified

ctest 39/39 (was 38) — test_firmware added, 11/11 inside it
default non-test build still builds
against the unfixed parser test_firmware aborts on rtos_count >= 1, exit 134

test_firmware.c is also dropped from NOT_BUILT in the registration guard, whose test_not_built_list_has_no_stale_entries case would otherwise fail — which is the guard doing its job.

@Kartikey1306

Copy link
Copy Markdown
Contributor Author

Merge-order note: this and #115 are siblings off #114, and both insert a block into tests/CMakeLists.txt at the same anchor. Each is MERGEABLE against master on its own, but whichever lands second will conflict there — a one-hunk resolution, both blocks are additive and independent (test_firmware here, test_pkg_fetch there). Happy to rebase whichever you take second.

@Kartikey1306
Kartikey1306 force-pushed the fix/build-the-backend-registry branch from 07594ab to 82a5b5b Compare September 1, 2026 15:37
@Kartikey1306

Copy link
Copy Markdown
Contributor Author

Rebased onto #116 → #114. #113 is closed and this branch carried its two commits; #116 repairs the same two things on master, so they are dropped here. Stack is now #116 → #114 → this. 39/39 locally, and the default non-test build still builds.

The tests/CMakeLists.txt conflict with #115 noted above still stands after the rebase — same one hunk, both blocks additive.

@Kartikey1306
Kartikey1306 force-pushed the fix/build-the-backend-registry branch from 82a5b5b to 3c72594 Compare September 2, 2026 08:30
@Kartikey1306

Copy link
Copy Markdown
Contributor Author

Rebased onto current master (#116 and #114 have merged and were dropped as already-upstream), then onto #125 — master is red on the registration guard for test_crypto_ed25519_loworder.c, which #125 fixes, so the stack is now #125 → this.

Also folds in cmd/eos. Backends without the thing that uses them was half the change: cmd/eos/main.c calls eos_backend_registry_init() and each eos_backend_*_init(), and cmd/ was in no add_subdirectory() either — so the eos binary the README tells users to run did not exist. With the registry built it compiles and works:

$ eos info          # in examples/industrial-gateway
Packages (2):
  - zlib 1.2.13 [cmake]
  - mosquitto 2.0.18 [cmake]
System:
  kind: hybrid
  RTOS targets (1):
    [0] provider: freertos
        board:    stm32mp1-m4

That last line is the parser fix visible end to end — RTOS targets reads 0 before it, which is the same defect test_firmware aborts on.

All 11 backends register (cmake, ninja, make, kbuild, zephyr, freertos, nuttx, meson, autotools, buildroot, cargo); none carries a TODO or stub marker.

ctest              40/40   (was 38 on master)
pytest tests/      15 passed
default build      OK, eos binary 143488 bytes

@srpatcha srpatcha left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review — eos#118 "fix(build): build the backend registry, and stop system.linux eating rtos"

head: e724633 author: Kartikey1306 ci: pass

Verdict: Correct diagnosis of a real three-link chain (unbuilt backends/, shadowed
eos/backend.h, system.linux swallowing the sibling rtos:), and the parser fix is
right for every config in examples/. Two of the three build-side fixes are applied at
the wrong level — a global include root and per-consumer link lines instead of the
declared dependency on eos_systems — and the parser fix is guarded by a hand-maintained
key list that will silently drift.

Findings

# Severity File:line Finding Recommended fix
1 Medium systems/CMakeLists.txt:9 systems/src/firmware.c:6 includes eos/backend.h and calls eos_backend_find(), but eos_systems still links only eos_core eos_toolchains. The PR fixes the link failure at the consumers instead — tests/CMakeLists.txt:141 adds eos_backends to test_firmware, and cmd/eos/CMakeLists.txt:5 already listed it. The library that owns the dependency still does not declare it, so every future consumer of eos_systems has to rediscover this. target_link_libraries(eos_systems PRIVATE eos_core eos_toolchains eos_backends). Then eos_backends can come off the test_firmware line — it arrives transitively with the PUBLIC include dir backends/CMakeLists.txt:17 already declares.
2 Medium CMakeLists.txt:62-66 backends/include is added to the global include_directories(), so a build-orchestration header is now on the include path of every target in the tree, including eos_kernel (CMakeLists.txt:108), eos_hal (:92) and eos_drivers (:122). Nothing stops kernel or HAL code from #include "eos/backend.h" and picking up a dependency that master design §5.1 forbids. The global entry is also not needed: backends/CMakeLists.txt:17 already exports that directory PUBLIC. Do not delete include/eos/backend.h — replace its contents with backends/include/eos/backend.h, delete backends/include/, and drop both the new global path entry and the target_include_directories in backends/. include/ stays the single public-header contract (.ai/architect.md, "Target shape"), the shadowing that caused finding 2 in the PR body becomes structurally impossible, and no new header root exists to point up a tier.
3 Medium core/src/config.c:186-199 The pop-back fires only for seven literal keys (kind, linux, kernel, rootfs, rtos, image_format, output). case SEC_SYSTEM: at core/src/config.c:209-222 is the real authority on what a system child key is, and the two lists are kept in sync by hand with nothing checking. Add an eighth key to SEC_SYSTEM later and this exact swallow bug returns for it, silently, with no test that fails. The key list is not load-bearing: in this format every descendant of a system sub-section sits at indent ≥ 2 (system.linux.provider at 2, rtos-entry keys at 3), and list items continue at config.c:170 before reaching this block. Drop the strcmp chain and keep the guard structural: if (indent <= 1 && (section == SEC_SYSTEM_LINUX || ... || section == SEC_SYSTEM_RTOS_ENTRY)) section = SEC_SYSTEM;. Same behaviour on every config in examples/, and it cannot drift.
4 Low core/src/config.c:202-208 The same defect is left in place one section up, in the function this PR is fixing. SEC_TOOLCHAIN_LINUX pops back for exactly one sibling (rtos, :204) and SEC_TOOLCHAIN_RTOS pops back for nothing, so a target: at toolchain's own indent written after the linux:/rtos: blocks lands in cfg->toolchain.rtos_target (:207) instead of cfg->toolchain.target — silently, exactly like rtos_count staying 0. No file under examples/ orders a config that way, which is why this is Low and not the same severity as the system case. Apply the structural guard from finding 3 to SEC_TOOLCHAIN_LINUX/SEC_TOOLCHAIN_RTOS in the same commit; it is three lines and removes the class rather than one instance.
5 Low core/src/config.c:209-222, core/include/eos/config.h:75-85 Adjacent silent drop the PR does not claim to fix, noted because it is the same failure mode: system.entry is parsed by nothing — case SEC_SYSTEM: has no entry branch and EosSystemConfig has no entry field — yet six shipped configs set it (examples/ui-touchscreen/eos.yaml:19, posix-app, uart-echo, multitask-rtos, multicore-amp, blink-gpio). A non-hybrid system.entry goes nowhere and nothing reports it. Out of scope for this PR. Worth its own issue: either give EosSystemConfig an entry field or make an unrecognised key under a known section a diagnostic rather than a no-op. The second is the fix that would have caught all of these.
6 Low pr.json body The body describes a 5-file change verified at ctest 39/39. The head under review also builds cmd/eos and the 2026-09-02 comment reports 40/40 plus the eos binary. The body is the first thing a reviewer reads and it no longer matches the head. Fold the 2026-09-02 comment into the body before merge.

The claims are backed: 26 required checks pass in checks.txt, including Cross-compile ARM Cortex-M4, Cross-compile ARM64 kernel, Default configuration (no tests, no product) and QEMU Boot Test, which is what the NOT CMAKE_CROSSCOMPILING guard and
the new add_subdirectory calls needed to be exercised by. The registration-guard entry
removed from tests/unit/test_cmake_test_registration.py:32-35 is correct — test_firmware.c
is registered at tests/CMakeLists.txt:140-142, so leaving it in NOT_BUILT would have
failed test_not_built_list_has_no_stale_entries. No check is weakened, no assertion
removed, no permission widened.

Architecture conformance

§5.1 architectural law — conforms in effect, weakened structurally. No new
dependency points up a tier in this diff. But finding 2 removes the barrier: making
backends/include a global header root means every lower-layer target in the repo can
now resolve eos/backend.h. core/src/scheduler.c:6 already includes it, and
eos_core neither links eos_backends nor adds its include dir — it compiles only
because of the new global path, while eos_backends links eos_core (backends/CMakeLists.txt:20).
That is a header-level cycle held together by a global include path. Finding 2's fix
resolves it into the shared contract in include/, which is what include/ is for.

§21 tier placement — deviates, and the deviation is older than this PR. backends/
wraps CMake, Ninja, Make, Kbuild, Zephyr, FreeRTOS, NuttX, Meson, Autotools, Buildroot
and Cargo, and cmd/eos/main.c drives them from a CLI. That is build orchestration.
Master design §9 makes eBuild "the central developer control plane", and Appendix C
lists "Approve eBuild as the only build orchestration/control plane" as a leadership
decision. §21 puts eos and ebuild in the same Tier 1 without drawing a boundary
between them. This PR did not create that overlap, but it is the change that makes it
real — before it, backends/ compiled into nothing and the eos binary did not exist;
after it, the repo ships a second build-orchestration control plane. Flagging for an
architecture decision, not asking for a revert: the dead code was worse. Proposal
appended to .ai/autoreview/proposals/2026-09.md.

§21.1 split policy — not engaged; nothing is being split out here.

Proposed changes

Smallest sequence that keeps every target building:

  1. git mv backends/include/eos/backend.h include/eos/backend.h (overwriting the stale
    copy rather than deleting it), remove target_include_directories(eos_backends ...)
    from backends/CMakeLists.txt:16-18, and drop the backends/include line from
    CMakeLists.txt:65. Build stays green at every step: the header content is
    unchanged, only its location.
  2. Add eos_backends to target_link_libraries in systems/CMakeLists.txt:9; remove
    it from tests/CMakeLists.txt:141.
  3. In core/src/config.c, replace the seven-key strcmp chain at :193-198 with the
    section test alone, and extend the same guard to SEC_TOOLCHAIN_LINUX /
    SEC_TOOLCHAIN_RTOS.
  4. Add one test_firmware case for the toolchain ordering in finding 4 so the
    generalised guard is covered, not just the system path.

Steps 1 and 2 are pure build-graph moves with no behaviour change; step 3 changes no
outcome for any config in examples/; step 4 is new coverage. None of them needs to be
in the same commit as another.

Not checked

  • I did not build this branch or run ctest. The 40/40 and eos info output in the
    2026-09-02 comment are the author's, not reproduced here; the CI results in
    checks.txt are the independent evidence and they are green for the head SHA.
  • Finding 4 is reasoned from core/src/config.c:202-208, not from a failing run — I
    read every eos.yaml under examples/ and none orders toolchain: in the way that
    triggers it, so I could not demonstrate it.
  • indent_level() (core/src/config.c:36-39) counts spaces only; a tab-indented config
    yields indent 0 for every line and behaves unpredictably in this block and in the
    pre-existing one at :234. Pre-existing, not introduced here, and I did not
    characterise it.
  • Whether eos_backend_register()'s EOS_MAX_BACKENDS 16 bound is enforced for the 11
    registrations cmd/eos/main.c performs — backends/src/backend.c is outside this
    diff and I did not read it.

Automated architecture review of e7246335fae7 — scheduled, model claude-opus-5, checked against the EmbeddedOS Master Design v2.0. Advisory only: this reviewer never approves, requests changes, or merges. Reply here to discuss or push back — a wrong finding is a bug worth reporting.

Kartikey1306 added a commit to Kartikey1306/eos that referenced this pull request Sep 3, 2026
…ARM file

Answers the review on embeddedos-org#126.

Finding 1 (Medium) -- the head carried an undisclosed second change, the
test_crypto_ed25519_loworder registration, which contradicted this PR's own
2026-09-02 comment ("this PR changes one file ... and does not touch tests/").
It was also the fourth open copy of that registration, at a different position
from embeddedos-org#127's. Dropped: the branch is rebased to the toolchain change alone, and
embeddedos-org#127 is the one to take for the registration -- it restores the four lines to
their original position, keeps the crypto suites grouped, and is the position
embeddedos-org#122 is already written against.

Finding 2 (Medium) -- nothing would catch this coming back. Every ARM
toolchain here sets CMAKE_TRY_COMPILE_TARGET_TYPE to STATIC_LIBRARY and no
cross build links an executable, so `Cross-compile ARM Cortex-M4` passed
identically with the duplicate present and absent; it passed on the unfixed
commit. The defect surfaced by accident when embeddedos-org#118 briefly put cmd/eos into the
cross build, and embeddedos-org#118 has since gated that to host builds -- removing the
accident too.

Adds tests/unit/test_toolchain_specs.py, modelled on
test_cmake_test_registration.py: parse every toolchains/*.cmake, extract each
-specs=<file>, and fail if one appears in more than one variable that reaches
a link line (CMake links through the compiler driver, so C/CXX flags do). A
second test asserts specs are declared on the linker flags rather than the
compiler flags, which is the arrangement that turns into a duplicate the
moment someone adds the linker entry that looks missing. No ARM toolchain
needed; runs in the existing pytest job.

Finding 3 (Low) -- arm-none-eabi-r5.cmake put --specs=nosys.specs in
CMAKE_C_FLAGS_INIT with nothing in CMAKE_EXE_LINKER_FLAGS_INIT. Not broken
today, and one added linker -specs= away from the same failure. Moved, so all
three ARM bare-metal toolchains state it the same way and the new guard is
green across all of them.

Verified:
  pytest tests/unit/test_toolchain_specs.py    2 passed
  pytest tests/                                all pass
  discrimination: reintroducing the duplicate in arm-cortex-m4.cmake gives
      arm-cortex-m4.cmake: -specs=nosys.specs is set in CMAKE_C_FLAGS_INIT
      and CMAKE_EXE_LINKER_FLAGS_INIT, so it reaches the link line more than
      once
    on both tests, so the guard catches the original defect rather than
    restating the fix.
  No ARM toolchain on this machine, so the cross link itself is NOT verified
  here; that remains CI's to show.

Refs embeddedos-org#126
Kartikey1306 added a commit to Kartikey1306/eos that referenced this pull request Sep 3, 2026
…ARM file

Answers the review on embeddedos-org#126.

Finding 1 (Medium) -- the head carried an undisclosed second change, the
test_crypto_ed25519_loworder registration, which contradicted this PR's own
2026-09-02 comment ("this PR changes one file ... and does not touch tests/").
It was also the fourth open copy of that registration, at a different position
from embeddedos-org#127's. Dropped: the branch is rebased to the toolchain change alone, and
embeddedos-org#127 is the one to take for the registration -- it restores the four lines to
their original position, keeps the crypto suites grouped, and is the position
embeddedos-org#122 is already written against.

Finding 2 (Medium) -- nothing would catch this coming back. Every ARM
toolchain here sets CMAKE_TRY_COMPILE_TARGET_TYPE to STATIC_LIBRARY and no
cross build links an executable, so `Cross-compile ARM Cortex-M4` passed
identically with the duplicate present and absent; it passed on the unfixed
commit. The defect surfaced by accident when embeddedos-org#118 briefly put cmd/eos into the
cross build, and embeddedos-org#118 has since gated that to host builds -- removing the
accident too.

Adds tests/unit/test_toolchain_specs.py, modelled on
test_cmake_test_registration.py: parse every toolchains/*.cmake, extract each
-specs=<file>, and fail if one appears in more than one variable that reaches
a link line (CMake links through the compiler driver, so C/CXX flags do). A
second test asserts specs are declared on the linker flags rather than the
compiler flags, which is the arrangement that turns into a duplicate the
moment someone adds the linker entry that looks missing. No ARM toolchain
needed; runs in the existing pytest job.

Finding 3 (Low) -- arm-none-eabi-r5.cmake put --specs=nosys.specs in
CMAKE_C_FLAGS_INIT with nothing in CMAKE_EXE_LINKER_FLAGS_INIT. Not broken
today, and one added linker -specs= away from the same failure. Moved, so all
three ARM bare-metal toolchains state it the same way and the new guard is
green across all of them.

Verified:
  pytest tests/unit/test_toolchain_specs.py    2 passed
  pytest tests/                                all pass
  discrimination: reintroducing the duplicate in arm-cortex-m4.cmake gives
      arm-cortex-m4.cmake: -specs=nosys.specs is set in CMAKE_C_FLAGS_INIT
      and CMAKE_EXE_LINKER_FLAGS_INIT, so it reaches the link line more than
      once
    on both tests, so the guard catches the original defect rather than
    restating the fix.
  No ARM toolchain on this machine, so the cross link itself is NOT verified
  here; that remains CI's to show.

Refs embeddedos-org#126
…removed

`CI — eos` and `Python Unit Tests` are red on master (769191a), and both
fail for one reason:

    these suites exist in tests/ but no add_executable() in
    tests/CMakeLists.txt builds them, so they never run:
    ['test_crypto_ed25519_loworder.c']

3aa8644 (embeddedos-org#93, "remove duplicate test targets from tests/CMakeLists.txt")
removed all four lines that build and register
test_crypto_ed25519_loworder. It was not a duplicate — there was exactly
one registration before that commit, and it took it.

What stopped running is the suite that guards embeddedos-org#101: Ed25519 rejecting
low-order public keys. A low-order key makes every term of the
verification equation collapse to the identity regardless of the
message, so a signature of all zeros verifies against any content at
all. The fix is still in services/crypto/src/ed25519_verify.c and still
correct; nothing has been checking it since embeddedos-org#93 merged, and nothing
would have noticed if a later change had removed it too.

Restored verbatim from 3aa8644^, in its original position before
test_crypto_sha512. No other file changes.

The guard that caught this is test_cmake_test_registration.py, added
recently. It did exactly its job — this is the first thing it found.

Verified:
  test_cmake_test_registration.py    -> 5 passed (1 failed on master)
  pytest tests/                      -> 15 passed (14 passed 1 failed on master)
  test_crypto_ed25519_loworder       -> 5/5 tests passed
  ctest                              -> 39/39 passed (master builds 38)
…-back structural

Answers the review on embeddedos-org#118.

Finding 1 (Medium) -- systems/src/firmware.c includes eos/backend.h and calls
eos_backend_find(), but eos_systems linked only eos_core and eos_toolchains;
the dependency was satisfied at each consumer instead, so every future user of
eos_systems had to rediscover it. eos_systems now declares it, and
eos_backends comes off the test_firmware line.

Finding 2 (Medium) -- backends/include was added to the global
include_directories(), putting a build-orchestration header on the include
path of every target in the tree, eos_kernel and eos_hal included. Nothing
then stops kernel or HAL code acquiring a dependency master design 5.1
forbids. Taken the reviewer's version rather than a narrower one: the header
moves to include/eos/backend.h -- replacing the stale copy that caused the
original shadowing -- backends/include is gone, and both the global entry and
backends' own target_include_directories with it. include/ stays the single
public-header contract, and the shadow is now structurally impossible rather
than merely resolved.

Finding 3 (Medium) -- the pop-back fired only for seven literal keys, which
made it a second authority on what a `system` child key is, kept in step with
`case SEC_SYSTEM:` by hand and by nothing else. Add an eighth key there and
the swallow returns for it silently. Now structural: every descendant of a
system sub-section sits at indent >= 2 and list items `continue` earlier, so
indent <= 1 is exactly the condition. Same behaviour on every config under
examples/, and it cannot drift.

Finding 4 (Low) -- applied the same structural guard to
SEC_TOOLCHAIN_LINUX/SEC_TOOLCHAIN_RTOS, which popped back for one sibling and
none respectively. A `target:` at toolchain's own indent written after the
linux:/rtos: blocks landed in toolchain.rtos_target instead of
toolchain.target. Not reachable by any config in examples/ today, which is
why it had not bitten.

Also dropped the test_crypto_ed25519_loworder registration hunk; this branch
is stacked on embeddedos-org#127, which is the copy to keep.

NOT fixed, and confirmed while testing: finding 5, `system.entry` is parsed by
nothing -- no branch in `case SEC_SYSTEM:` and no field on EosSystemConfig --
while six shipped configs set it. examples/multicore-amp/eos.yaml sets
`entry: main.c` and `eos info` reports nothing for it. Same failure mode, but
it needs a new struct field and a decision about what consumes it, so it is a
follow-up rather than something to fold in here.

Verified:
  ctest                    40/40 PASS
  pytest tests/            15 passed
  eos info, in examples/industrial-gateway:
      kind: hybrid / kernel provider: buildroot / RTOS targets (1)
    which is the parser fix visible end to end -- RTOS targets reads 0 before
    it. examples/multicore-amp reports 0, correctly: that config has no rtos
    list at all.

Refs embeddedos-org#118
@Kartikey1306
Kartikey1306 force-pushed the fix/build-the-backend-registry branch from e724633 to 13c20b4 Compare September 3, 2026 10:15

@srpatcha srpatcha left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review — eos#118 "fix(build): build the backend registry, and stop system.linux eating rtos"

head: 13c20b4 author: Kartikey1306 ci: fail (both failures inherited from eBoot master — see CI)

Verdict: The build wiring is right and I reproduced every figure in the PR (40/40 ctest, 15/15 pytest, clean default build). The parser fix is real but only for 2-space YAML: both new pop-back guards hardcode indent <= 1 against an indent_level() that divides by two, so a 4-space config still gets RTOS targets (0) — plus a new symptom, the RTOS toolchain target silently overwriting the Linux one.

Findings

# Severity File:line Finding Recommended fix
1 High core/src/config.c:161-164, :175-178 Both new guards test indent <= 1, but indent_level() is spaces / 2 (:36-40), so "system's own child level" is a 2-space assumption, not the structural fact the comment claims. Every other part of this parser is indentation-agnostic — if (indent == 0) for top level, and the descend-on-key cases carry no indent test — so 4-space YAML works everywhere except here. With 4 spaces the guards never fire and the exact defect this PR is titled for comes back, silently, together with a second one. Verified with the eos binary this PR builds, on two files with identical semantics: 2-space → Toolchain: target aarch64-linux-gnu / rtos arm-none-eabi, RTOS targets (1), Firmware Targets (1). 4-space → Toolchain: target arm-none-eabi (the RTOS target overwrote the Linux cross target), RTOS targets (0), Firmware Targets (0). That is "a hybrid build produced no RTOS firmware without saying so", still. Record the indent at which each sub-section was entered and pop when a later key sits at or below it, instead of comparing to a literal. At each section = SEC_SYSTEM_LINUX; continue;-style descent, also store section_indent = indent;, then the guard becomes if (indent <= section_indent && …). That is indentation-agnostic, removes the /2 dependency from the pop-back logic, and also repairs the pre-existing literal at :271 (SEC_SYSTEM_KERNEL, indent <= 1 && key == "rootfs"), which has the same flaw. Add a 4-space fixture to test_firmware.c::test_config_load_hybrid — today it only loads examples/industrial-gateway/eos.yaml, which is 2-space, so nothing in the suite can see this.
2 Medium include/eos/backend.h:1-13 The surviving public header lost the extern "C" guard the deleted include/ version had (#ifdef __cplusplus / extern "C" {). .ai/architect.md calls include/ "public headers — the contract other repos compile against", and EoStudio and eBrowser are C++ clients of this tree. Verified nothing in-tree breaks: no .cpp/.cc/.hpp/.cxx translation unit includes eos/backend.h. Worth settling rather than drifting — only 1 of the 4 headers in include/eos/ (package.h) carries the guard now, and this change took it from 2 to 1. Re-add the __cplusplus guard around the declarations in the surviving header.
3 Medium include/eos/backend.h (whole file), pr.json body Consolidating two divergent copies of one public header is the right call, but it changes the declared API and the body does not mention it. EosBackend::name goes const char * → char[EOS_MAX_NAME]; the eos_backend_configure_fn / _build_fn / _install_fn typedefs and void *user_data are removed; returns move int → EosResult; and eos_backend_init_all() is deleted from the public surface. Verified there is no in-tree fallout — zero references to eos_backend_init_all anywhere in the tree, and EosBackend::user_data had no users (the only user_data hits are EosNode's, in core/src/graph.c:32). But §23.2 makes the source-level EoS API a versioned contract and §30/§38 want an RFC/ADR for this class of change, and a reader of the diff cannot tell which of the two copies was live. One paragraph in the body: backends/include was on eos_backends' PUBLIC include path but backends/ was in no add_subdirectory(), so nothing consumed it — the include/ copy is what systems/src/firmware.c actually compiled against, and this PR replaces its signatures with the backends/ shape. State that, and that no consumer used the removed declarations. That turns an unexplained API break into a deliberate consolidation.
4 Low CMakeLists.txt:16-29 The if(NOT CMAKE_CROSSCOMPILING) gate is correct on its own merits — a host build-system driver has no business in a cross build — but the justification in the comment is about to become wrong. The comment attributes the gate to nosys.specs being supplied twice on a Cortex-M4 link, and eos#126 fixes exactly that; its own docstring says the defect "was found by accident, when a PR briefly put cmd/eos into the cross build", which is this PR. Once #126 lands, the comment names a bug that no longer exists as the reason for a gate that should stay anyway. Keep the gate, and reword the comment to lead with the real reason (cmd/eos is a host tool, gated like services/pkg), with the nosys.specs incident as a historical note pointing at #126 rather than as the rationale.
5 Low tests/CMakeLists.txt:93-96 The test_crypto_ed25519_loworder registration block is eos#125's fix, carried here so this branch is not red on the registration guard. Confirmed necessary: origin/master has tests/test_crypto_ed25519_loworder.c but does not register it, so tests/unit/test_cmake_test_registration.py fails on master today. But eos#126 adds the identical block at the identical anchor, so #118, #125 and #126 will all conflict in tests/CMakeLists.txt, and whichever two land later each need a one-hunk resolution. This is the same situation the author already flagged for #115. Land #125 first, then rebase #118 and #126 and drop the duplicated hunk from both. Worth adding to the existing merge-order comment so the third collision is not discovered at merge time.

CI

Two checks red, and neither is caused by this PR:

  • Cross-compile ARM64 kernel — .github/workflows/eos-simulation.yml:28-33 checks out embeddedos-org/eBoot@master alongside eos and builds it. eBoot's master does not compile. Job log: eBoot/include/eos_image.h:135 and :142, error: 'eos_image_header_t' has no member named 'reserved', failing eboot_core. I reproduced this independently by building eBoot origin/master (22d8f8b) in a clean worktree; it has three distinct pre-existing errors from one bad merge, and eBoot#94 already targets them.
  • Full-stack integration summary — needs: [build-kernel, …] (:325-329), so it reports FAIL purely because of the above. QEMU ARM64 simulation (13 TCs) shows skipping for the same reason.

Everything else is green, including Cross-compile ARM Cortex-M4 (both instances), Host Tests (x86_64), Build & Test (Linux x86_64), Default configuration (no tests, no product), Static Analysis, Analyze (C/C++), CodeQL and QEMU Boot Test.

Reproduced locally at head 13c20b4 (fresh worktree, host gcc):

cmake -S . -B bt -DEOS_BUILD_TESTS=ON && cmake --build bt   # rc=0, no errors
ctest --test-dir bt --no-tests=error                        # 40/40 passed
cmake -S . -B bdef && cmake --build bdef                    # default (no tests) rc=0, 0 errors
pytest tests/ -q                                            # 15 passed
pytest tests/unit/test_cmake_test_registration.py -q        # 5 passed

Every figure in the PR body and comments checks out, including the "was 38 on master" delta and the working eos info output. Finding 1 was found by running that same binary against a 4-space config, not by reading.

Architecture conformance

Conforms on layering; the header consolidation is an improvement; one deviation on process.

  • §5.1 / .ai/architect.md dependency direction — conforms, and the systems/CMakeLists.txt change is the right correction. systems/src/firmware.c includes eos/backend.h and calls eos_backend_find(), so eos_systems is where eos_backends belongs; declaring it at each consumer made every future consumer rediscover it. Nothing points up a tier: eos_backends links eos_core PRIVATE, eos_systems links eos_core eos_toolchains eos_backends, and cmd/eos sits above all of them. No product-repo dependency is introduced.
  • include/ as the contract — conforms in direction. Two files named eos/backend.h with different struct layouts, one of them shadowing the other via a PUBLIC include path, is precisely the "widen a public header to make one caller compile" failure mode inverted; collapsing to a single copy under include/ is what .ai/architect.md asks for. Findings 2 and 3 are about how, not whether.
  • §21 tier placement — correct. eos is Tier 1 Foundation; the backend registry, the systems layer and the eos CLI all belong here. §21.1 is not engaged — nothing is being split out.
  • .ai/architect.md, "Restructure and change behaviour in the same commit" — deviates, but defensibly. This PR both wires up backends/+cmd/eos and changes the config parser. The coupling is genuine: test_firmware is the test that proves the wiring, and it cannot pass without the parser fix, so neither half lands alone. Worth saying explicitly in the body rather than leaving a reviewer to infer it. The part that is not coupled is the toolchain pop-back (:175-178) — no test needs it, no config exercises it, and the author says so ("No config under examples/ is ordered that way today, which is the only reason it has not bitten"). Finding 1 shows that untested half is where the remaining defect lives.
  • Weakened checks — checked, none. Removing test_firmware.c from the skip list in tests/unit/test_cmake_test_registration.py is the opposite: a suite that was excused is now built and run.

Proposed changes

  1. Finding 1 — remembered section indent plus a 4-space fixture. This is the one that matters; without it the PR's headline fix covers only the indentation style the examples happen to use.
  2. Finding 2 — restore the extern "C" guard. One hunk.
  3. Findings 3 and 4 — body paragraph and comment reword. No code.
  4. Finding 5 — land eos#125, then rebase #118 and #126 and drop the duplicated tests/CMakeLists.txt hunk from both.
  5. Independently, and not this author's problem: eBoot#94 has to land before any eos PR can show a green Cross-compile ARM64 kernel.

I opened no fix PR. Finding 1 is High and the fix is small, but it can only be expressed on top of this branch — the indent <= 1 guards do not exist on master — and the brief forbids touching a branch that belongs to an open PR.

Not checked

  • The 4-space behaviour is verified; the proposed fix is not. I did not implement the remembered-indent change, so I have not confirmed it keeps 40/40 green or that indent <= section_indent is right at every descent site (SEC_PKG_* and SEC_LAYERS reach the guards via different paths and were not analysed).
  • Tab-indented YAML was not tested. indent_level() counts only spaces, so a tab-indented file yields indent 0 for every line and would be parsed as all-top-level. YAML forbids tabs for indentation, so I did not treat this as a finding, but nothing in the parser rejects it either.
  • No cross build was run locally. Cross-compile ARM Cortex-M4 passing in CI is the only evidence that the NOT CMAKE_CROSSCOMPILING gate does what finding 4 discusses; I did not install arm-none-eabi to confirm the nosys.specs failure mode the comment describes.
  • The 11 backends were not exercised. eos_backend_registry_init() plus each eos_backend_*_init() now compile and the binary runs eos info, but no backend's configure/build/install/clean was invoked against a real build system. The author's claim that none carries a TODO or stub marker was not verified.
  • eos info was run against synthetic configs and examples/industrial-gateway, not against every file under examples/; other examples may have their own indentation.
  • The removed public declarations were checked for in-tree callers only. Whether any other repo in the org compiles against include/eos/backend.h was not checked, which is the gap finding 3 asks the body to close.

Automated architecture review of 13c20b404eba — scheduled, model claude-opus-5, checked against the EmbeddedOS Master Design v2.0. Advisory only: this reviewer never approves, requests changes, or merges. Reply here to discuss or push back — a wrong finding is a bug worth reporting.

Kartikey1306 added a commit to Kartikey1306/eos that referenced this pull request Sep 3, 2026
Residue from 0957ac4, which dropped the borrowed
test_crypto_ed25519_loworder registration and left the blank line that had
separated it from the block below. It is the only remaining change this PR
makes to tests/CMakeLists.txt, it changes nothing, and it sits at the exact
anchor where embeddedos-org#118, embeddedos-org#119 and embeddedos-org#127 each add the real registration block -- so
it would have conflicted with whichever of those lands first, for no reason
at all.

tests/CMakeLists.txt is now untouched by this branch.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Kartikey1306 added a commit to Kartikey1306/eos that referenced this pull request Sep 3, 2026
…ARM file

Answers the review on embeddedos-org#126.

Finding 1 (Medium) -- the head carried an undisclosed second change, the
test_crypto_ed25519_loworder registration, which contradicted this PR's own
2026-09-02 comment ("this PR changes one file ... and does not touch tests/").
It was also the fourth open copy of that registration, at a different position
from embeddedos-org#127's. Dropped: the branch is rebased to the toolchain change alone, and
embeddedos-org#127 is the one to take for the registration -- it restores the four lines to
their original position, keeps the crypto suites grouped, and is the position
embeddedos-org#122 is already written against.

Finding 2 (Medium) -- nothing would catch this coming back. Every ARM
toolchain here sets CMAKE_TRY_COMPILE_TARGET_TYPE to STATIC_LIBRARY and no
cross build links an executable, so `Cross-compile ARM Cortex-M4` passed
identically with the duplicate present and absent; it passed on the unfixed
commit. The defect surfaced by accident when embeddedos-org#118 briefly put cmd/eos into the
cross build, and embeddedos-org#118 has since gated that to host builds -- removing the
accident too.

Adds tests/unit/test_toolchain_specs.py, modelled on
test_cmake_test_registration.py: parse every toolchains/*.cmake, extract each
-specs=<file>, and fail if one appears in more than one variable that reaches
a link line (CMake links through the compiler driver, so C/CXX flags do). A
second test asserts specs are declared on the linker flags rather than the
compiler flags, which is the arrangement that turns into a duplicate the
moment someone adds the linker entry that looks missing. No ARM toolchain
needed; runs in the existing pytest job.

Finding 3 (Low) -- arm-none-eabi-r5.cmake put --specs=nosys.specs in
CMAKE_C_FLAGS_INIT with nothing in CMAKE_EXE_LINKER_FLAGS_INIT. Not broken
today, and one added linker -specs= away from the same failure. Moved, so all
three ARM bare-metal toolchains state it the same way and the new guard is
green across all of them.

Verified:
  pytest tests/unit/test_toolchain_specs.py    2 passed
  pytest tests/                                all pass
  discrimination: reintroducing the duplicate in arm-cortex-m4.cmake gives
      arm-cortex-m4.cmake: -specs=nosys.specs is set in CMAKE_C_FLAGS_INIT
      and CMAKE_EXE_LINKER_FLAGS_INIT, so it reaches the link line more than
      once
    on both tests, so the guard catches the original defect rather than
    restating the fix.
  No ARM toolchain on this machine, so the cross link itself is NOT verified
  here; that remains CI's to show.

Refs embeddedos-org#126
Kartikey1306 added a commit to Kartikey1306/eos that referenced this pull request Sep 3, 2026
Residue from 0957ac4, which dropped the borrowed
test_crypto_ed25519_loworder registration and left the blank line that had
separated it from the block below. It is the only remaining change this PR
makes to tests/CMakeLists.txt, it changes nothing, and it sits at the exact
anchor where embeddedos-org#118, embeddedos-org#119 and embeddedos-org#127 each add the real registration block -- so
it would have conflicted with whichever of those lands first, for no reason
at all.

tests/CMakeLists.txt is now untouched by this branch.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every open eos pull request is currently red on `EoS Full-Stack Simulation`,
and none of them caused it. The kernel job checks out embeddedos-org/eBoot at
`ref: master`, and eBoot master does not compile: include/eos_image.h:135 and
:142 static-assert offsetof(eos_image_header_t, reserved) for a member that
became tlv_len + tlv_hash, and core/ed25519_verify.c redefines
point_is_identity -- two pairs of PRs that each merged clean and broke the
build together. eBoot embeddedos-org#94 repairs it and waits on review; until it lands, no
change to any eos PR can make this job pass, and after it lands the next
broken eBoot master does the same thing again.

A floating `ref: master` makes every eos PR's CI depend on the moment-to-
moment state of another repository's default branch. Pinning the two
compiled-against checkouts makes the job a function of this repository plus
two recorded SHAs -- red means the PR broke something, green means it did not,
and a cross-repo bump is a reviewable one-line diff with CI on it.

  EBOOT_COMMIT  a172a6d6e1e6  the newest eBoot master ancestor that compiles;
                found by walking master back and building each candidate.
                Its child 8a015b2 already carries the ed25519 redefinition.
  EBUILD_COMMIT e5d8052f3e2c  ebuild's current master, frozen as-is. Both of
                its checkouts feed steps that are commented out (eFab does
                not exist), so this pin only removes the float.

The middleware matrix (10 repos) still floats; it runs those repos' own
tests rather than compiling eos against them, and 10 more pins deserve their
own decision. When eBoot embeddedos-org#94 merges, bump EBOOT_COMMIT to the merge SHA.

Verified before pushing, master vs pin under the job's own configuration
(-DEBLDR_BOARD=qemu_arm64 -DEBLDR_VERIFY_STAGE1=OFF -DCMAKE_BUILD_TYPE=Release,
cross-compile mode): eBoot@master fails at the eos_image.h asserts, the exact
error in the job log; eBoot@a172a6d builds every target, rc=0. Both pinned
SHAs verified reachable from their repos' master. Workflow parses; the three
`ref:` edits and the env block are the whole diff. This PR's own simulation
run is the live test: it uses this branch's workflow and should go green
while every master-based PR stays red.

Carried onto this branch from embeddedos-org#135 (001128d) so this PR's own simulation
job runs against a buildable eBoot: each pull request executes its own copy
of the workflow, so the fix greens a PR only once its branch contains it.
Byte-identical to embeddedos-org#135's commit -- git drops it as already-upstream the
moment embeddedos-org#135 merges. The pin itself is embeddedos-org#135's to review.

@srpatcha srpatcha left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review — eos#118 "fix(build): build the backend registry, and stop system.linux eating rtos"

head: f95e4cb author: Kartikey1306 ci: 25 pass, 0 failing, Create GitHub Release skipping

Verdict: The build wiring is still right and everything reproduces at this head: test build clean, ctest --no-tests=error 40/40, default build clean, and bdef/cmd/eos/eos exists and runs. CI is now fully green, which is what f95e4cb buys — and f95e4cb is a byte-identical copy of #135's cross-repo pin, the sixth open PR carrying it. The High finding from 13c20b40 is unresolved, and the commit that was meant to address it (13c20b4, "make the pop-back structural") changed the comment rather than the condition, so the code now documents an invariant it does not have.

Findings

# Severity File:line Finding Recommended fix
1 High core/src/config.c:204, :218, :270, and the comment at :195-203 Carried from 13c20b40, unresolved, re-reproduced at this head. Both pop-back guards still test indent <= 1 against an indent_level() that is spaces / 2 (:36-40), so "system's own child level" remains a 2-space assumption. Verified with the eos binary this PR builds, on two configs with identical semantics: 2-space → RTOS targets (1), Firmware Targets (1); 4-space → RTOS targets (0), Firmware Targets (0). That is verbatim the defect in the PR title — "a hybrid build produced no RTOS firmware without saying so" — still present for any config that does not use two spaces. What 13c20b4 changed is the comment, which now argues the guard is "structural rather than a list of the seven keys" and concludes "so indent <= 1 is exactly the 'back at system's own child level' condition". It is not: it is exactly that condition given /2. A literal a reader can see is safer than a literal a comment vouches for, and the PR body is more honest than the code here — it says plainly that "indent_level() counts 2-space units, so <= 1 is exactly system:'s direct children". Store the indent at each descent: at every section = SEC_SYSTEM_LINUX; continue;-style site also set section_indent = indent;, then the guard is if (indent <= section_indent && …). Indentation-agnostic, removes the /2 dependency from the pop-back logic, and repairs the same literal at :270 (SEC_SYSTEM_ROOTFS on rootfs) and the indent <= 2 pair at :318-320. Add a 4-space fixture to test_firmware.c::test_config_load_hybrid — today it loads only examples/industrial-gateway/eos.yaml, which is 2-space, so nothing in the suite can see this.
2 Medium .github/workflows/eos-simulation.yml:11-20, :41, :48, :288 f95e4cb ("ci: pin the eBoot and ebuild checkouts instead of floating on master") duplicates #135, and also #114's f43d860, #115's 4375466, #116's 2a32e3a and #117's f7a715b. Verified: git diff of this file between this head and #135's head is empty. Six open PRs, one CI change, and none of the six bodies mentions it. Identical hunks merge cleanly so nothing breaks — the cost is that a cross-repository dependency pin is being reviewed six times inside PRs about other subjects, and once in the PR that is actually about it. Drop f95e4cb. #135 is scoped to it and has the staleness-guard commit (f34edb3) these copies lack; its substance is reviewed there and is not re-raised here.
3 Medium PR body Carried from 13c20b40, and the specific mismatch is sharper than before. The body's section 2 says the fix is to "Put backends/include ahead of it" on the include path and that "the copy in include/ is the stale one … removing it changes no caller". The diff does neither: it keeps include/eos/backend.h (68 added, 30 removed — rewritten to the backends/ shape) and deletes backends/include/eos/backend.h (81 removed). Same end state, opposite mechanism, and a reader following the body will look for a change to an include path that is not there. The numbers are stale too (ctest 39/39 (was 38); it is 40/40 at this head), as is the opening line — "Stacked on #113 and #114", where the actual stack parent is 13a8ffa, i.e. #127. Per the brief, a body whose "Verified" table describes a tree that no longer exists is the finding, because it reads as evidence. Rewrite section 2 to describe what the diff does: consolidate the two divergent eos/backend.h copies into the one under include/, taking the backends/ signatures, and state that no consumer used the removed declarations (eos_backend_init_all has zero references in the tree; EosBackend::user_data had no users). §23.2 makes the source-level EoS API a versioned contract, so that paragraph is what turns an unexplained API break into a deliberate consolidation. Then re-run and paste 40/40, and fix the stack line.
4 Medium include/eos/backend.h:1-13 Carried from 13c20b40, unresolved. The surviving public header still has no extern "C" guard; the deleted backends/include copy had one. .ai/architect.md calls include/ "public headers — the contract other repos compile against", and EoStudio and eBrowser are C++ clients of this tree. Verified nothing in-tree breaks — no .cpp/.cc/.hpp/.cxx translation unit includes eos/backend.h. Measured at this head: 1 of the 4 headers in include/eos/ carries the guard (package.h; backend.h, eos_config.h and quality.h do not), and this change took it from 2 to 1. Re-add #ifdef __cplusplus / extern "C" { around the declarations. One hunk, and it stops the ratio drifting further in a directory the architecture calls the cross-repo contract.

Resolved since 13c20b40: prior finding 4. CMakeLists.txt:149-158 now leads with the real reason for the if(NOT CMAKE_CROSSCOMPILING) gate — "Host-only, gated the same way services/pkg is. It is a build-system driver that runs on the developer's machine" — with the nosys.specs incident as the supporting detail rather than the rationale. That is what was asked for.

Still open from 13c20b40, one line: the tests/CMakeLists.txt:93-96 test_crypto_ed25519_loworder block arrives from the stack parent 13a8ffa (#127) and is not this PR's, but #126 still adds the identical block at the identical anchor (confirmed against #126's diff at this run). Whichever of #126 and the #127 stack lands second needs a one-hunk resolution. Worth adding to the merge-order comment.

Architecture conformance

Conforms on layering; the header consolidation is an improvement; the same one process deviation.

  • §5.1 / .ai/architect.md dependency direction — conforms, and the systems/CMakeLists.txt change is the right correction: systems/src/firmware.c includes eos/backend.h and calls eos_backend_find(), so eos_systems is where eos_backends belongs rather than re-declared at each consumer. Nothing points up a tier — eos_backends links eos_core PRIVATE, eos_systems links eos_core eos_toolchains eos_backends, cmd/eos sits above all of them, and no product-repo dependency is introduced.
  • include/ as the contract — conforms in direction. Two files named eos/backend.h with different struct layouts, one shadowing the other through a PUBLIC include path, is the "widen a public header to make one caller compile" failure mode inverted; collapsing to a single copy is what .ai/architect.md asks. Findings 3 and 4 are about how, not whether.
  • §21 tier placement — correct. eos is Tier 1 — Foundation; the backend registry, the systems layer and the eos CLI all belong here, and §21.1 is not engaged since nothing is being split out.
  • .ai/architect.md, "restructure and change behaviour in the same commit" — deviates, defensibly, and still unstated in the body. Wiring backends/ + cmd/eos and changing the config parser are genuinely coupled: test_firmware is the test that proves the wiring and cannot pass without the parser fix. The part that is not coupled is the toolchain pop-back at :218 — no test needs it and no config exercises it, which the body admits ("No config under examples/ is ordered that way today, which is the only reason it has not bitten"). Finding 1 is where that untested half still fails.
  • Weakened checks — none. Dropping test_firmware.c from NOT_BUILT in tests/unit/test_cmake_test_registration.py is the opposite of a weakened check: a suite that was excused is now built and run.

No architecture proposal from this PR. The pin's design gap is already recorded in .ai/autoreview/proposals/2026-09.md.

Proposed changes

  1. Finding 1 — remembered section indent plus a 4-space fixture. This is the one that matters; without it the PR's headline fix covers only the indentation style the examples happen to use, and the comment now says otherwise.
  2. Finding 2 — drop f95e4cb.
  3. Finding 4 — restore the extern "C" guard. One hunk.
  4. Finding 3 — rewrite the body's section 2, the numbers and the stack line.
  5. Coordinate tests/CMakeLists.txt with #126.

I opened no fix PR. Finding 1 is High and the fix is small, but it can only be expressed on top of this branch — the indent <= 1 guards do not exist on master — and the brief forbids touching a branch that belongs to an open PR.

Not checked

  • The 4-space failure is verified; the proposed fix is not. I did not implement the remembered-indent change, so I have not confirmed it keeps 40/40 green or that indent <= section_indent is right at every descent site (SEC_PKG_* and SEC_LAYERS reach the guards by different paths and were not analysed).
  • One symptom from the previous review did NOT reproduce in my fixture. That review reported the 4-space case also showing Toolchain: target arm-none-eabi — the RTOS target overwriting the Linux cross target. In my 4-space config, written with toolchain.rtos.target nested, both parsed correctly (target: aarch64-linux-gnu, rtos: arm-none-eabi). That symptom needs a target: key written at toolchain's own indent after the rtos: block, which my fixture did not have. So :218 remains untested rather than shown-broken by me; only the system half at :204 is reproduced here.
  • Tab-indented YAML not tested. indent_level() counts spaces only, so a tab-indented file yields indent 0 for every line. YAML forbids tabs for indentation, so not treated as a finding — but nothing in the parser rejects it either.
  • No cross build run locally. Cross-compile ARM Cortex-M4 passing in CI is the only evidence about the NOT CMAKE_CROSSCOMPILING gate; I did not install arm-none-eabi.
  • The 11 backends were not exercised. eos_backend_registry_init() and each eos_backend_*_init() compile and the binary runs eos info; no backend's configure/build/install/clean was invoked against a real build system, and the claim that none carries a TODO or stub marker was not verified.
  • pytest tests/ not run in this pass. The registration guard was run at 13c20b40 (5 passed) and tests/unit/test_cmake_test_registration.py is unchanged between that head and this one, so its result is unchanged by inspection, not by re-running. (A uv-managed pytest 9.1.1 is available on this host with the system site-packages on PYTHONPATH; I used the build time on the parser reproduction instead.)
  • eos info was run against synthetic 2-space and 4-space configs, not against every file under examples/; other examples may have their own indentation.
  • The removed public declarations were checked for in-tree callers only. Whether any other repository in the org compiles against include/eos/backend.h was not checked — that is the gap finding 3 asks the body to close.
  • The pinned eBoot tree was not built. a172a6d exists and is an ancestor of eBoot's origin/master; I did not compile eos's kernel job against it, so "the pin makes CI green" rests on checks.txt.

Evidence

Read-only git archive extraction of f95e4cb2; the eos checkout was clean and was not modified.

this head   cmake -B bt -DCMAKE_BUILD_TYPE=Release -DEOS_BUILD_TESTS=ON \
                  -DEOS_PRODUCT=vbox_test && cmake --build      -> OK
            ctest --test-dir bt --no-tests=error                -> 40/40 passed
            cmake -B bdef -DCMAKE_BUILD_TYPE=Release && build   -> OK
            bdef/cmd/eos/eos                                    -> exists, runs

finding 1, same semantics, two indentation styles, via the binary this PR builds:
    2-space eos.yaml   System: RTOS targets (1)   --- Firmware Targets (1) ---
    4-space eos.yaml   System: RTOS targets (0)   --- Firmware Targets (0) ---
    core/src/config.c:36-40  indent_level() = spaces / 2
    core/src/config.c:204    if (indent <= 1 && (section == SEC_SYSTEM_LINUX || ...
    core/src/config.c:218    if (indent <= 1 && (section == SEC_TOOLCHAIN_LINUX || ...
    core/src/config.c:270    if (indent <= 1 && strcmp(key, "rootfs") == 0)
    core/src/config.c:318-320  if (indent <= 2 && ...)     (SEC_PKG_*)

finding 2:
    git diff --stat 13c20b40 f95e4cb2
      -> .github/workflows/eos-simulation.yml | 12 +++, 3 ---   (the entire delta)
    git diff f95e4cb2 <#135 f34edb3> -- .github/workflows/eos-simulation.yml -> empty

finding 4:
    include/eos/backend.h      grep __cplusplus -> no match
    include/eos/eos_config.h   no guard
    include/eos/package.h      guard
    include/eos/quality.h      no guard

resolved (prior finding 4):
    CMakeLists.txt:149  "Host-only, gated the same way services/pkg is. It is a
                         build-system driver that runs on the developer's machine"

Automated architecture review of f95e4cb2662d — scheduled, model claude-opus-5, checked against the EmbeddedOS Master Design v2.0. Advisory only: this reviewer never approves, requests changes, or merges. Reply here to discuss or push back — a wrong finding is a bug worth reporting.

srpatcha pushed a commit that referenced this pull request Sep 8, 2026
…cs is passed twice (#126)

* fix(build): restore the low-order Ed25519 suite that #93 removed

`CI — eos` and `Python Unit Tests` are red on master (769191a), and both
fail for one reason:

    these suites exist in tests/ but no add_executable() in
    tests/CMakeLists.txt builds them, so they never run:
    ['test_crypto_ed25519_loworder.c']

3aa8644 (#93, "remove duplicate test targets from tests/CMakeLists.txt")
removed all four lines that build and register
test_crypto_ed25519_loworder. It was not a duplicate — there was exactly
one registration before that commit, and it took it.

What stopped running is the suite that guards #101: Ed25519 rejecting
low-order public keys. A low-order key makes every term of the
verification equation collapse to the identity regardless of the
message, so a signature of all zeros verifies against any content at
all. The fix is still in services/crypto/src/ed25519_verify.c and still
correct; nothing has been checking it since #93 merged, and nothing
would have noticed if a later change had removed it too.

Restored verbatim from 3aa8644^, in its original position before
test_crypto_sha512. No other file changes.

The guard that caught this is test_cmake_test_registration.py, added
recently. It did exactly its job — this is the first thing it found.

Verified:
  test_cmake_test_registration.py    -> 5 passed (1 failed on master)
  pytest tests/                      -> 15 passed (14 passed 1 failed on master)
  test_crypto_ed25519_loworder       -> 5/5 tests passed
  ctest                              -> 39/39 passed (master builds 38)

* fix(toolchain): stop passing nosys.specs twice on a Cortex-M4 link

arm-cortex-m4.cmake sets -specs=nosys.specs in CMAKE_C_FLAGS_INIT and again
in CMAKE_EXE_LINKER_FLAGS_INIT. CMake links through the compiler driver, so
both reach the link line and the specs file is read twice. A specs file may
only be read once -- the second read tries to re-rename a spec it has
already renamed:

    arm-none-eabi-gcc: fatal error: nosys.specs: attempt to rename spec
    'link_gcc_c_sequence' to already defined spec 'nosys_link_gcc_c_sequence'

It has been latent because CMAKE_TRY_COMPILE_TARGET_TYPE is STATIC_LIBRARY
and nothing in the cross build links an executable, so the duplicate never
reached a link line. It surfaced the moment one did.

The flag belongs on the link line only; arm-none-eabi-stm32f4.cmake already
keeps it there and nowhere else. This matches it. The compile line does not
need it: nosys.specs supplies the syscall stubs the linker wants.

Verified with arm-none-eabi-gcc rather than by reading. The mechanism, using
a specs file this toolchain does ship:

    once:  arm-none-eabi-gcc ... m.o -specs=dup.specs -o a.elf     -> links
    twice: arm-none-eabi-gcc -specs=dup.specs m.o -specs=dup.specs -> fatal error:
           attempt to rename spec 'link_gcc_c_sequence' to already defined
           spec 'dup_link_gcc_c_sequence'

the same error CI reports, from the same cause. And the generated build:
before, every compile command carried -specs=nosys.specs; after, none does
and the exe link flags still carry it once.

The full cross build could not be run here -- the Homebrew arm-none-eabi-gcc
ships no newlib, so nosys.specs and stdint.h are both absent. Configure
succeeds with this change and fails without it on that toolchain, which is
the part it governs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(toolchain): guard the duplicate-specs class, and align the third ARM file

Answers the review on #126.

Finding 1 (Medium) -- the head carried an undisclosed second change, the
test_crypto_ed25519_loworder registration, which contradicted this PR's own
2026-09-02 comment ("this PR changes one file ... and does not touch tests/").
It was also the fourth open copy of that registration, at a different position
from #127's. Dropped: the branch is rebased to the toolchain change alone, and
#127 is the one to take for the registration -- it restores the four lines to
their original position, keeps the crypto suites grouped, and is the position
#122 is already written against.

Finding 2 (Medium) -- nothing would catch this coming back. Every ARM
toolchain here sets CMAKE_TRY_COMPILE_TARGET_TYPE to STATIC_LIBRARY and no
cross build links an executable, so `Cross-compile ARM Cortex-M4` passed
identically with the duplicate present and absent; it passed on the unfixed
commit. The defect surfaced by accident when #118 briefly put cmd/eos into the
cross build, and #118 has since gated that to host builds -- removing the
accident too.

Adds tests/unit/test_toolchain_specs.py, modelled on
test_cmake_test_registration.py: parse every toolchains/*.cmake, extract each
-specs=<file>, and fail if one appears in more than one variable that reaches
a link line (CMake links through the compiler driver, so C/CXX flags do). A
second test asserts specs are declared on the linker flags rather than the
compiler flags, which is the arrangement that turns into a duplicate the
moment someone adds the linker entry that looks missing. No ARM toolchain
needed; runs in the existing pytest job.

Finding 3 (Low) -- arm-none-eabi-r5.cmake put --specs=nosys.specs in
CMAKE_C_FLAGS_INIT with nothing in CMAKE_EXE_LINKER_FLAGS_INIT. Not broken
today, and one added linker -specs= away from the same failure. Moved, so all
three ARM bare-metal toolchains state it the same way and the new guard is
green across all of them.

Verified:
  pytest tests/unit/test_toolchain_specs.py    2 passed
  pytest tests/                                all pass
  discrimination: reintroducing the duplicate in arm-cortex-m4.cmake gives
      arm-cortex-m4.cmake: -specs=nosys.specs is set in CMAKE_C_FLAGS_INIT
      and CMAKE_EXE_LINKER_FLAGS_INIT, so it reaches the link line more than
      once
    on both tests, so the guard catches the original defect rather than
    restating the fix.
  No ARM toolchain on this machine, so the cross link itself is NOT verified
  here; that remains CI's to show.

Refs #126

* test(toolchains): see the spec wherever it is written, and drop the borrowed hunk

Addresses findings 1-4 of the review on 126bd0c.

Finding 4 first, because it is the one that was making the branch look better
than it is. The test_crypto_ed25519_loworder registration was carried here
from #127. It made Run Python tests green on a branch that has nothing to do
with it, contradicted this PR's own 2026-09-02 comment, and sat at a different
anchor to #127's, so it would have conflicted. Dropped. That check is red
again until #127 lands, which is the honest state -- folding a master repair
into a toolchain PR to get a green tick is how a red master becomes everyone's
problem to route around.

Finding 1: the parser recognised one CMake idiom, set(VAR "literal"). It could
not see string(APPEND ...), an unquoted set(), or -- the one that matters here
-- a spec arriving through ${CPU_FLAGS}, which is how both Cortex-M4 toolchains
build their flag strings. Putting -specs= there would have reached both
CMAKE_C_FLAGS_INIT and CMAKE_EXE_LINKER_FLAGS_INIT while every check passed.
The file is now walked in order with a variable table, ${...} resolved against
what is in force at that point, and string(APPEND ...) applied. Two tests pin
those two bypasses.

Finding 3: CMAKE_ASM_FLAGS_INIT added to LINK_REACHING. CMake picks a target's
linker language from its sources, so an executable built only from .S files
links with the ASM flags. Both Cortex-M4 files set it from ${CPU_FLAGS}. The
current tree still passes; no toolchain names a spec there today.

Finding 2: the .yaml descriptors are scanned too. They are the second
authority on these flags -- toolchains/src/toolchain.c parses them into
EosToolchain.cflags/ldflags, a separate consumer from CMake -- and all five
already keep specs in ldflags, which is a good argument for this change and a
better one for holding both descriptions to one rule.

Found while verifying rather than assuming: my first regex required the closing
paren on the same line, so arm-none-eabi-stm32f4.cmake's three-line
set(CMAKE_EXE_LINKER_FLAGS_INIT ...) was invisible and every check passed
vacuously for that file. That is this test's own failure mode one level up.
Fixed, and pinned by test_a_multi_line_set_is_still_seen and
test_every_arm_toolchain_is_actually_parsed -- the latter asserts the parser
finds specs in all three ARM files, so a parser that silently sees nothing can
no longer pass.

Verified: the parser reports arm-cortex-m4 [nosys, nano], arm-none-eabi-r5
[nosys], arm-none-eabi-stm32f4 [nano, nosys], all on
CMAKE_EXE_LINKER_FLAGS_INIT. Mutations caught: a spec hidden in CPU_FLAGS, and
the original duplicate re-introduced on CMAKE_C_FLAGS_INIT.

7 toolchain tests pass, ctest 38/38.

* test(toolchains): drop the blank line the removed hunk left behind

Residue from 0957ac4, which dropped the borrowed
test_crypto_ed25519_loworder registration and left the blank line that had
separated it from the block below. It is the only remaining change this PR
makes to tests/CMakeLists.txt, it changes nothing, and it sits at the exact
anchor where #118, #119 and #127 each add the real registration block -- so
it would have conflicted with whichever of those lands first, for no reason
at all.

tests/CMakeLists.txt is now untouched by this branch.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(build): take the loworder registration from #127, not a copy of it

Restacked onto #127. The previous head dropped the registration hunk
entirely, per finding 4, which left Run Python tests red by design until
#127 landed. Stacking gets the same result without a duplicate: the hunk
comes from #127, this branch adds none of its own, and the diff against
#127 is toolchain-only.

Verified locally before pushing: pytest 22 passed (including
test_every_c_suite_is_built_or_listed, the check that was failing), cmake
configure and build clean, ctest 39/39 with test_crypto_ed25519_loworder
among them.

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
@srpatcha
srpatcha merged commit ccf77b3 into embeddedos-org:master Sep 8, 2026
16 of 22 checks passed
Kartikey1306 added a commit to Kartikey1306/eos that referenced this pull request Sep 14, 2026
Nine PRs were merged into master within minutes on 09-08, each on the base it
was written against. Master has not compiled since, and the Python guards
that would have named the rest never ran because the C build failed first.

Compile (every C job, CodeQL, and the ARM64 kernel in the simulation):
- services/linux/src/linux_security.c: embeddedos-org#119 and embeddedos-org#132 each added an #else to
  the same #ifndef _WIN32 in eos_busybox_install_to_rootfs(), so master has
  "#else after #else". The embeddedos-org#132 arm (`(void)bb;`) is the one removed -- the
  embeddedos-org#119 arm already uses bb and reports the unsupported platform.

Guards from embeddedos-org#121 / embeddedos-org#93 that later merges walked back:
- ci.yml: embeddedos-org#132 added windows-test after embeddedos-org#121's gate; the gate did not wait
  for it, so "CI Gate" could be green with the MSVC leg red.
- test_ci_gate.py: embeddedos-org#129 gave eosim-sanity.yml and simulation-test.yml a
  path-filtered pull_request trigger (they test their own edits); a
  path-filtered check cannot be required, so both are recorded in
  NOT_REQUIRED with the book-build.yml reason.
- tests/CMakeLists.txt: test_linux_security_paths (embeddedos-org#119) and test_pkg_fetch
  (embeddedos-org#115) had no add_executable(). embeddedos-org#115's replay replaced embeddedos-org#119's block with
  its own, and embeddedos-org#118's replay replaced that; two suites compiled against
  nothing. Both registered again. 41 -> 43 suites.
- tests/test_kernel.c: four tests from embeddedos-org#130 and embeddedos-org#131 were defined and never
  called -- their RUN() lines did not survive the replay of main().

And the one that was not bookkeeping:
- kernel/src/task.c: embeddedos-org#130 was merged after embeddedos-org#131 from a base that predates it,
  and its copy of task.c replaced embeddedos-org#131's. embeddedos-org#131 had also flattened 574 CRLF
  line endings, so its 1166-line diff hid a 39/21 change and the replay took
  embeddedos-org#130's side wholesale. Master kept embeddedos-org#131's tests and lost its kernel: the
  idle task could be deleted and suspended, a half-initialised TCB was
  published to the scheduler before its stack existed, and eos_schedule()
  pointed g_current_sp at the outgoing task. test_idle_task_is_permanent
  fails on master the moment it is called. embeddedos-org#131's task.c diff re-applied
  on top of embeddedos-org#130's; the result is embeddedos-org#131's file plus embeddedos-org#130's wake_armed hunk
  and nothing else (verified by diff against 3a00bd9).

Verified locally (macOS, clang): Release build clean, 43/43 ctest; the
README default configuration builds; 47/47 pytest.

Not in this PR: the nightly "Upstream drift" job builds eBoot at master and
eBoot master is broken separately (its own repair PR); bump EBOOT_COMMIT in
eos-simulation.yml once that lands.
Kartikey1306 added a commit to Kartikey1306/eos that referenced this pull request Sep 14, 2026
…ARM file

Answers the review on embeddedos-org#126.

Finding 1 (Medium) -- the head carried an undisclosed second change, the
test_crypto_ed25519_loworder registration, which contradicted this PR's own
2026-09-02 comment ("this PR changes one file ... and does not touch tests/").
It was also the fourth open copy of that registration, at a different position
from embeddedos-org#127's. Dropped: the branch is rebased to the toolchain change alone, and
embeddedos-org#127 is the one to take for the registration -- it restores the four lines to
their original position, keeps the crypto suites grouped, and is the position
embeddedos-org#122 is already written against.

Finding 2 (Medium) -- nothing would catch this coming back. Every ARM
toolchain here sets CMAKE_TRY_COMPILE_TARGET_TYPE to STATIC_LIBRARY and no
cross build links an executable, so `Cross-compile ARM Cortex-M4` passed
identically with the duplicate present and absent; it passed on the unfixed
commit. The defect surfaced by accident when embeddedos-org#118 briefly put cmd/eos into the
cross build, and embeddedos-org#118 has since gated that to host builds -- removing the
accident too.

Adds tests/unit/test_toolchain_specs.py, modelled on
test_cmake_test_registration.py: parse every toolchains/*.cmake, extract each
-specs=<file>, and fail if one appears in more than one variable that reaches
a link line (CMake links through the compiler driver, so C/CXX flags do). A
second test asserts specs are declared on the linker flags rather than the
compiler flags, which is the arrangement that turns into a duplicate the
moment someone adds the linker entry that looks missing. No ARM toolchain
needed; runs in the existing pytest job.

Finding 3 (Low) -- arm-none-eabi-r5.cmake put --specs=nosys.specs in
CMAKE_C_FLAGS_INIT with nothing in CMAKE_EXE_LINKER_FLAGS_INIT. Not broken
today, and one added linker -specs= away from the same failure. Moved, so all
three ARM bare-metal toolchains state it the same way and the new guard is
green across all of them.

Verified:
  pytest tests/unit/test_toolchain_specs.py    2 passed
  pytest tests/                                all pass
  discrimination: reintroducing the duplicate in arm-cortex-m4.cmake gives
      arm-cortex-m4.cmake: -specs=nosys.specs is set in CMAKE_C_FLAGS_INIT
      and CMAKE_EXE_LINKER_FLAGS_INIT, so it reaches the link line more than
      once
    on both tests, so the guard catches the original defect rather than
    restating the fix.
  No ARM toolchain on this machine, so the cross link itself is NOT verified
  here; that remains CI's to show.

Refs embeddedos-org#126
Kartikey1306 added a commit to Kartikey1306/eos that referenced this pull request Sep 14, 2026
Residue from 0957ac4, which dropped the borrowed
test_crypto_ed25519_loworder registration and left the blank line that had
separated it from the block below. It is the only remaining change this PR
makes to tests/CMakeLists.txt, it changes nothing, and it sits at the exact
anchor where embeddedos-org#118, embeddedos-org#119 and embeddedos-org#127 each add the real registration block -- so
it would have conflicted with whichever of those lands first, for no reason
at all.

tests/CMakeLists.txt is now untouched by this branch.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants