Skip to content

crypto: refuse stub RSA/ECC signing unless EOS_ALLOW_STUB_CRYPTO - #84

Merged
srpatcha merged 2 commits into
embeddedos-org:masterfrom
JoaoMorais03:fix/stub-crypto-sign-fail-closed
Sep 1, 2026
Merged

srpatcha merged 2 commits into
embeddedos-org:masterfrom
JoaoMorais03:fix/stub-crypto-sign-fail-closed

Conversation

@JoaoMorais03

Copy link
Copy Markdown

Contribution after reviewing the project: RSA/ECC verify already fail-closed unless EOS_ALLOW_STUB_CRYPTO is defined, but sign still minted fake signatures in production builds.

Issue

services/crypto/src/rsa_ecc_sha512.c documents that the RSA/ECC routines are stubs, not PKCS#1/ECDSA. After the earlier verify-side fix, eos_rsa_verify_sha256 / eos_ecc_verify refuse to run without the opt-in. eos_rsa_sign_sha256 still wrote a PKCS#1-looking blob ending in the hash and returned 0; eos_ecc_sign still wrote a fake 64-byte r||s and returned 0. A signing pipeline could look like it worked while producing forgeries.

tests/test_crypto_failclosed.c already asserts that signing must refuse, and is listed among unregistered tests. It was not in tests/CMakeLists.txt.

Approach

Call EOS_STUB_CRYPTO_REFUSE from both sign functions after the existing NULL checks, matching verify. Register test_crypto_failclosed against eos_crypto without EOS_ALLOW_STUB_CRYPTO so CI covers the production path. Existing test_crypto_rsa / test_crypto_ecc only (void) the sign/verify status, so they still pass.

Testing

The fail-closed suite checks:

  • the old RSA trailing-hash forgery is rejected
  • RSA/ECC verify never succeed without the opt-in
  • RSA/ECC sign refuse to mint fake signatures

I did not run CTest here (no local checkout). test_crypto_rsa.c / test_crypto_ecc.c were read to confirm they do not assert sign success.

Limitations

Does not implement real RSA/ECDSA. Tests that want the old stub behavior must define EOS_ALLOW_STUB_CRYPTO (none of the currently registered tests do). Master currently has duplicate test_crypto_aes / test_crypto_sha512 CMake targets, so a full test configure still fails until that is fixed independently; this PR only adds test_crypto_failclosed.

Verify already fail-closed; sign still minted fake signatures in production.
Register the existing test_crypto_failclosed suite so that cannot regress.

Signed-off-by: João Morais <118842104+JoaoMorais03@users.noreply.github.com>
Copilot AI lite review requested due to automatic review settings August 29, 2026 16:26

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

The test CMakeLists currently defines duplicate targets (breaking test configuration) and the RSA signing stub still risks unsafe length underflow/overflow in EOS_ALLOW_STUB_CRYPTO builds.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR hardens the stub RSA/ECC implementation in eos_crypto by making sign fail-closed (matching existing verify behavior) unless EOS_ALLOW_STUB_CRYPTO is explicitly enabled, and registers a regression test intended to prevent “fake signature” behavior from silently returning success in production builds.

Changes:

  • Add EOS_STUB_CRYPTO_REFUSE(...) guards to eos_rsa_sign_sha256() and eos_ecc_sign() so stub signing refuses to run without EOS_ALLOW_STUB_CRYPTO.
  • Register test_crypto_failclosed in the test CMake list to exercise the fail-closed production path.
File summaries
File Description
tests/CMakeLists.txt Registers test_crypto_failclosed (but currently contains duplicate AES/SHA512 targets that will prevent CMake from configuring tests).
services/crypto/src/rsa_ecc_sha512.c Makes RSA/ECC signing match verify’s fail-closed behavior; small stub-safety/cleanup issues remain.
Review details

Suppressed comments (1)

services/crypto/src/rsa_ecc_sha512.c:58

  • eos_rsa_sign_sha256() computes len from key_bits and then uses len - 35/len - 33 later. If a caller sets an unexpectedly small key_bits value (or an oversized one), this can underflow/overflow and cause out-of-bounds writes to sig in EOS_ALLOW_STUB_CRYPTO builds.
    EOS_STUB_CRYPTO_REFUSE("eos_rsa_sign_sha256");

    /* Stub: produce a deterministic fake signature for testing.
     * Replace with real PKCS#1 v1.5 or PSS signing. */
    size_t len = (size_t)(key->key_bits / 8);
  • Files reviewed: 2/2 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread tests/CMakeLists.txt
Comment on lines 72 to +76
add_executable(test_crypto_sha512 test_crypto_sha512.c)
target_link_libraries(test_crypto_sha512 PRIVATE eos_crypto)
add_test(NAME test_crypto_sha512 COMMAND test_crypto_sha512)

# --- test_crypto_failclosed: unimplemented RSA/ECC stubs refuse to run ---
Comment on lines +85 to 88
EOS_STUB_CRYPTO_REFUSE("eos_ecc_sign");
/* Stub: produce a fake 64-byte signature (r||s) */
size_t len = 64;
if (hash_len < 32) len = 64;
srpatcha
srpatcha previously approved these changes Aug 29, 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.

Verified — this closes a real asymmetry

Checked origin/master before reviewing. The guards are on verify only:

50:int eos_rsa_sign_sha256(...)                      ← unguarded
67:int eos_rsa_verify_sha256(...)
69:    EOS_STUB_CRYPTO_REFUSE("eos_rsa_verify_sha256")
80:int eos_ecc_sign(...)                             ← unguarded
92:int eos_ecc_verify(...)
97:    EOS_STUB_CRYPTO_REFUSE("eos_ecc_verify")

So a build that correctly refuses to verify with stub crypto would still happily sign with it. Your reasoning for why that matters is the right one: a stub that mints a PKCS#1-looking blob or a fake r||s lets a signing pipeline appear to work end to end. The artifacts it produces are not signed by anything, and the failure surfaces at some later point — a real verifier rejecting them, or worse, a stub verifier accepting them — a long way from the code that caused it.

Making sign and verify symmetric is the correct fix, and reusing EOS_ALLOW_STUB_CRYPTO rather than inventing a second opt-in keeps one switch for one concept.

The test is the durable part

test_crypto_failclosed deliberately does not define EOS_ALLOW_STUB_CRYPTO, so it exercises the production path rather than the test path. That is the distinction that makes it a guard: the existing crypto tests all opt in, so none of them would notice the refusal being removed.

The note to replace it with known-answer tests against published vectors when real verification lands is the right instruction to leave behind — a fail-closed test becomes actively misleading once the function works.

One merge-order note

This conflicts with #82, which repairs master. Both touch tests/CMakeLists.txt: your new test_crypto_failclosed registration sits inside the duplicated test_crypto_aes / test_crypto_sha512 block that #82 removes — the duplicate is why master currently fails to configure at all.

The resolution is to keep your test_crypto_failclosed lines and drop the duplicated aes/sha512 ones, which are already registered earlier in the file. I did exactly that locally and it is clean.

Verification

Merged onto origin/master + #82, resolved as above: configure OK, 0 build errors, ctest 29/29. Not verified against master alone, because master does not currently configure.

srpatcha added a commit that referenced this pull request Aug 31, 2026
ed25519_verify() decoded the public key and never checked which subgroup it
was in. Ed25519 has eight low-order points, and for any of them every term
of the verification equation collapses regardless of the message, so a
signature of all zeros verifies against arbitrary content.

This was not hypothetical. eos_pkg.c:94 ships

    static const uint8_t eos_pkg_public_key[32] = {0};

and the all-zero encoding is one of the eight. Measured against the built
library, before this change:

    all-zero key: 2 of 256 arbitrary signatures ACCEPTED

Package installation authenticated nothing. The SHA-256 check two lines
above is no help — it compares a hash of the payload against a field in the
same attacker-supplied file, which detects corruption, not origin. See #98.

The check multiplies the key by L and requires the identity, rather than
comparing against a table of the eight encodings. Both are correct; this one
cannot be wrong in a way that silently rejects valid keys because a constant
was transcribed incorrectly.

Two things this took to get right, both found by testing rather than
reasoning:

The subgroup test alone is insufficient. The identity has order 1, which
divides L, so [L]identity = identity and it passes. The first version of
this check still accepted an identity key. It is now rejected explicitly,
which is why the helper has two parts.

The regression test's first version passed against the unfixed code. The
all-zero key is order 4, so with S = 0 the equation reduces to [k](-A) and
whether that lands on R depends on k mod 4 — on the message. The forgery
succeeds for roughly one message in four, and "untrusted firmware" happened
to be one of the three that do not verify. It looked like a regression test
and was not one. It now sweeps messages verified to be accepted by the
unfixed implementation.

    unfixed   test_all_zero_key_is_rejected  [FAIL] at line 112
    fixed     5/5 tests passed

The suite covers all 64 combinations of the eight low-order encodings as key
and as R, plus RFC 8032 vector 1 — without which a check that rejected every
key would also pass.

    ctest   28/29 -> 29/29 passed, 0 build errors

This does not make package signing work. A correct verifier with an all-zero
trust anchor now rejects everything instead of accepting everything, which is
the safer failure and still not a functioning signature check. #98 covers
supplying a real key, and whether eos_pkg should refuse to run at all while
its anchor is unset — the shape #84 uses for stub RSA/ECC.

Refs #98

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings September 1, 2026 09:21

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review is ineligible. To be eligible to request a review, you need a paid Copilot license, or your organization must enable Copilot code review.

@srpatcha
srpatcha merged commit cc90f6f into embeddedos-org:master Sep 1, 2026
srpatcha added a commit that referenced this pull request Sep 1, 2026
fix(crypto): reject Ed25519 public keys outside the prime-order subgroup

ed25519_verify() decoded the public key and never checked which subgroup it
was in. Ed25519 has eight low-order points, and for any of them every term
of the verification equation collapses regardless of the message, so a
signature of all zeros verifies against arbitrary content.

This was not hypothetical. eos_pkg.c:94 ships

    static const uint8_t eos_pkg_public_key[32] = {0};

and the all-zero encoding is one of the eight. Measured against the built
library, before this change:

    all-zero key: 2 of 256 arbitrary signatures ACCEPTED

Package installation authenticated nothing. The SHA-256 check two lines
above is no help — it compares a hash of the payload against a field in the
same attacker-supplied file, which detects corruption, not origin. See #98.

The check multiplies the key by L and requires the identity, rather than
comparing against a table of the eight encodings. Both are correct; this one
cannot be wrong in a way that silently rejects valid keys because a constant
was transcribed incorrectly.

Two things this took to get right, both found by testing rather than
reasoning:

The subgroup test alone is insufficient. The identity has order 1, which
divides L, so [L]identity = identity and it passes. The first version of
this check still accepted an identity key. It is now rejected explicitly,
which is why the helper has two parts.

The regression test's first version passed against the unfixed code. The
all-zero key is order 4, so with S = 0 the equation reduces to [k](-A) and
whether that lands on R depends on k mod 4 — on the message. The forgery
succeeds for roughly one message in four, and "untrusted firmware" happened
to be one of the three that do not verify. It looked like a regression test
and was not one. It now sweeps messages verified to be accepted by the
unfixed implementation.

    unfixed   test_all_zero_key_is_rejected  [FAIL] at line 112
    fixed     5/5 tests passed

The suite covers all 64 combinations of the eight low-order encodings as key
and as R, plus RFC 8032 vector 1 — without which a check that rejected every
key would also pass.

    ctest   28/29 -> 29/29 passed, 0 build errors

This does not make package signing work. A correct verifier with an all-zero
trust anchor now rejects everything instead of accepting everything, which is
the safer failure and still not a functioning signature check. #98 covers
supplying a real key, and whether eos_pkg should refuse to run at all while
its anchor is unset — the shape #84 uses for stub RSA/ECC.

Refs #98

Co-authored-by: srpatcha <srikanth@embeddedos.org>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
srpatcha added a commit that referenced this pull request Sep 1, 2026
…oup (#99)

ed25519_verify() decoded the public key and never checked which subgroup it
was in. Ed25519 has eight low-order points, and for any of them every term
of the verification equation collapses regardless of the message, so a
signature of all zeros verifies against arbitrary content.

This was not hypothetical. eos_pkg.c:94 ships

    static const uint8_t eos_pkg_public_key[32] = {0};

and the all-zero encoding is one of the eight. Measured against the built
library, before this change:

    all-zero key: 2 of 256 arbitrary signatures ACCEPTED

Package installation authenticated nothing. The SHA-256 check two lines
above is no help — it compares a hash of the payload against a field in the
same attacker-supplied file, which detects corruption, not origin. See #98.

The check multiplies the key by L and requires the identity, rather than
comparing against a table of the eight encodings. Both are correct; this one
cannot be wrong in a way that silently rejects valid keys because a constant
was transcribed incorrectly.

Two things this took to get right, both found by testing rather than
reasoning:

The subgroup test alone is insufficient. The identity has order 1, which
divides L, so [L]identity = identity and it passes. The first version of
this check still accepted an identity key. It is now rejected explicitly,
which is why the helper has two parts.

The regression test's first version passed against the unfixed code. The
all-zero key is order 4, so with S = 0 the equation reduces to [k](-A) and
whether that lands on R depends on k mod 4 — on the message. The forgery
succeeds for roughly one message in four, and "untrusted firmware" happened
to be one of the three that do not verify. It looked like a regression test
and was not one. It now sweeps messages verified to be accepted by the
unfixed implementation.

    unfixed   test_all_zero_key_is_rejected  [FAIL] at line 112
    fixed     5/5 tests passed

The suite covers all 64 combinations of the eight low-order encodings as key
and as R, plus RFC 8032 vector 1 — without which a check that rejected every
key would also pass.

    ctest   28/29 -> 29/29 passed, 0 build errors

This does not make package signing work. A correct verifier with an all-zero
trust anchor now rejects everything instead of accepting everything, which is
the safer failure and still not a functioning signature check. #98 covers
supplying a real key, and whether eos_pkg should refuse to run at all while
its anchor is unset — the shape #84 uses for stub RSA/ECC.

Refs #98

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Kartikey1306 added a commit to Kartikey1306/eos that referenced this pull request Sep 3, 2026
embeddedos-org#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 embeddedos-org#75, embeddedos-org#82 and embeddedos-org#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.
Kartikey1306 added a commit to Kartikey1306/eos that referenced this pull request Sep 3, 2026
embeddedos-org#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 embeddedos-org#75, embeddedos-org#82 and embeddedos-org#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.
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>
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.

3 participants