Skip to content

fix(build): remove duplicate test targets from tests/CMakeLists.txt - #93

Merged
srpatcha merged 2 commits into
embeddedos-org:masterfrom
prakhar7017:fix/cmake-duplicate-test-targets
Sep 1, 2026
Merged

srpatcha merged 2 commits into
embeddedos-org:masterfrom
prakhar7017:fix/cmake-duplicate-test-targets

Conversation

@prakhar7017

Copy link
Copy Markdown
Contributor

Problem

tests/CMakeLists.txt declared test_crypto_aes and test_crypto_sha512 twice
each — once in a group block at lines 59–74, then again individually at lines
76–84. A repeated add_executable target name is a hard CMake error, not a
warning:

CMake Error at tests/CMakeLists.txt:77 (add_executable):
add_executable cannot create target "test_crypto_aes" because another
target with the same name already exists. The existing target is an
executable created in source directory "D:/eos/tests".

CMake Error at tests/CMakeLists.txt:79 (add_test):
add_test given test NAME "test_crypto_aes" which already exists in this
directory.

CMake Error at tests/CMakeLists.txt:82 (add_executable):
add_executable cannot create target "test_crypto_sha512" because another
target with the same name already exists.

-- Configuring incomplete, errors occurred!

Configuration aborted, so the exact command CLAUDE.md and README.md give for
building the suite —

cmake -B build/host -DCMAKE_BUILD_TYPE=Debug -DEOS_BUILD_TESTS=ON

— produced no build system at all. Not one test target could be generated,
built or run. This affects every developer following the documented setup.

Changes

- tests/CMakeLists.txt — removed the duplicate add_executable /
  target_link_libraries / add_test triples for test_crypto_aes and
  test_crypto_sha512. Kept the copy whose comment describes what the suite
  actually covers (AES-128/192/256 ECB + CBC, NIST FIPS-197 / SP 800-38A,
  SHA-512 against NIST vectors), and gave test_crypto_ecc and
  test_crypto_rsa the same one-line description so the block is consistent.
- CHANGELOG.md — entry under [Unreleased] / Fixed.

No target was added or removed, and no source file or link dependency changed.
The 28 registered test targets are exactly the same set as before, each now
declared once.

Testing

No new tests. This is a build-system repair — the evidence is that configuration
now completes.

┌───────────────────────────────────────────────────────────────────────┬──────────────────────────────────────────┐
│                                 Check                                 │                  Result                  │
├───────────────────────────────────────────────────────────────────────┼──────────────────────────────────────────┤
│ cmake -B build/pr1 -G Ninja -DCMAKE_BUILD_TYPE=Debug                  │ PASS — Configuring done / Generating     │
│ -DEOS_BUILD_TESTS=ON on this branch                                   │ done, no errors                          │
├───────────────────────────────────────────────────────────────────────┼──────────────────────────────────────────┤
│ Same command on master                                                │ FAIL — 4 CMake errors, Configuring       │
│                                                                       │ incomplete                               │
└───────────────────────────────────────────────────────────────────────┴──────────────────────────────────────────┘

Verified with CMake 4.4.3, Ninja 1.13.0, GCC 6.3.0 (MinGW) on Windows.

tests/CMakeLists.txt declared test_crypto_aes and test_crypto_sha512
twice each. A repeated add_executable target name is a hard CMake error,
not a warning:

    CMake Error at tests/CMakeLists.txt:77 (add_executable):
      add_executable cannot create target "test_crypto_aes" because
      another target with the same name already exists.
    CMake Error at tests/CMakeLists.txt:79 (add_test):
      add_test given test NAME "test_crypto_aes" which already exists.

Configuration aborted, so the command the README and CLAUDE.md give for
building the test suite produced no build system at all and no test
could be built or run.

Kept the second copy of each pair, whose comments describe what the
suite actually covers, and gave test_crypto_ecc and test_crypto_rsa the
same one-line description for consistency. No target, source file or
link dependency changes.
srpatcha
srpatcha previously approved these changes Aug 30, 2026

@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.

The defect is real and I reproduced it on master:

$ git show origin/master:tests/CMakeLists.txt | grep -oE 'add_executable\([a-z_0-9]+' | sort | uniq -c | awk '$1>1'
      2 test_crypto_aes
      2 test_crypto_sha512

Two declarations each, and a duplicate add_executable name is a hard CMake
error, so -DEOS_BUILD_TESTS=ON cannot generate a single test target. The
CHANGELOG entry describes it accurately.

One thing you should know before this merges: #82 fixes the same two
duplicates
, as part of repairing four breakages that landed together. It takes
tests/CMakeLists.txt from 30 add_executable lines to 28; so does this. The
two will conflict.

That does not make this PR wasted, because you made the better call on which
copy survives. #82 keeps the grouped comment:

# --- test_crypto_aes / _ecc / _rsa / _sha512: were sitting in tests/ unused ---

which describes how the tests got there rather than what they cover. This keeps
the per-test descriptions:

# --- test_crypto_aes: AES-128/192/256 ECB + CBC, NIST FIPS-197 / SP 800-38A ---
# --- test_crypto_ecc: ECC point arithmetic and ECDSA ---
# --- test_crypto_rsa: RSA modular exponentiation and verify ---
# --- test_crypto_sha512: SHA-512 against NIST vectors ---

Naming the standard each vector set comes from is worth more to the next reader
than a note about the file's history. I am folding your version into #82 with
attribution, so the outcome is yours even though the conflict resolves in that
PR's favour.

Approving on the merits. If #82 lands first this becomes empty and can close; if
this lands first, #82 rebases cleanly around it.

Worth flagging that this is not the only duplicate-target problem in the
platform. eos and eBoot both define test_crypto and test_multicore, and
since ebuild composes them with add_subdirectory, the integration build could
not configure either — same class of error, one repository apart. That one is
embeddedos-org/ebuild#85, fixed in embeddedos-org/eBoot#71.

srpatcha added a commit that referenced this pull request Aug 30, 2026
Deduplicating test_crypto_aes and test_crypto_sha512 left the grouped
comment that had been sitting above the block:

    # --- test_crypto_aes / _ecc / _rsa / _sha512: were sitting in tests/ unused ---

That records how the files got here, which stops being interesting the
moment they are wired up. #93 fixes the same two duplicates and keeps the
per-test descriptions instead:

    # --- test_crypto_aes: AES-128/192/256 ECB + CBC, NIST FIPS-197 / SP 800-38A ---
    # --- test_crypto_ecc: ECC point arithmetic and ECDSA ---
    # --- test_crypto_rsa: RSA modular exponentiation and verify ---
    # --- test_crypto_sha512: SHA-512 against NIST vectors ---

Naming the standard each vector set comes from is worth more to the next
reader than a note about the file's history. Adopting that here so the
better wording survives the conflict between the two PRs.

Comment-only; no target, source or link line changes.

    28/28 tests passed, 0 build errors

Co-authored-by: prakhar7017 <prakhar7017@users.noreply.github.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
srpatcha added a commit that referenced this pull request Sep 1, 2026
* fix: repair master — four merges landed code that does not compile

`master` does not configure, and once it does it does not build, and once it
builds one suite fails. Each break is the same failure: a PR verified green on
its own branch, squash-merged onto a base that had moved, and nothing
re-verified the result.

1. tests/CMakeLists.txt registered test_crypto_aes and test_crypto_sha512
   twice (#59 landing beside the block that already had them), so CMake
   refused to configure at all:

       add_test given test NAME "test_crypto_aes" which already exists

2. eos_task_set_current_internal was defined twice in task.c and declared
   twice in kernel_internal.h, with two different behaviours — one clears
   g_current and marks the task RUNNING, the other silently returns. Kept the
   first, which is what the header documents ("Handles >= EOS_MAX_TASKS clear
   it") and what the mutex tests rely on; folded the second declaration's
   comment into the surviving one, since it records why the function exists.

3. sync.c would not compile: g_blocked_on, pi_propagate and task_valid were
   used and defined nowhere, alongside a reference to a struct member
   original_prio that mtx_t does not have.

   #67 and #55 implement incompatible designs for the same feature. #67
   recomputes effective priority from

       effective = min(base_priority, priority of every waiter on every
                       mutex the task holds)

   on every change; #55 saves and restores a value around each lock. Merging
   #55 after #67 deleted #67's implementation while leaving its call sites,
   so neither design was left intact.

   Restored #67's. It is the stronger of the two — save/restore is wrong when
   a task holds two mutexes and releases one, and wrong again when a task is
   boosted while already holding a mutex — and it subsumes what #55 fixes:
   recomputing on timeout is one of the cases it recomputes on, and it
   already rejects a full waiter table rather than blocking a task that was
   never enqueued.

4. #74's tests were merged without its parser change. Six assertions in
   test_config failed because a "- name:" item arriving while section was
   SEC_PKG_DEPS fell into the dependency branch, so the package after a deps
   list was swallowed as a dependency of the previous one. Restored the
   SEC_PKG_DEPS arm of the outer condition, nested the dependency handling as
   the else of the name check, and added the deps transition under
   SEC_PKG_BUILD.

    before   cmake: configure fails
    after    build clean, ctest 28/28

Also re-checked the package-overflow guard that arrived via #61/#65, since it
landed in a different shape than #78 proposed: AddressSanitizer is clean on a
129-package config that previously overflowed, and the 129th entry is refused
with a message rather than silently dropped.

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

* fix(core): make eos cross-compile for Cortex-M4 again

eos does not build for the target it exists to build for. From a clean
checkout of master, with the toolchain the CI job installs:

    $ cmake -B barm -DCMAKE_TOOLCHAIN_FILE=toolchains/arm-cortex-m4.cmake
    $ cmake --build barm
    core/src/log.c:113:9: error: implicit declaration of function 'clock_gettime'
    core/src/log.c:113:23: error: 'CLOCK_MONOTONIC' undeclared

get_timestamp_ms() had two branches, _WIN32 and "else, assume POSIX". Bare-
metal newlib is neither: arm-none-eabi declares neither clock_gettime nor
CLOCK_MONOTONIC, so the POSIX branch cannot compile for any Cortex-M target.
The macro is the precise test — where it is absent, so is the function.

There is a timebase on a target, eos_get_tick_ms() in the HAL, but core/ sits
below hal/ and must not depend upward, so log entries carry a zero timestamp
on a freestanding build until a port supplies one. Ordering within the ring
buffer is unaffected; only absolute time is missing.

Also fixed the format specifier one line down. uint32_t is `unsigned int` on
x86-64 and `unsigned long` on 32-bit ARM, so the literal %u was wrong on
exactly the target this build is for. It is PRIu32 now, which is right on
both.

Why CI did not catch it. The ARM job points CMAKE_TOOLCHAIN_FILE at
cmake/arm-cortex-m4.cmake; the file is at toolchains/. That fails loudly --
"Could not find toolchain file" -- so the job has been red rather than
silently passing, but it dies at configure and never reaches the compile step
where this bug lives.

The path is not new breakage: #75 fixed it, #79 and #51 carried the fix, and
#59 reverted it. That is the same stale-base merge pattern as the other three
repairs on this branch.

    before   ARM: configure fails on a missing toolchain file; with the real
             path, 2 compile errors in core/src/log.c
    after    ARM: 70/70 targets built, artifacts report `architecture:
             armv7e-m`; host: 28/28 ctest, unchanged

Kernel and services together measure 15.8 KB flash and 26 KB RAM on
Cortex-M4, which is the first real footprint number this repo has produced.

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

* docs(tests): describe what each crypto test covers

Deduplicating test_crypto_aes and test_crypto_sha512 left the grouped
comment that had been sitting above the block:

    # --- test_crypto_aes / _ecc / _rsa / _sha512: were sitting in tests/ unused ---

That records how the files got here, which stops being interesting the
moment they are wired up. #93 fixes the same two duplicates and keeps the
per-test descriptions instead:

    # --- test_crypto_aes: AES-128/192/256 ECB + CBC, NIST FIPS-197 / SP 800-38A ---
    # --- test_crypto_ecc: ECC point arithmetic and ECDSA ---
    # --- test_crypto_rsa: RSA modular exponentiation and verify ---
    # --- test_crypto_sha512: SHA-512 against NIST vectors ---

Naming the standard each vector set comes from is worth more to the next
reader than a note about the file's history. Adopting that here so the
better wording survives the conflict between the two PRs.

Comment-only; no target, source or link line changes.

    28/28 tests passed, 0 build errors

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

* docs(sync): say why each priority-inheritance branch acts as it does

#94 repairs the same spliced priority-inheritance code as this PR, and
converged on the same design independently — g_blocked_on, task_valid,
pi_recompute, pi_propagate, recomputing from the invariant rather than
saving and restoring a value around each lock. The two implementations of
sync.c differ by nothing except these two comments, which explain the
consequence rather than only the action:

    "...it would sleep forever."
    -> "...it would sleep forever. Refusing also avoids boosting the owner
        on behalf of a caller that never waits."

    "Timeout — leave the queue and drop the boost we were causing."
    -> "...so a waiter that gave up no longer keeps the owner running
        elevated."

Both name the priority-inheritance effect of the branch, which is the part
a reader is likely to get wrong when editing it later.

Comment-only; sync.c is otherwise byte-identical to #94's.

    28/28 tests passed, 0 build errors

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: prakhar7017 <prakhar7017@users.noreply.github.com>
@srpatcha
srpatcha merged commit 3aa8644 into embeddedos-org:master Sep 1, 2026
18 of 21 checks passed
Kartikey1306 added a commit to Kartikey1306/eos that referenced this pull request Sep 3, 2026
…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)
Kartikey1306 added a commit to Kartikey1306/eos that referenced this pull request Sep 3, 2026
…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)
srpatcha pushed a commit that referenced this pull request Sep 8, 2026
* 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)

* chore(tests): drop the unused assert.h include from test_net.c

Retitles and reduces this PR to what it still does, per the review.

Both repairs it was opened for have landed on master by other routes:
net/src/net_posix.c is byte-identical to origin/master, and master's default
configuration builds clean. Against current master the branch's only real
content was this one line -- and merging it as it stood would have *reverted*
646 lines that landed since, including #120's pkg trust anchor,
tests/test_pkg_trust_anchor.c and tests/unit/test_cmake_test_registration.py.
Reset to master and re-applied the one surviving change rather than leave a
stale branch that still looks mergeable.

The include is genuinely unused: tests/test_net.c has zero assert() calls and
uses its own CHECK() macro at 22 call sites. Worth correcting the record, per
finding 2: #124 added it one commit ago as "the missing assert.h include that
broke master", and the file had no assert() at #124's parent either. What
fixed that link error was the assert() -> CHECK() conversion already on
master; the include was never load-bearing, so removing it changes nothing
except the next reader's understanding.

Stacked on #127, which restores the test_crypto_ed25519_loworder registration
master is currently red on.

Verified:
  pytest tests/    15 passed
  ctest            39/39 PASS

Refs #116, #124

* ci: pin the eBoot and ebuild checkouts instead of floating on master

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 #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 #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 #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 #135's commit -- git drops it as already-upstream the
moment #135 merges. The pin itself is #135's to review.
srpatcha added a commit that referenced this pull request Sep 8, 2026
… it (#119)

* 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(linux_security): close the gaps in the shell-path guard, and test it

linux_security.c builds shell commands with snprintf and hands them to
system() or popen() — nine call sites. is_path_safe() is the only thing
between a caller's path and the shell. It had three holes.

1. The denylist was incomplete. It rejected ;|&><$()"' but not the
   backtick, which is command substitution in every POSIX shell, and not
   a newline, which ends one command and begins the next. Both were
   reported safe:

       is_path_safe("/tmp/`touch /tmp/pwned`")  ->  1
       is_path_safe("/tmp/x\ntouch /tmp/pwned") ->  1

2. It was applied at two of the nine call sites. eos_dmverity_create(),
   eos_dmverity_verify(), eos_ima_install_to_rootfs(),
   eos_ima_sign_file(), and the three busybox entry points interpolated
   their arguments with no validation at all. eos_selinux_install_to_
   rootfs() checked policy_dir and policy_name but not rootfs_dir, which
   reaches the same command through `path`.

3. is_path_safe(NULL) returned 1 — "safe" is the wrong default for a
   predicate guarding command construction.

Fixed the validator, added backslash and control characters while there,
and applied it at every site. Two busybox commands interpolate WITHOUT
surrounding quotes (`make -C "%s" %s` and ` CROSS_COMPILE=%s`), where a
single space already injects an extra make argument; those use a
stricter is_word_safe().

Adds tests/test_linux_security_paths.c — eleven hostile payloads against
the public API, plus a counter-check that a well-formed path still
reaches the shell, proved by a generated Makefile whose target creates a
sentinel file. A guard that refused everything would pass the first half
alone.

Verified. Against the unfixed file the new test fails and `id` runs:

    uid=501(kartikey) gid=20(staff) groups=...

Note the two dmverity assertions do not discriminate on a host without
veritysetup — system() returns non-zero either way — so on this machine
the failures came from the busybox and IMA paths plus that observable
execution. On a host with veritysetup they discriminate too.

  ctest -> 35/35 passed (34 before, +test_linux_security_paths)

Not addressed here, and worth its own change: these commands would be
better run through fork/execv with an argv array than validated as
strings. Two of them need a shell for pipelines, so that is a larger
restructure than this fix.

* test: create the fixture Makefile 0600, not 0666

CodeQL, high severity, on this PR's own new test: fopen(mk, "w")
creates with mode 0666 masked by umask, so on a permissive umask the
fixture Makefile would be world-writable -- and its contents are handed
to make and executed.

mkdtemp() already gives the containing directory 0700, so this was not
reachable in practice, but a test that demonstrates command-injection
defences should not itself create an executable file with default
permissions. open(O_WRONLY|O_CREAT|O_EXCL, S_IRUSR|S_IWUSR) + fdopen(),
with O_EXCL so the fixture cannot be pre-created by anyone else.

  test_linux_security_paths -> 6/6 passed
  ctest                     -> 35/35 passed

* test(linux_security): prove the injection does not run, not just that it returns non-zero

The two dm-verity refusal assertions in this file check for a non-zero
return. That does not discriminate on a host without veritysetup:
system() returns non-zero whether the command was refused or merely
failed to find the binary. CI installs no veritysetup, so those two
assertions pass against the unfixed file on every CI run. I flagged this
in the PR; better to fix it than to document it.

test_injection_does_not_execute() watches for the side effect instead.
The hostile path carries a command substitution that would create a
sentinel file. If the guard refuses the path, the command is never built
and the sentinel cannot appear — regardless of what is installed. Run
against the three entry points that take a caller-supplied path into a
shell string: eos_dmverity_verify(), eos_dmverity_create() and
eos_ima_sign_file().

Against the unfixed file it fails on all three, and says why:

    [FAIL] the injected command executed: /tmp/eos_lsp_inj_QIrR09/pwned exists

which is the defect stated as an observation rather than an argument.

  ctest -> 35/35 passed

* fix(linux_security): validate the value the guard protects, and stop reporting unrun steps as done

Answers both High findings from the review on #119.

Finding 1 -- the new guard was bypassed on the default path.
eos_busybox_configure() validated bb->source_dir, then filled it from
bb->version when empty. Empty is what eos_busybox_init() leaves, so on the
path a caller gets by default is_path_safe("") returned 1 (its loop body
never runs), the guard passed, and ".eos/build/src/busybox-<version>" went
into `make -C "%s"`. A backtick in version is command substitution inside
those double quotes, and eos_busybox_set_version() was a bare strncpy with no
check at all.

Fixed at both ends, as the finding asks: the setter refuses a version that is
not word-safe, and the guard moved below the default-fill so it validates the
value actually used rather than the empty string.

  This is demonstrated, not argued. Running the new test against the
  unmodified source:
      [FAIL] the default path executed the injection:
             /tmp/eos_lsp_ver_uNG5ro/pwned
  The sentinel file exists, so the injected command ran.

Finding 2 -- security steps reported success without having run.
eos_ima_sign_file() discarded system()'s return and its command ended
`|| echo 'evmctl not available'`, which masked the shell's status too, so on
any host without evmctl it signed nothing and returned 0. .ai/security.md:
a verification step that cannot run must fail, not pass. Now returns -1 and
logs through EOS_ERROR, following eos_dmverity_verify() which already gets
this right. The Windows branch returns -1 rather than 0 for the same reason.

  eos_linux_security gains a link to eos_core for EOS_ERROR -- the same
  resolution #108 took for eos_ota, and no cycle: eos_core does not depend on
  eos_linux_security.

  The four remaining `|| true` / `|| echo` discards named in the finding
  (eos_selinux_install_to_rootfs, eos_selinux_label_rootfs,
  eos_ima_install_to_rootfs, eos_busybox_install_to_rootfs) are NOT fixed
  here. They are install/copy steps rather than verification steps, and
  distinguishing "the tool is absent" from "the tool ran and failed" for each
  needs a caller-visible state that does not exist yet. Left deliberately,
  said here rather than implied by silence.

Also: the [PASS] lines printed unconditionally, so a function could report
success after its own CHECKs had failed -- the same shape as the defects
under test. Now guarded on the failure count not having moved.

Verified:
  ctest                             39/39 PASS
  test_linux_security_paths         9/9 PASS
  discrimination, against the source with only these two fixes reverted:
      set_version accepts the hostile version          FAIL
      the sentinel file is created                     FAIL  <- injection ran
      ima_sign_file returns 0 with no evmctl           FAIL
  rebased onto master; the tests/CMakeLists.txt conflict with the four
  suites now on master is resolved additively.

Refs #119

* test: create the ima fixture 0600, not 0666

CodeQL alert 404 on this branch's head, at
tests/test_linux_security_paths.c:266 -- "A file may be created here with mode
0666, which would make it world-writable."

Third instance of this in the same file. Commit bba834a fixed the Makefile
fixture for exactly this reason and the comment there explains it, and I
introduced a new `fopen(target, "w")` in the evmctl test two commits later
without applying the same rule. fopen creates 0666 masked by umask, so under a
permissive umask the file a signing tool is pointed at is world-writable.

Now open(O_WRONLY | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR), matching the fixture
above it. O_EXCL as well, since the path is inside a fresh mkdtemp directory
and an existing file there would mean something is wrong.

No `fopen(..., "w")` remains in the file.

Verified:
  ctest                             40/40 PASS
  test_linux_security_paths         9/9 PASS

Refs #119

* fix(linux_security): one shell word is an allowlist, not a third denylist

Review findings 2, 3 and 5 on #119, none of which changes what any caller
sees.

is_word_safe() answered an incomplete metacharacter denylist with a second
denylist -- space, tab, *, ?, ~ -- and it had the same shape of hole: no [
or ] (a glob character class) and no { or } (brace expansion, which /bin/sh
performs when it is bash). Both reach make unquoted through defconfig and
cross_compile, and a leading '-' turns the defconfig target into an option.
None of that is command execution, but a denylist that lists three of five
glob characters is the failure mode this PR exists to fix, so the predicate
is now a positive definition: isalnum() plus ._/+=:- and nothing else. It
cannot be incomplete, and the new test asserts both halves -- twelve strings
that are not one word are refused, and sixteen real defconfig targets,
cross-compile prefixes and hash algorithm names are still accepted. The
counter-check in test_ordinary_paths_still_reach_the_shell() now passes
CROSS_COMPILE=arm-linux-gnueabihf- through to a real make invocation.

offset += snprintf(...) accumulated the would-be length rather than what was
written, so a truncating segment would put cmd + offset out of bounds and
underflow sizeof(cmd) - offset to a huge size_t. Unreachable at the current
field widths (512 + 128 + 128 against a 2048 buffer) and nothing ties the two
together, so both busybox functions check instead of relying on the arithmetic
staying true.

eos_core becomes a PRIVATE dependency: eos/log.h is used by linux_security.c
and by nothing under include/, so there was no reason to push eos_core's
include directories onto every consumer. CMake still records a static
library's PRIVATE dependency as a $<LINK_ONLY:> interface requirement, so
test_linux_security_paths -- which names only eos_linux_security -- still
links; verified by building and running it.

Also from the review: the injection payload in the default-source_dir test was
formatted into a 192-byte buffer from a 320-byte sentinel path. No truncation
occurred in practice, but a truncated payload loses its closing backtick and
stops being an injection, and the test would then pass while testing nothing.
The buffers are sized from each other now and snprintf's return is asserted,
as is the payload's length against bb.version[64], which it also has to
survive intact. make is probed with command -v before the test that needs it,
the way evmctl already was.

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

* fix(linux_security): stop reporting security steps that did not run

Review finding 1 on #119, and a contract change: five functions that always
returned 0 can now return -1.

This PR already removed one `|| echo` + discarded system() pair from
eos_ima_sign_file() and left four more instances of the same defect in the
same file. Each one reported a security step as done without having run it:

  eos_selinux_install_to_rootfs()  cp -r ... || true, result discarded --
      a failed policy copy left /etc/selinux/config naming a policy the
      rootfs does not contain.
  eos_selinux_label_rootfs()       setfiles ... || echo, result discarded --
      on any host without setfiles this labeled nothing. With no
      file_contexts set it echoed a "skipping" line and also returned 0.
  eos_ima_install_to_rootfs()      cp key ... || true, result discarded --
      an appraise policy installed with no key to appraise against. The
      non-shell half had the same shape: a policy_file that could not be
      opened left /etc/ima/policy empty and returned 0.
  eos_busybox_install_to_rootfs()  make install, result discarded -- a rootfs
      with no busybox in it, and then an /init written with fopen()'s result
      ignored, so an unwritable rootfs produced an initramfs with no /init.

All of them now separate "the tool is absent or failed" from "the step
completed", log through EOS_ERROR and return -1, following
eos_dmverity_verify() which already did this. The Windows arms return -1 for
the same reason rather than 0 for a step that cannot run there.

.ai/security.md, Never: "Report a check as passing without running it."

Caller survey, since this changes return values: outside its own header and
this test file, nothing in the tree calls any of these five functions --
nothing in systems/, cmd/ or examples/. No caller can break on the new -1.
That survey is what the review asked for before this landed.

Every fix is pinned by a test that fails without it. Verified by mutation --
eleven reversions, each caught:

  || true restored on the SELinux copy          1 check failed
  || echo restored on setfiles                  1
  the file_contexts branch back to 0            1
  || true restored on the IMA key copy          1
  make install's result discarded again         2
  an unreadable policy_file swallowed again     1
  is_word_safe back to the old denylist         1
  rootfs_dir dropped from the SELinux guard     1  + sentinel created
  rootfs_dir dropped from the busybox guard     1  + sentinel created
  rootfs_dir dropped from the IMA guard         1
  /init's fopen() silent again                  1

The two sentinel lines are the ones worth reading: with the rootfs_dir term
deleted from those guards the injected `touch` genuinely ran. The first draft
of the SELinux case did not discriminate -- a hostile rootfs_dir fails at the
fopen() long before any command is built, so it passed on an unguarded build
too. It now uses a real directory whose *name* is the injection, with the
/etc the function needs and a loaded policy, so the cp is actually reached.

The counter-checks matter as much: eos_ima_install_to_rootfs() with no key and
no policy file must still return 0, eos_selinux_install_to_rootfs() with the
policy present must still return 0, eos_selinux_label_rootfs() must still
return 0 when SELinux is disabled, and a busybox install whose make target
succeeds must still write its /init. The SELinux one caught a fixture bug that
had made two of these tests pass for the wrong reason -- these functions
MKDIR("<rootfs>/etc/<x>") without creating <rootfs>/etc first, so a bare
mkdtemp() rootfs fails at the fopen(), not at the step under test.

Also covered here, from the first review: one hostile-rootfs_dir case per
*_install_to_rootfs entry point -- the guard the PR body named as its second
finding shipped with no test -- and the EOS_IMA_OFF ordering, where a
malformed rootfs_dir is now refused even with IMA off.

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

* docs(changelog): record the linux_security fixes, including the contract change

Every merged fix in this repo carries an [Unreleased] entry and this one did
not. The third bullet is the one that matters to a reader: five functions that
always returned 0 can now return -1.

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Srikanth Patchava <srikanth.patchava@outlook.com>
srpatcha added a commit that referenced this pull request Sep 8, 2026
* 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)

* test: catch tests that are compiled but never called

Closes the other half of the gap @srpatcha identified reviewing #127.

test_cmake_test_registration.py answers "is this file compiled?". It
cannot answer "is this test called?", because a test function nothing
invokes is valid C: it compiles, it links, ctest runs its suite, and the
suite passes having skipped it. No build error, no ctest failure, and
no registration check can see it.

Added test_every_test_function_is_called(). Run against master it finds
exactly one instance across every registered suite, with no false
positives:

    test_kernel.c::test_queue_send_waiter_overflow

That is the suite guarding the eos_queue_send / eos_queue_receive waiter
table -- a full table now returns EOS_KERN_NO_MEMORY instead of blocking
a task that can never be woken. It has been defined and uncalled since
it landed, so that fix has had no executing test. Calling it, the test
passes: the fix was right, nothing was checking it.

The same class had already bitten eBoot, where a merge kept a test
function and dropped its run_ call (eBoot#94).

Two declaration styles are handled -- `static void test_x(void)` called
as test_x(), and `TEST(x)` called as run_x(). Occurrences are counted by
bare identifier rather than by `name(`, so a function passed by address
(a task entry point, a callback) is not reported as dead; test_entry in
this same file is exactly that case and is correctly ignored.

test_kernel.c's summary printed a hardcoded "(12/12)" while thirteen
tests sat in the file, so the literal agreed with the omission instead
of exposing it. It is counted now, by a RUN() macro that increments as
it calls, which is the same shape as the fix eBoot#94 applied to its
tests_run.

Verified:
  guard against master's tree  -> 1 failure, names the right function
  guard after the fix          -> 7 passed
  test_kernel                  -> 13/13 (was 12 called, 13 defined)
  pytest tests/                -> 17 passed
  ctest                        -> 39/39 passed

Stacked on #127, which master needs before any of this runs.

---------

Co-authored-by: Srikanth Patchava <srikanth.patchava@outlook.com>
srpatcha pushed a commit that referenced this pull request Sep 8, 2026
…135)

* 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)

* ci: pin the eBoot and ebuild checkouts instead of floating on master

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 #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 #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.

* ci: make the pin visible when it goes stale, not silently permanent

The pin in the previous commit stops an eBoot or ebuild master that does not
compile from turning every eos pull request red. It also stops that break
being visible from here at all, which on its own is the worse half of the
trade: the next upstream breakage would be silent instead of loud, and the
pin would sit at a172a6d indefinitely because nothing would ever ask -- while
quietly dropping the six eBoot commits that follow it.

`.github/workflows/upstream-drift.yml` is the other half. It builds eBoot at
master and compares both pins against their master, so an upstream break
still surfaces, on a run that blocks no pull request and names the repository
responsible.

Design choices, each one avoiding a defect this repo has been filing against:

  Its own file, schedule + workflow_dispatch only. The first draft was a job
  inside eos-simulation.yml guarded by `if: github.event_name == 'schedule'`.
  That works, but the run showed it on the PR as a SKIPPED check -- and a
  required-check gate that treats any non-success as a failure, which is the
  design in #121, would trip on exactly that. Moving it out removes the check
  from PR runs entirely rather than relying on every future gate to exempt it.

  Not `continue-on-error`. A job reporting success over a failure is the
  fail-open shape we keep finding in aggregating gates. This one may fail
  honestly precisely because it gates nothing.

  Failing is reserved for "upstream does not build". A pin merely behind a
  healthy master is a warning: actionable, not broken.

  The pins are read out of eos-simulation.yml, not copied. A second copy of a
  SHA is a second thing to forget. The read demands a 40-character hex value
  and fails closed if it cannot find one, so a renamed or reformatted key
  stops the job instead of silently letting it check a pin of "".

  The failure message is scoped to the build step's own outcome, so a
  checkout, apt or pin-read failure cannot report itself as evidence about
  eBoot's master. The summary says eBoot is built and ebuild is only
  compared, rather than implying both were checked.

Verified by execution, not by reading:

  eBoot @ a172a6d (the pin)   host build rc=0, 0 errors
  eBoot @ 22d8f8b (master)    host build rc=2, "no member named 'reserved'
                              in 'eos_image_header_t'" at eos_image.h:135
                              and :142 -- the same defect CI reports, so the
                              pin is demonstrably the difference

  pin extraction, run against the real file and two mutations of it:
    real workflow                -> both SHAs read correctly, and the eBoot
                                    one resolves to #86's merge
    key renamed to EBOOT_SHA     -> fails closed, rc=1
    value unquoted and shortened -> fails closed, rc=1

  drift logic, run against the real repositories:
    pin 6 behind healthy master  -> "behind by 6" warning, no failure
    pin absent from master       -> "not an ancestor" warning, exit 0 under
                                    set -e (the first draft printed "pinned
                                    pin not found on master commit(s)")
    pin equal to master          -> no warning

  eos on this branch: cmake build rc=0, ctest 39/39, pytest 15/15 with the
  new workflow file present, test_crypto_ed25519_loworder registered and
  passing.

When eBoot #94 lands, bump EBOOT_COMMIT to that merge; this job is what will
say whether it holds.

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
srpatcha pushed a commit that referenced this pull request Sep 8, 2026
* 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)

* chore: untrack the sanitizer build trees committed in #110

bsan/ and bsan2/ are CMake build directories. They reached master in
8a2d835 (#110) — 1441 files, 12.2 MB, four compiled object files, and
two CMakeCache.txt holding absolute paths from the machine that ran the
sanitizer build. Nothing in the repository refers to either directory.

They are mine, and they are the same mistake as the .coverage database
that #75 carried to master and #79 had to remove: `git add -A` in a tree
with a build directory whose name no ignore pattern matched. .gitignore
already lists build/, _build/, build-*/, build_coverage/,
build_qemu_arm64/ and build_sim/ — every one of them added after being
committed once. Widened to cover bsan*/ so this name cannot come back.

Removed with `git rm -r --cached`, so anyone with a local sanitizer
build keeps it; it is simply no longer tracked.

Contents only — no source, build or CI file is touched, and the tree
builds and tests exactly as before:
  -DEOS_BUILD_TESTS=ON -DEOS_PRODUCT=vbox_test + ctest -> 34/34 passed

* chore: collapse the redundant bsan ignore patterns, and say what untracking does not do

Answers the review on #117.

Finding 1 (Low) -- bsan*/ already matches bsan/ and bsan2/, so those two lines
were dead the moment the third was written. This file's stated problem is that
it accumulates one redundant entry per incident; adding two more while fixing
that is the wrong direction. Verified the single pattern still covers all
three shapes: bsan/, bsan2/ and bsan99/ are each ignored with only bsan*/
present.

Finding 2 (Low) -- the body read as though the trees were gone. They are only
untracked going forward: the 12.2 MB and the absolute paths stay in history at
8a2d835 permanently, and every clone still downloads them. Said so in the PR
body rather than leaving the next reader to work it out. No history rewrite is
proposed -- the reviewer scanned all 1441 blobs for credential-shaped content
and found none, the only disclosure being a public GitHub handle, so a rewrite
would cost every consumer a re-clone for no security benefit.

Verified:
  pytest tests/                                 15 passed
  bsan/, bsan2/, bsan99/ with only bsan*/       all three ignored
  git status --short                            clean

Refs #117

* ci: pin the eBoot and ebuild checkouts instead of floating on master

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 #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 #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 #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 #135's commit -- git drops it as already-upstream the
moment #135 merges. The pin itself is #135's to review.
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 pushed a commit that referenced this pull request Sep 8, 2026
* 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)

* ci: pin the eBoot and ebuild checkouts instead of floating on master

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 #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 #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 #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 #135's commit -- git drops it as already-upstream the
moment #135 merges. The pin itself is #135's to review.
srpatcha pushed a commit that referenced this pull request Sep 8, 2026
…rd it (#114)

* 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)

* test: stop building test_net_mock, which covers nothing, and say what the guard checks

Reduces this PR to what it still contributes, and answers the review.

The four registrations it opened with have landed on master by other routes --
test_mpu_validate, test_e2e, test_hal_stm32f4 and the registration guard are
all there. Merging the branch as it stood would have reverted 505 lines that
landed since, including #120's pkg trust anchor. Reset onto #127 and kept only
what is still true.

Finding 1 (Medium) -- test_net_mock was registered with no
target_link_libraries and includes no EmbeddedOS header. Its mock_net_*
functions are called directly rather than standing in for eos_net_*, so no
change to net/ can make it fail: 614 lines asserting a mock against itself.
Registering it added a green ctest entry covering nothing, which is precisely
the failure mode the guard in this same commit exists to surface -- and the
guard's own NOT_BUILT policy is where it belongs. Listed there with the reason,
including what would make it worth building: driving the real eos_net_* API
against the mock transport, which is what its docblock already claims it does.

Finding 2 (Low) -- documented the guard's limit where the policy is defined.
It checks that every suite is built and registered with ctest. It does not
check that a suite reaches the code it is named after, and a file that links
nothing satisfies it completely. Worth stating next to NOT_BUILT, because that
is the list someone reaches for when the guard fires.

Findings 3 and 4 are moot at this head: the test_mpu_validate comment and
test_net_mock's vestigial assert.h include both belonged to hunks that are
either upstream or removed here.

Verified:
  pytest tests/     15 passed
  ctest             38/38 PASS
    (test_net_mock is no longer among them; the count is unchanged from
    master because #127, which this is stacked on, adds
    test_crypto_ed25519_loworder -- a suite that does exercise real code.)

Refs #114

* ci: pin the eBoot and ebuild checkouts instead of floating on master

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 #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 #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 #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 #135's commit -- git drops it as already-upstream the
moment #135 merges. The pin itself is #135's to review.
srpatcha added a commit that referenced this pull request Sep 8, 2026
* 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(pkg): refuse a source download that cannot be verified

eos_fetch_source() compared the archive against its expected digest only
when one was supplied:

    if (expected_hash && expected_hash[0]) { ...compare... }

so calling it without a digest downloaded the archive and extracted it
having compared it against nothing -- and returned EOS_OK, which is what a
verified fetch also returns. The caller cannot tell the two apart, and the
argument is an ordinary optional parameter of a public function declared
in pkg/include/eos/package.h.

Refuse instead, before the network is touched. Git URLs stay exempt: a
clone carries its own object hashes.

ebuild made the same change to PackageFetcher.fetch() for the same reason
(embeddedos-org/ebuild#67) -- omitting one field bought an unverified
download there too.

tests/test_pkg_fetch.c is new; the function had no tests. Every case is
offline by construction, because returning before the network is touched
is the property under test: each archive form this fetcher extracts is
refused without a digest, an empty digest is refused rather than treated
as absent, an unsafe URL still fails as unsafe rather than as a checksum
error, and an empty URL is still the no-op it was.

39/39. Against the unfixed fetcher the suite fails on its first assertion.

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

* fix(pkg): honour the digest on the git path too, and anchor is_git_url

Answers the review on #115. The tarball half of this PR was correct; the
identical defect was still live one branch over.

Finding 1 (High) -- the guard read `if (is_tarball(url) && ...)`, so a git URL
passed it with a digest and without one, and fetch_git() then never looked at
expected_hash. A caller that pinned a revision got EOS_OK and the content with
nothing compared -- verbatim the property this PR's own body names as the
defect: "the caller cannot tell the two apart, because both return EOS_OK."

fetch_git() now takes the pin and compares it against the commit the clone
actually landed on (`git -C <dest> rev-parse HEAD`), failing EOS_ERR_CHECKSUM
on a mismatch or if HEAD cannot be read.

Finding 3 (Medium) -- the exemption's stated reason was "a clone carries its
own object hashes". That is true and does not support the exemption: object
hashes prove the transfer was not corrupted, not that the content is the
content that was reviewed. `git clone --depth 1` takes whatever the default
branch points at when it runs, so an unpinned git source is exactly the moving
input this function exists to refuse. The pre-network guard now covers git as
well, naming "commit" rather than "SHA-256" in the message.

  Behaviour change worth stating plainly: a git source with no pinned commit
  is now refused where it used to be cloned. No recipe in this repository uses
  a git source, so nothing in-tree changes, but an out-of-tree recipe relying
  on an unpinned clone will need a commit added.

Finding 2 (Medium) -- is_git_url() was `strstr(url, ".git") != NULL`, an
unanchored substring test. It matched any host under *.github.io, including
this organisation's own pages, and any archive whose path contained ".git" --
https://example.com/.github/release.tar.gz took the git branch and skipped the
tarball digest check entirely. Now anchored: ".git" at end of string or before
/, # or ?, plus the git:// and git@ schemes.

Finding 4 (Low) -- "every archive form this fetcher extracts is covered" named
three of four; .zip was missing. Added, and the claim narrowed to what it
asserts.

Verified:
  ctest                                     39/39 PASS
  test_pkg_fetch                            7/7 PASS
  the new tests against the pre-fix fetcher: the git cases FAIL, so they
    discriminate rather than restate the fix
  no .git recipe in-tree (grep over *.yaml/*.yml) so the stricter git rule
    breaks nothing here

Refs #115

* ci: pin the eBoot and ebuild checkouts instead of floating on master

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 #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 #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 #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 #135's commit -- git drops it as already-upstream the
moment #135 merges. The pin itself is #135's to review.

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Srikanth Patchava <srikanth.patchava@outlook.com>
srpatcha added a commit that referenced this pull request Sep 8, 2026
* 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)

* ci: add one job branch protection can require

#92 stays open on this point: `required_status_checks` is null on master, so
nothing builds the merge result before it becomes master. Four individually
green PRs combined into code that had never been compiled in the form it
landed in, and the gap that allowed it is still open.

Marking a check required is a settings change, but it needs a name worth
pointing at. `release` is skipped on every pull request, and a required check
that is skipped never reports, so the pull request waits for a status that
never arrives. Requiring the four real jobs individually works only until the
next one is added and silently sits outside the requirement.

`ci-gate` is one job, one stable name, that succeeds only if every other job
that runs on a pull request succeeded. `if: always()` so a real failure shows
as a red X rather than a pull request that hangs. Any non-success result fails
it, `skipped` included -- a job that did not run did not verify anything, and
treating that as a pass is the fail-open shape #75, #82 and #84 removed
elsewhere.

tests/unit/test_ci_gate.py fails if a job is added without being wired into
the gate. Verified by mutation: dropping a job from `needs`, removing
`if: always()`, renaming the gate, and adding an unwired job are each caught.

pyyaml added to both pytest installers, since the test parses the workflow and
neither installer had it.

Verified: 16 Python tests pass, ctest 34/34.

* ci: gate every workflow that reports on a pull request, not just ci.yml

Addresses the review on #121. Finding 1 was right and it was the important
one: ci.yml is one of nine workflows that trigger on pull_request, so
requiring `CI Gate` and stopping there left CodeQL, the QEMU boot test, the
ARM64 kernel cross-build, the Python suites and the SBOM validation outside
the requirement -- while the test written to stop that rot only ever read
ci.yml.

The scan now covers .github/workflows/*.yml, and REQUIRED_CHECKS pins the name
a maintainer must require for each. Adding a workflow, or a job to one, fails
the suite until the registry is updated.

Six workflows are now covered. Two needed a gate job that did not exist
(build.yml) or did not do its job (eos-simulation.yml, below). Three define a
single job whose own name is stable and requirable, so they need no gate, and
a test keeps them single-job. Three gate nothing on purpose -- auto-assign,
claude-code-review and book-build all skip on ordinary pull requests, and a
required check that is skipped never reports -- each recorded with its reason.

eos-simulation.yml's `integration-summary` already had the shape of a gate:
the right needs, if: always(), and a printed PASS/FAIL per dependency. It only
exited non-zero for simulate-qemu, so build-kernel, test-middleware and
test-cad-pipeline could all fail while it reported success. Anyone requiring
that check would have believed three things were gated that were not.

Finding 2: _only_runs_on_tags() was a substring test for "refs/tags", which
also matches the inverted condition -- a job running on every pull request
would have been classified as tag-only and dropped from the requirement
silently. Replaced with a pinned CANNOT_RUN_ON_PR map, plus an assertion that
each excluded job really does carry the condition claimed for it.

Finding 3: the rule is now .github/scripts/ci-gate-check.sh, executed by the
tests against real inputs -- success, failure, skipped, cancelled, mixed, and
an empty context -- instead of the workflow's run: text being grepped for
`!= "success"`. A test that greps an implementation passes for those tokens in
a comment and fails for a correct rewrite. Every gate calls the one script,
and a test enforces that too.

Finding 4: dropped `2>/dev/null || true` from the pip install in
python-tests.yml.

Verified by mutation, each caught: a new pull_request workflow with no gate; a
job added to build.yml outside its gate; a gate losing if: always(); a
required display name renamed; the shared rule weakened to ignore `skipped`;
and integration-summary reverted to gating only simulate-qemu.

28 Python tests pass, ctest 34/34, all 19 workflow files parse.

* ci: let the full-stack gate run at all, and test that a gate can

`Full-stack integration summary` invoked .github/scripts/ci-gate-check.sh
with no `actions/checkout` step, so on this head the step exited 127 on a
missing file. The gate's rule never ran: it was red because the script was
not in the workspace, not because a dependency failed, and it could never
be green -- so a maintainer following this PR's own instruction to require
that name would have made master permanently unmergeable. That is the
mirror image of the fail-open shape this PR exists to remove, and worse in
practice: fail-open lets bad code through, this blocks everything.

`test_every_gate_invokes_the_shared_script` passed on that commit, because
it greps the job's `run:` text for the script name and a job that cannot
execute the script still names it. That is the one remaining
grep-the-YAML test in a file whose own docstring says the rule should be
executed rather than pattern-matched, and it is exactly the test that
should have caught this. So the fix and the test that would have caught it
land together:

- eos-simulation.yml: `actions/checkout@v4` as the first step of
  integration-summary, as ci-gate and build-gate already have.
- test_every_gate_checks_out_the_repository_before_running_the_script:
  any gate whose `run:` names a repo-relative script must also have a
  checkout step. Fails without the fix.

Three further hardenings to the same file:

- The NOT_REQUIRED reasons were unverified free text and one had already
  drifted: book-build.yml was recorded as skipping because build-pdf is
  conditional, but its `validate` job carries no `if:` at all and the real
  reason it cannot be required is the `paths: ['docs/book/**']` filter on
  its pull_request trigger -- the never-arrives failure mode this PR is
  careful about everywhere else. The reason is corrected, and a test now
  asserts each NOT_REQUIRED workflow either carries paths/paths-ignore or
  gates every job behind an `if:`. auto-assign.yml and
  claude-code-review.yml pass it as written.
- test_gate_script_exists_and_is_executable checked only `is_file()`.
  The gates pipe into the path itself, which needs mode +x; it is
  committed 100755, but `git update-index --chmod=-x` or a
  core.fileMode=false checkout would break all three gates with the suite
  green. It now asserts os.access(X_OK), and one case invokes the script
  directly rather than via `bash`, so the invocation path the workflows
  use is covered.
- `_triggers_on_pull_request` handled only the mapping form of `on:`.
  `on: pull_request` and `on: [push, pull_request]` are both valid and
  both made a workflow invisible to the scan -- silently unclassified,
  which is the rot this file exists to catch. Latent, not live: all 19
  current workflow files use the mapping form.

Also: the comment at build.yml:174 pointed at `.github/STANDARDS.md`,
which does not exist in this repository. STANDARDS.md is the org-wide file
at embeddedos-org/.github; the comment now links it.

Each of the four new assertions was mutation-checked: removing the
checkout, chmod -x on the script, dropping book-build's paths filter, and
rewriting a workflow to the list form of `on:` each fail the suite and
each is caught by the intended test.

pytest tests/ -q: 31 passed (18 -> 21 in test_ci_gate.py).
ctest Release: 34/34.

* ci: make each gate report under its own name

The run that proved the checkout fix also showed the shared script naming
the wrong check. `Full-stack integration summary` failed and logged:

    ::error::CI Gate failed. These jobs did not succeed:
      build-kernel: failure
      simulate-qemu: skipped

The diagnosis is right and the check name is not: "CI Gate" was hardcoded
in ci-gate-check.sh, so two of the three gates report a failure under a
check name that is not theirs. In a pull request whose subject is checks
that report something other than what they measured, that is not a
cosmetic detail -- a maintainer reading the log of a red merge is told to
go look at a check that is green.

The script now takes `GATE_NAME` (defaulting to "Gate"), each gate step
sets it to its own display name, and two tests hold it in place: one runs
the script with GATE_NAME set and asserts the message uses it, one asserts
each gate declares the display name that REQUIRED_CHECKS says it reports
under. Both mutation-checked -- hardcoding the name in the script fails the
first, giving build-gate the wrong name fails the second.

pytest tests/ -q: 33 passed.

---------

Co-authored-by: Srikanth Patchava <srikanth.patchava@outlook.com>
srpatcha added a commit that referenced this pull request Sep 8, 2026
…rtos (#118)

* 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(build): declare the dependency where it belongs, and make the pop-back structural

Answers the review on #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 #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 #118

* ci: pin the eBoot and ebuild checkouts instead of floating on master

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 #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 #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 #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 #135's commit -- git drops it as already-upstream the
moment #135 merges. The pin itself is #135's to review.

---------

Co-authored-by: Srikanth Patchava <srikanth.patchava@outlook.com>
srpatcha pushed a commit that referenced this pull request Sep 8, 2026
…129)

* 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)

* ci: finish the EoSim fix — a tag that exists, and no wheel anywhere

`Simulation Test` and `EoSim Sanity` are both red on master. #106 fixed
this class in eosim-sanity.yml but did not reach every site.

simulation-test.yml still pins EOSIM_VERSION: "0.1.0". EoSim has no
v0.1.0 tag — its releases run v1.0.0 to v3.0.1 — so every run failed at
the clone:

    fatal: Remote branch v0.1.0 not found in upstream origin

Bumped to 1.5.0, the release marked Latest and the one eosim-sanity.yml
already pins.

eosim-sanity.yml still installs a wheel on its Windows and macOS legs:

    ERROR: HTTP error 404 while getting
    .../releases/download/v1.5.0/eosim-1.5.0-py3-none-any.whl

No EoSim release ships a wheel, so that URL 404s at any version — the
version bump alone could never fix these two. The other three jobs in
the same workflow already install from a clone; these two were missed.
Converted to the same clone + `pip install -e`.

Verified, against the real remote rather than from the error message:

    wheel URL                       -> HTTP 404
    git ls-remote --tags | v0.1.0   -> 0 matches
    git ls-remote --tags | v1.5.0   -> 1 match
    clone v1.5.0 + pip install -e   -> rc 0
    eosim --version                 -> eosim, version 2.0.0
    eosim doctor                    -> rc 0

(The v1.5.0 tag reporting version 2.0.0 is EoSim's own inconsistency,
not a wrong pin — v1.5.0 is what its releases page marks Latest.)

Both files still parse as YAML. Stacked on the #127 registration fix so
this branch's CI is not red for an unrelated reason.

* ci(eosim): pin the commit, assert the version pip resolved, and let the workflow test itself

Answers the review on #129. The two diagnoses in the previous commit hold --
there is no v0.1.0 tag and no EoSim release ships a Python artifact -- and
this addresses what the replacement pin did not.

Finding 1 (High) -- `EOSIM_VERSION: "1.5.0"` is a label, not a version.
Re-verified against the remote rather than taken from the review:

  tag      commit    committed    commit subject             pyproject  __init__
  v1.5.0   7dec3460  2026-05-27   "production-ready v1.4.0"    3.0.1      3.0.1
  v3.0.1   b297ec26  2026-05-16   "v3.0.1 — unified ..."       3.0.1      2.0.0

v1.5.0 is the release GitHub marks Latest and the newest tag by commit date,
tags a commit whose own message claims v1.4.0, and installs a dist declaring
3.0.1. A reader of `EOSIM_VERSION: "1.5.0"` would believe eos is simulated
against EoSim 1.5.0. It is not, and nothing in the file would reveal that.

Both workflows now pin `EOSIM_COMMIT` to the full SHA that tag resolves to
today, with the table above in the env block and the date it was checked.
Not v3.0.1 instead: its `__init__.py` says 2.0.0, so it is only marginally
less confusing. A SHA cannot be moved under us; the comment says to go back to
a tag once EoSim's tags mean something.

Finding 3 (Medium) -- `eosim --version` cannot detect a wrong install.
EoSim hardcodes it (`eosim/cli/main.py:49`,
`@click.version_option(version="2.0.0")`), confirmed by reading that file at
v1.5.0, so it prints 2.0.0 from every tag -- which is exactly why the previous
commit could record the version oddity as a curiosity rather than as a failed
pin. All three sites now also assert `importlib.metadata.version("eosim")`
against `EOSIM_EXPECTED_DIST`, which is the only version the commit pin
controls.

Finding 4 (Medium) -- this PR could not be validated by CI and was not: both
workflows were schedule- and dispatch-only, so neither appeared in this head's
checks and all 25 green ones were other workflows. Added
`pull_request: paths:` on both files listing both files, so a change to either
now runs both. `workflow_dispatch` on the fork is not available to me --
GitHub resolves the workflow on the fork's default branch and returns 404 --
so this trigger is the evidence that is actually obtainable, and it applies to
every future change to these files rather than to this one only.

Finding 2 (High) -- after this, nothing in the org exercises a published EoSim
artifact, and none exists. Verified independently: 13 releases whose assets
are promo .mp4 files, a manifest.json and EoSim-guide.pdf; no wheel or sdist
at any tag; `https://pypi.org/pypi/eosim/json` returns 404. Converting to a
clone is right -- a permanent 404 is not a useful red -- but it would also
make the gap green and invisible, which is the "verification whose result is
discarded" shape.

So `published-artifact-watch` runs on every execution, checks the releases and
PyPI, and says out loud that the packaging debt is open. It cannot fail the
workflow, deliberately: it is a tracking signal, not a gate on someone else's
repo. It emits a `::notice::` the day an artifact appears, which is when it
should be deleted and the installs pointed at it. The debt is also stated in
the PR body rather than left implied, and raised against EoSim.

Finding 5 (Low) -- `/tmp/EoSim` was hardcoded in the two jobs that run on
windows-latest and macos-latest, where the Windows job sets no `shell:` and
gets pwsh. Now `${{ runner.temp }}/EoSim` everywhere.

Finding 6 (Low) -- dropped `-e`. Nothing needs an editable install, it is
further from what a developer does in a workflow meant to prove the install
works, and it made the dist-metadata assertion depend on the clone staying
put. Changed at all five sites, not two, so they are consistent.

Also, while in this file: `sanity-gate` had the fail-open shape --
`needs:` five jobs, tested `install-validate` alone, then printed "All EoSim
sanity checks passed". Replaced with the `toJSON(needs)` body, which cannot
fall out of step with `needs:`, and the new job is included in it.

Verified:
  ctest                                       39/39 PASS
  pytest tests/                               15 passed
  both workflows parse, and structurally:
    pull_request paths list this file           both
    EOSIM_VERSION gone from every code line     both
    EOSIM_COMMIT is a full 40-char sha          both
    no /tmp/EoSim literal, no `pip install -e`  both
    every `git clone --filter` has a matching `checkout --detach`
    three importlib.metadata assertions
    sanity-gate needs == every other job in the file
  the SHA resolves: refs/tags/v1.5.0 -> tag object 978f215f -> commit
    7dec3460cba76083b6645eb4a867e39aa2af97df

  NOT RUN: neither workflow. `workflow_dispatch` on the fork returns 404
  because GitHub resolves it on the fork's default branch. The
  `pull_request: paths:` trigger should make both run on this PR for the
  first time -- that will be the first execution either has ever had, and I
  will report what it shows rather than predicting it.

Finding 7 (Low): still stacked on #127; the tests/CMakeLists.txt hunk goes
when that lands.

Refs #129

* ci(eosim): copy platforms/ in every job that runs a simulation

The first real execution of these workflows -- which the pull_request trigger
added in the previous commit made possible -- failed, and showed the next
blocker behind the missing v0.1.0 tag:

    FileNotFoundError: [Errno 2] No such file or directory:
      '/opt/hostedtoolcache/Python/3.12.14/x64/lib/python3.12/site-packages/platforms'

on all three Guest OS Install legs and all seven Nested Simulation platforms.

platforms/ is data, not package data: eosim/cli/main.py sets PLATFORMS_DIR to
<site-packages>/platforms while the wheel ships only
eosim/platforms/__init__.py. None of the five jobs that run eosim copied it,
so none of them could ever have worked. A tag that exists was necessary and
not sufficient, exactly as the review said.

  Worth recording: this is also what the dropped `pip install -e` was
  accidentally papering over in the jobs that had a copy elsewhere. With an
  editable install eosim.__file__ points into the clone, so PLATFORMS_DIR
  landed beside the checked-out platforms/ by luck. A plain install is the
  right thing and it made the real dependency visible.

install-validate went green in that run and the tracking job reported the
packaging debt as intended; the failures were confined to the jobs that
actually boot a platform.

Verified:
  both workflows parse; all five eosim-running jobs now copy platforms/
  the run that produced the error: 33783625398 (EoSim Sanity), first
  execution this workflow has ever had

Refs #129

* ci(eosim): run the bash steps in bash, including on the Windows legs

Second real execution, after the platforms/ fix: ubuntu and macOS now pass
Install & Validate; all three windows-latest legs fail.

    Install & Validate (windows-latest, Python 3.12)
      #   FileNotFoundError: .../site-packages/platforms
      ##[error]Process completed with exit code 1

The give-away is the leading `#` in that line: pwsh echoed my comment as a
command. install-validate is a matrix over ubuntu/windows/macos and set no
`shell:`, so on the Windows legs it got the runner default, where
`VAR=$(...)`, `cp -r` and a leading `#` are all wrong. windows-sanity had
the same gap.

`shell: bash` on the six run steps in the two Windows-capable jobs. This is
the same defect the review raised as finding 5 for the hardcoded /tmp path --
${{ runner.temp }} fixed where the clone goes, and this fixes what interprets
the script.

Worth stating plainly: none of this was visible before the pull_request
trigger. Two runs have now found two blockers behind the missing v0.1.0 tag --
platforms/ never copied in any job, and bash steps running under pwsh -- in a
workflow that had never executed once. The trigger is doing more work than the
pin.

Verified:
  parses; no run step in a windows-capable job is left without a shell
  run 33784349071: ubuntu + macOS Install & Validate green, windows red
    with the error above

Refs #129

* ci(sim): run the bash steps in bash on the Windows leg too

Third run. EoSim Sanity is green -- 23/23 jobs, the first green run that
workflow has ever had. Simulation Test has one failure left,
`Cross-Platform (windows-latest)`, and it is the same defect I fixed in
eosim-sanity.yml one commit ago and did not apply here:

    Cross-Platform (windows-latest)  Install EoSim from source
      # fails with FileNotFoundError.
      ##[error]Process completed with exit code 1

pwsh echoing a `#` comment as a command. The cross-platform matrix includes
windows-latest and its two run steps set no `shell:`.

Refs #129

* ci(eosim): one install recipe, gates that refuse empty input, and a watch that stops posing as a check

Answers the overnight review on #129.

Finding 3 (Low, and the structural one) -- the five-line install recipe was
copy-pasted at seven sites across two workflows, and the PR's own history
shows the cost: -e dropped "at all five sites, not two", runner.temp at all
of them, shell: bash at several, each a separate round because each site was
a separate edit. Now .github/actions/install-eosim owns it -- clone at the
pinned commit, install, stage platforms/, assert the dist version -- and all
seven sites are a four-line `uses:`. The platforms/ workaround and the
version assertion get one place to be deleted from when EoSim fixes its
packaging. The triplicated inline importlib.metadata assert is gone with it.

Finding 5 (Low), folded into the composite -- SITE_PACKAGES comes from
sysconfig.get_paths()["purelib"], which needs no import and cannot be
shadowed, matching eBoot's copy of the workaround instead of solving the same
problem a second way.

Finding 1 (Medium) -- the inline gate accepted nothing as everything: an
empty RESULTS printed "All EoSim sanity jobs succeeded." and exited 0, and
null produced a raw jq error, where eos#121's extracted ci-gate-check.sh
refuses both. Both gates now refuse empty/null input with a diagnosis before
jq runs, under set -euo pipefail, and carry a note to become a call to #121's
script once it is on master so the two cannot drift.

  Reproduced the review's side-by-side on the hardened body:
      ""      rc=1  gate received no results to check   (was rc=0 PASS)
      null    rc=1  gate received no results to check   (was raw jq error)
      {}      rc=0  PASS   -- shared with #121's script; reported there, and
      []      rc=0  PASS      unreachable here: needs has five entries
      skipped rc=1  FAIL: a: skipped
      success rc=0  PASS

Also applied to simulation-test.yml's Simulation Gate, which still had the
original fail-open shape -- needs: [simulate, cross-platform], a test of
`simulate` alone, then "All simulation tests passed". Same toJSON(needs)
body, same guards.

Finding 4 (Low) -- published-artifact-watch could not fail and sat in
sanity-gate's needs under a name that made a green tick assert a deficiency.
Now "EoSim artifact publication (informational)", schedule/dispatch only, out
of the gate: pull requests no longer carry a permanently-green status about
another repository's release process, and the debt still gets said out loud
every night.

Finding 6 (Low) -- simulation-test's EOSIM_COMMIT comment argued tag
selection for what is now a SHA; it now states the actual reason (EoSim's tag
names do not identify their contents) and gains the EOSIM_EXPECTED_DIST the
composite asserts.

Finding 2 (Medium) is a two-line change in eos#121's registry, not here --
both workflows need NOT_REQUIRED entries with a paths-filter reason once both
PRs land, or #121's classification test fails. Posted on both threads with
the exact entries; not folded in, because that registry lives on #121's
branch and this PR must not carry another PR's hunks.

Verified locally before push:
  all three YAML files parse
  composite referenced at 7 sites; zero inline clone/install/copy remnants
  eosim-sanity gate needs == every job except itself and the informational
    watch; sim gate iterates toJSON(needs), no single-dependency branch
  the five-input table above, executed
  the workflows themselves: this push's pull_request run is the test, and I
  will report what it shows rather than predict it.

Refs #129

* ci: pin the eBoot and ebuild checkouts instead of floating on master

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 #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 #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 #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 #135's commit -- git drops it as already-upstream the
moment #135 merges. The pin itself is #135's to review.
srpatcha added a commit that referenced this pull request Sep 8, 2026
…Boot (#122)

* 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(ed25519): reject a non-canonical S, and share the contract with eBoot

Two things, and the second found the first.

eos and eBoot each carry their own Ed25519 verifier. Different code,
opposite return conventions — this one returns 1 to accept, eBoot's
returns EOS_OK — doing the same job on the same wire format. When only
eos rejected low-order public keys, nothing in either repo could notice:
there is no shared build, and the two are far too different for a source
diff to say anything. That divergence is why eBoot #73 survived here
after this side had already fixed it.

tests/vectors/ed25519_contract_vectors.h is the part that can be shared:
76 vectors of pure data with a SHA-256 over them, byte-identical to
eBoot's copy (eBoot #89). Each side runs its own verifier and prints the
digest, so a change to one copy that does not reach the other shows up
as two different digests.

On its first run it failed here, on three vectors eBoot passes.

ed25519_verify() had only ref10's partial check, `signature[63] & 224`.
That rejects any S with a bit set above 2^252, but

    L = 2^252 + 27742317777372353535851937790883648493

so every S in [L, 2^253) passes it while being non-canonical. Adding L
to a valid S lands squarely in that band, and (R, S) and (R, S + L) both
verified — signature malleability. Anything treating a signature as a
unique identifier (deduplication, replay caches, logging by signature
hash) sees two distinct signatures over one message and one key.

RFC 8032 5.1.7 requires rejecting S outside [0, L). eBoot's
core/ed25519_verify.c has done this all along via s_is_canonical();
this side did not. Added sc_is_canonical(), compared most-significant
byte first against a public constant — S is public, so this leaks
nothing.

Verified:
  contract vectors, before the fix -> 3 of 76 wrong (all three S+L)
  contract vectors, after          -> 76/76, 3 accepted, 73 refused
  test_crypto_ed25519_loworder     -> 5/5, unchanged
  ctest                            -> 35/35 passed
  digest 1059febedae2b3e3dfaa1ef5b419fb37ed7f0d53b377a3250b53b95a546282a2
         identical to eBoot #89's copy

Stacked on #116; eos master does not build without it.

* build(tests): generate the Ed25519 contract vectors, do not commit them

tests/vectors/ed25519_contract_vectors.h is 54 KB of generated data, and the
identical file was checked in to BOTH eos and eBoot, along with the generator
that produces it -- 61 KB duplicated verbatim across two repositories and kept
in step by hand.

The generator is pure stdlib and deterministic, so each repo can produce the
header into its own build tree instead. tools/gen_ed25519_contract_vectors.py
grows an -o/--output flag (stdout stays the default, so the regeneration
command in its docstring still works), and tests/CMakeLists.txt drives it from
add_custom_command. Only the 6.8 KB generator is now carried twice, and it is
source rather than generated output.

Verified: the generated header is byte-identical to the file it replaces; both
repos still report digest
1059febedae2b3e3dfaa1ef5b419fb37ed7f0d53b377a3250b53b95a546282a2, which is the
thing that actually holds the two verifiers to one contract. Suites pass (eos
35/35, eBoot 22/22). Breaking the generator fails the build rather than
silently reusing a stale header. The cross-compile legs configure with tests
OFF so find_package(Python3) is never reached there; confirmed by configuring
under a cross toolchain.

* test(ed25519): assert the contract digest, and test the [0, L) boundary directly

Answers the review on #122. The malleability fix itself was found correct;
these are the findings around it.

Finding 1 (High) -- the digest was printed and never compared, so the shared
contract could not fail. EOS_ED25519_CONTRACT_DIGEST came from the header this
repo's own generator had just written, a hash taken over its own output, so
editing the generator here and not in eBoot produced a self-consistent corpus
with a new digest, a green test, and a divergence visible only to a human
reading two CI logs. Now pinned as a literal in committed source, together
with the vector count and accept count, which are generated for the same
reason and were equally unfalsifiable. The eBoot twin (#89) pins the identical
value.

Finding 4 (Medium) -- sc_is_canonical()'s accept side had no committed test.
Through the public API the boundary is unreachable: S == L and a failed group
equation are both simply 0, so an off-by-one refusing a valid S = L - 1 would
satisfy every vector in the corpus and reject real signatures only in the
field. It is a pure function of 32 bytes, so tests/test_ed25519_canonical_s.c
tests it directly -- 0, 1, L-1, L, L+1, 2^252, 2^252+2^251 and all-ones -- with
no signing key and no `cryptography` module. ed25519_verify.c is compiled a
second time with EOS_ED25519_TEST_HOOKS to give the function external linkage;
the shipped build is unchanged.

  This is the case the finding was about, reproduced. With `S == L` accepted:
      test_ed25519_canonical_s   [FAIL] at the S = L case
      test_ed25519_contract      [PASS] all 76 vectors
  The corpus cannot see it. The direct test can.

  Also corrected while writing it: 2^252 is BELOW L (L = 2^252 + 2.77e37), so
  it is in range. The value that is both non-canonical and passed by ref10's
  `signature[63] & 224` test is 2^252 + 2^251 -- 0x18 & 224 == 0. Both are now
  asserted, the second alongside the mask check that shows why the partial
  test misses it.

Finding 2 (Medium) -- tests/vectors/ed25519_contract_vectors.h is now
committed rather than generated. CMAKE_CURRENT_SOURCE_DIR preceded
CMAKE_CURRENT_BINARY_DIR on the include path, so a stale source-tree copy
would silently win over the generated one -- and the PR body's own
re-derivation command wrote to exactly that path. Committing removes the
shadow, makes the corpus auditable from a tag, and drops
find_package(Python3 ... REQUIRED) from a pure-C suite.

NOT fixed here, and stated rather than left implied: finding 3, the corpus
omitting the y >= p (non-canonical public key) family. That is the class most
likely to diverge between the two verifiers and it is worth adding -- but
adding vectors changes the digest, which both repos now pin, so it has to land
in eos and eBoot together in one coordinated change. That is the mechanism
working as intended, and it is a follow-up rather than something to slip into
this PR.

Verified:
  ctest                          41/41 PASS
  test_ed25519_contract          76 vectors, 3 accept, 73 refuse
  test_ed25519_canonical_s       PASS
  rebased onto master; conflicts in tests/CMakeLists.txt and
  services/crypto/src/ed25519_verify.c resolved additively -- master's
  ed25519_public_key_is_usable() (#120) and this branch's sc_is_canonical()
  both kept.

Refs #122

* test(ed25519): hash the vectors, and drop the linkage macro CodeQL flagged

Answers the second round on #122. Both findings are defects in my previous
commit.

The digest pin did not cover the corpus. EOS_ED25519_CONTRACT_DIGEST is a
#define inside the generated header; EOS_ED25519_CONTRACT_EXPECTED is a
#define in the driver. The test compared two string literals and never hashed
a vector, so editing a committed vector changed the data and left the value it
was compared against untouched. Raised on the eBoot twin (#89) and stated
there to apply here too; it did.

contract_digest() now recomputes SHA-256 over the vectors at run time in the
generator's serialisation -- public_key[32] || signature[64] ||
message[message_len] || (uint8_t)expect_accept -- via eos_sha256_* from
eos/crypto.h. It compares the result to the pinned literal and to the header's
own claim, so a corpus and a header from different generator runs are caught.

  Verified with the probe from the eBoot review. Changing one byte of the
  order-1 identity public key:
      [FAIL] corpus digest changed
             expected 1059febe...282a2
             got      55370deb...cae9      exit 1
  That `got` value is byte-identical to the one eBoot's driver produces for
  the same edit, which is independent confirmation that the two committed
  corpora really are the same file.

  Worth recording because it nearly cost me the finding: the first probe run
  reported PASS. The build had not picked up the header change, so the binary
  was stale. `touch` on the driver and a rebuild gave the failure above. A
  probe that does not rebuild proves nothing -- check that the link step ran.

CodeQL alert 405, "Static function sc_is_canonical is unreachable" -- caused
by the EOS_ED25519_TEST_HOOKS macro I added, which gave the symbol a linkage
that varied by build. Removed: the function is static again, and
tests/test_ed25519_canonical_s.c includes the translation unit to reach it,
which is the ordinary way to test a static and needs no conditional
compilation in a crypto source file. eos_crypto is still linked for the
ge_*/sha512 helpers the included unit calls -- a static archive contributes
only objects needed for undefined symbols, and ed25519_verify.c.o is not one,
because the test object already defines everything in it.

Verified:
  ctest                              41/41 PASS
  test_ed25519_canonical_s           PASS, links with no duplicate symbols
  test_ed25519_contract              76 vectors, 3 accept, 73 refuse
  byte-flip probe                    FAILS (after a real rebuild)
  off-by-one probe, `S == L` accepted:
      test_ed25519_canonical_s       FAILS
      test_ed25519_contract          still exit 0
    so the direct boundary test still discriminates where the corpus cannot.

Refs #122

* docs(ed25519): make the two copies actually byte-identical, drop stale comments

Addresses srpatcha's review findings 1 and 3 on #122. Text and comments only;
no behaviour change, and the 76 vectors and their digest are untouched.

Finding 1 -- the body claimed the corpus was byte-identical to eBoot#89's copy
and it was not: the two generators had drifted in their docstrings and in the
header comment they emit, so the files differed by three comment lines while
all 76 vectors and the digest matched. Adopted eBoot#89's wording verbatim --
it is the more accurate description, since each side recomputes the digest and
compares it to a pinned literal rather than merely printing it -- and
regenerated. Both repos' tools/gen_ed25519_contract_vectors.py and
tests/vectors/ed25519_contract_vectors.h are now byte-identical:

    sha256 489b1fc0662187841c58e12c650ca1a25a974e4fafe4d4318f4fad62b7e4b5cf

which is the value the review measured for eBoot's copy, so the spot-check the
finding describes -- hash both files -- now passes.

Finding 3 -- three comment blocks described an approach that was abandoned,
two of them contradicting the code beside them:

(a) ed25519_verify.c opened "Linked, not static, when the test hook is on"
    six lines above "It stays static", and the function is static.
(b) tests/CMakeLists.txt described compiling ed25519_verify.c a second time
    with EOS_ED25519_TEST_HOOKS. That macro occurred exactly once in the tree
    -- in that sentence. The next line already describes what the test really
    does (#include the translation unit).
(c) the paragraph saying the vector table "is produced into the build tree
    rather than committed" sat directly above one saying it is committed. It
    is committed; the generate-at-build-time paragraph was mine, left behind
    when that approach was reverted, and it is the half that was false.

In each case the surviving text already explained the real design, so this is
subtraction. Verified: cmake+ctest 41/41 (the count finding 4 asks for, not
the 35/35 still in the body), test_ed25519_contract reports digest 1059feb...
over 76 vectors with 3 accepted and 0 wrong, and test_ed25519_canonical_s
still passes.

---------

Co-authored-by: Srikanth Patchava <srikanth.patchava@outlook.com>
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.
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