Repository navigation
Conversation
…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)
`Simulation Test` and `EoSim Sanity` are both red on master. embeddedos-org#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 embeddedos-org#127 registration fix so this branch's CI is not red for an unrelated reason.
srpatcha
left a comment
There was a problem hiding this comment.
Review — eos#129 "ci: finish the EoSim fix — a tag that exists, and no wheel anywhere"
head: 57e5f38 author: Kartikey1306 ci: pass (25 pass, 2 skipping — but neither workflow this PR fixes ran; see finding 4)
Verdict: Both diagnoses are correct and I confirmed them against the real remote — there is no v0.1.0 tag, and no EoSim release ships a Python artifact at any version. The two wheel-install sites this touches are the only two left in the repo, so the class is genuinely closed. But the replacement pin is not a version pin: EOSIM_VERSION: "1.5.0" resolves to a commit that calls itself four different versions, and the workflow's only version assertion is a hardcoded literal that cannot detect it. The body's closing note calls this "EoSim's own inconsistency" and sets it aside; it is load-bearing for the change being made.
What I verified against the remote
gh api repos/embeddedos-org/EoSim/tags -> v3.0.1 v3.0.0 v2.0.0 v1.5.0 v1.4.0 v1.3.1
v1.3.0 v1.2.0 v1.1.0 v1.0.2 v1.0.1 v1.0.0
v0.2.0-book production-ready
no v0.1.0 -> the simulation-test.yml fix is necessary and right
gh api .../releases (13 releases, all assets) -> promo .mp4 x13, manifest.json x2, EoSim-guide.pdf
no wheel, no sdist, at any version
https://pypi.org/pypi/eosim/json -> HTTP 404
gh api .../releases/latest -> v1.5.0 (the author's pin rationale is correct)
And by installing both candidate tags here (uv pip install -e, rc=0 for each):
tag v1.5.0 |
tag v3.0.1 |
|
|---|---|---|
| commit | 7dec3460 |
b297ec26 |
| commit date | 2026-05-27 | 2026-05-16 |
| commit subject | feat: production-ready v1.4.0 — |
chore(release): v3.0.1 — unifie… |
pyproject.toml:7 (the installed dist version) |
3.0.1 |
3.0.1 |
eosim/__init__.py __version__ |
3.0.1 |
2.0.0 |
eosim --version |
eosim, version 2.0.0 |
eosim, version 2.0.0 |
So v1.5.0 is the newest tag by commit date, tags a commit whose own message says v1.4.0, declares 3.0.1 to pip, and reports 2.0.0 from the CLI. The tag namespace is not ordered and does not describe its contents.
Findings
| # | Severity | File:line | Finding | Recommended fix |
|---|---|---|---|---|
| 1 | High | .github/workflows/simulation-test.yml:44 (EOSIM_VERSION: "1.5.0"); eosim-sanity.yml:20 |
1.5.0 is a label, not a version. The table above is the evidence: that tag installs a dist declaring 3.0.1, from a commit dated after v3.0.1's and whose message claims 1.4.0. v2.0.0, v3.0.0 and v3.0.1 are self-consistent in pyproject.toml; v1.5.0 is the one that is not, and it is the one both workflows now pin. A reader of EOSIM_VERSION: "1.5.0" will believe eos is simulated against EoSim 1.5.0. It is not, and no amount of re-reading the workflow reveals that. §9.2 requires "reproducible lockfiles/manifests for production builds"; this is the CI equivalent and it is not reproducible in any sense that survives EoSim retagging. |
Pin the commit, not the name: git clone --filter=blob:none https://…/EoSim.git /tmp/EoSim && git -C /tmp/EoSim checkout <sha>, with EOSIM_COMMIT: 7dec3460cba76083b6645eb4a867e39aa2af97df (today's v1.5.0) or b297ec26544cfdab55fe13105265604e2d772293 (today's v3.0.1) and a comment saying which tag it corresponded to and when. Do not simply switch to v3.0.1 — its __version__ is 2.0.0, so it is only marginally less confusing. Reinstate a tag pin once EoSim's tags mean something. |
| 2 | High | .github/workflows/eosim-sanity.yml:113-120, 129-136 |
After this merges, nothing in the org exercises a published EoSim artifact — and none exists to exercise. Verified: 13 releases, zero wheels, zero sdists, and eosim is not on PyPI (404). §17 makes EoSim an adoption primitive — "Discover -> Install -> ebuild run --sim", "first simulated application in minutes, not hours" — and §39's success state has an independent developer install the SDK and run EoS in simulation. A developer following that path today cannot: their only route is git clone at a tag, which is what these two jobs are being converted to. The conversion is the correct immediate move (a 404 is not a useful red), but it removes the last signal that would have surfaced the real defect, which is .ai/reviewer.md's "a verification whose result is discarded" — not to be rounded down. It is also §10 exactly: "repositories should not be the dependency API." eos consumes EoSim by git URL because EoSim publishes no component. |
Not fixable in this repo, so land this and record the debt in the same breath: (a) open an EoSim issue to build and attach a wheel + sdist per release, or publish to PyPI, with release.yml doing it; (b) keep one job — or a new eosim-release-artifact check — that asserts the published artifact exists and installs, so its absence stays visible instead of becoming green; (c) note in the PR body that the wheel path is now untested org-wide, not merely fixed. |
| 3 | Medium | eosim-sanity.yml:115, 131 (eosim --version) |
The "Verify installation" assertion cannot detect a wrong install. eosim --version is a hardcoded literal — eosim/cli/main.py:49, @click.version_option(version="2.0.0", prog_name="eosim"), repeated at eosim/api/server.py:29 — so it prints 2.0.0 from both candidate tags and would print it from any future one. That is why the mis-pin in finding 1 is invisible, and why the body could record eosim --version -> 2.0.0 as an oddity rather than as a failed pin. A version check that returns a constant is a check that has not run. |
Assert the number pip actually installed: python -c "import importlib.metadata as m; assert m.version('eosim') == '$EOSIM_EXPECTED', m.version('eosim')". That reads dist metadata from pyproject.toml, which is the only version the pin controls. Separately, an EoSim issue to single-source __version__ and stop hardcoding it in cli/main.py and api/server.py. |
| 4 | Medium | simulation-test.yml:3-6; eosim-sanity.yml:3-6 |
This PR cannot be validated by CI, and was not. Both workflows are on: schedule + workflow_dispatch — no pull_request trigger — so neither Simulation Test nor EoSim Sanity appears anywhere in this head's checks.txt (27 entries, neither present). The 25 green checks say nothing about the change. All evidence for the fix is the author's local runs, which is honest but is not the same as the job passing. |
Run both via workflow_dispatch on fix/eosim-remaining-wheel-and-version and link the two run URLs in the body before merge — that is the only pre-merge proof available. Longer term, add on: pull_request: paths: ['.github/workflows/eosim-sanity.yml', '.github/workflows/simulation-test.yml'] so a change to these files tests itself. |
| 5 | Low | eosim-sanity.yml:113, 129 |
/tmp/EoSim is hard-coded in the two jobs that run on windows-latest and macos-latest. The Windows job sets no shell:, so it gets the runner default pwsh, where /tmp/EoSim resolves drive-relative rather than to a real temp directory. It works today by accident of that resolution. The three jobs already using a clone (lines 38, 69, 97) are all ubuntu-latest in at least one matrix leg, so this is the first time the path crosses to Windows. |
Use the variable GitHub provides for this: ${{ runner.temp }}/EoSim in both new jobs. Correct on all three runners and one token longer. |
| 6 | Low | eosim-sanity.yml:114, 130 |
pip install -e installs the clone editable. Nothing here needs editability, and an editable install is further from what a developer does than a plain one — which matters in a workflow whose purpose is to prove the install works. It also makes the dist metadata read in finding 3 depend on the clone staying in place. |
pip install /tmp/EoSim (no -e). Matches the three existing clone jobs only if they are changed too; if consistency with them is preferred, leave it and change all five in one follow-up. |
| 7 | Low | tests/CMakeLists.txt:93-97 |
Carries #127's test_crypto_ed25519_loworder registration. Declared up front in the body ("Stacked on #127 … Review the second commit"), which is the right way to do it, but it is a fifth PR touching that anchor alongside #127, #125, #118 and #126. |
Merge #127 first, then rebase this and drop the hunk. No change needed now. |
Credit where due: the body separates the two failures by cause, states that a version bump alone could never have fixed the wheel legs, and records the version oddity instead of burying it. Findings 1 and 3 extend that note rather than contradicting it.
Architecture conformance
Conforms as a change; surfaces a real deviation elsewhere.
- §21 Tier 1 — Foundation is
eos, eBoot, ebuild, EoSim. Both files are CI configuration ineos; nothing points up a tier, no runtime dependency is added, and eBuild is not bypassed. The diff itself is clean under §5.1. - §17, EoSim as an adoption primitive — this is where the deviation is. §17's target is "first simulated application in minutes, not hours" and its flow is
Discover -> Install -> ebuild run --sim. With no wheel, no sdist and no PyPI entry, theInstallstep in that flow does not exist for anyone outside the org; a shallow git clone at a tag is the only route, and it is the route this PR standardises on. Finding 2. - §10, "repositories should not be the dependency API", and
.ai/platform.md's first non-negotiable — "a consumer depends on a component with a version range, never on a git URL". eos's simulation CI depends on EoSim by git URL at a mutable tag. That is a consequence of §11's Registry not yet carrying EoSim rather than a choice made here, but this PR is the place it becomes the settled arrangement in five jobs. - §23.1/§23.2 and
.github/STANDARDS.md"Tag scheme" — "All canonical product repos tag releases asvMAJOR.MINOR.PATCH(SemVer)". EoSim satisfies the syntax and violates the intent:v1.5.0tags a commit dated afterv3.0.1's, calling itselfv1.4.0, declaring3.0.1. §23.2's compatibility contract governs formats but says nothing about a tag matching the artifact it tags, which is the gap that let this through — see the proposals appended for 2026-09. - Weakened checks — finding 2 is the honest reading of this diff under
.ai/reviewer.md. Nothing is disabled and no assertion is deleted, but the last two jobs that referenced a published artifact stop doing so, and the check that replaces them cannot fail for the reason the old one did. Recorded as High rather than rounded down, with the caveat that the pre-existing state (a permanent 404) was not a working check either.
Proposed changes
- Change the pin to a commit SHA with a comment recording which tag it was and when (finding 1). One line per file, no behaviour change beyond becoming honest.
workflow_dispatchboth workflows on this branch and link the runs in the body (finding 4). This is the only pre-merge evidence obtainable and it also proves finding 1's replacement pin.${{ runner.temp }}/EoSimin the two new jobs (finding 5).- Add the
importlib.metadataassertion to the two new jobs (finding 3). - Add to the body: the published-wheel path is now untested across the org, and no publishable artifact exists (finding 2).
- Then merge, after #127, dropping the
tests/CMakeLists.txthunk (finding 7). - Open in EoSim: publish a wheel + sdist per release (finding 2); single-source
__version__and stop hardcoding2.0.0incli/main.py:49andapi/server.py:29(finding 3); reconcile or delete thev1.5.0tag (finding 1).
I opened no fix PR, deliberately, despite two High findings. Finding 1's fix is a one-line change to EOSIM_VERSION — in the exact file and line this open PR exists to change, so a fix PR would collide with it head-on, and the brief forbids touching a branch belonging to an existing PR. Finding 2 is not fixable in eos at all; it needs EoSim to publish something. Finding 3 is EoSim's code.
Not checked
- The workflows were never executed. No
workflow_dispatchrun, no act, no local GitHub Actions runner. I verified the YAML's inputs — tags, releases, PyPI, install — not the jobs. - The Windows and macOS legs specifically. Finding 5 is reasoned from GitHub's documented default shell for
windows-latest(pwsh) and drive-relative path resolution; I have no Windows or macOS host and did not observe either job. I cannot say the current/tmp/EoSimfails — my claim is only that it is accidental. - Installs were done with
uv pip install -einto auvvenv, notpip(python3 -m venvis unusable on this host — noensurepip). Both tags returned rc=0 and a workingeosim --version. The workflows usepip; I did not reproduce their exact installer. eosim list,eosim doctorandeosim simulatewere not run for the tags — only--version. The author reportsdoctorrc=0; unverified here.simulation-test.yml's 11-platform matrix and thenested-simulation/nested-guest-installjobs were read for their install lines only. Whether they pass once the clone succeeds is unknown, and is the more likely next failure — a tag that exists is necessary, not sufficient.- I did not diff
v1.5.0againstv3.0.1beyond a file-leveldiff -rq, which showed substantially different trees (android/, coverage files present only inv1.5.0). Which tag is functionally better for eos is unexamined; finding 1 recommends a SHA precisely because I cannot answer that. - EoSim was skipped by this run's sync as a dirty working tree (179 files), so I read it only from fresh clones in
/tmpand from the GitHub API — never from the local checkout. - Whether
Simulation Test/EoSim Sanityare required checks for merge was not determined;mergeStateStatusisBLOCKEDand I did not establish what blocks it.
Automated architecture review of 57e5f38333dd — scheduled, model claude-opus-5, checked against the EmbeddedOS Master Design v2.0. Advisory only: this reviewer never approves, requests changes, or merges. Reply here to discuss or push back — a wrong finding is a bug worth reporting.
…he workflow test itself Answers the review on embeddedos-org#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 embeddedos-org#127; the tests/CMakeLists.txt hunk goes when that lands. Refs embeddedos-org#129
55066f6 to
731331d
Compare
Addressed — and one thing this PR now makes visible rather than fixesThank you, this was a good catch on the pin. I re-verified every remote claim independently rather than taking the table on trust, and it all reproduces:
Finding 1 — pinned by SHA in both files, with that table and the check date in the env block. Not Finding 3 — you are right that this is why my own note read as a curiosity rather than a failed pin. Finding 4 — I could not get a Finding 2 — recorded, not fixed, and I want to be plain about it. After this merges, nothing in the org exercises a published EoSim artifact, because none exists. Converting to a clone is the right immediate move, but it also turns a permanent red into a green that checks nothing — which is the shape So there is now a Findings 5 and 6 done ( One thing beyond the findings. While in this file: Still open, in EoSim, not here
I have not opened those, since I do not have write access to EoSim — happy to if that is useful, or they may be better raised by a maintainer. |
|
Diagnosis of the 25 red checks on First, the Cause 1 — inherited from eBoot, not this PR
eBoot master does not compile. #87 pinned Cause 2 — real, and mine to have missed
eBoot already solved this in #81, and the step is load-bearing rather than incidental: SITE_PACKAGES=$(python -c 'import sysconfig; print(sysconfig.get_paths()["purelib"])')
cp -r /tmp/eosim-data/platforms "$SITE_PACKAGES/"— Worth knowing while you are in here: I am not pushing to this branch since you are working it — flagging so the |
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 embeddedos-org#129
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 embeddedos-org#129
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 embeddedos-org#129
Finding 4, answered — both workflows are green, for the first time in their historyYou said the only pre-merge proof available was to actually run these. It also did what you predicted a run would do: a tag that exists was necessary and not sufficient. Four runs, three more blockers, none of them visible by reading.
Blocker 2 — Blocker 3 — bash steps under pwsh. The runs
Still red, and not this PR
Not runThe 11-platform matrix in |
srpatcha
left a comment
There was a problem hiding this comment.
Review — eos#129 "ci: finish the EoSim fix — a tag that exists, and no wheel anywhere"
head: 731331d author: Kartikey1306 ci: fail (22 red, all one cause introduced here)
Verdict: The install half is fixed and now proven by CI rather than asserted — all nine Install & Validate legs, Windows Sanity and macOS Sanity are green for the first time in this workflow's history, and the SHA pin plus the dist-metadata assertion are the right answers to findings 1 and 3. The pull_request: paths: trigger did what it was meant to do: it ran the workflows and showed what the previous round could not see. What it shows is that dropping -e broke eosim run on every platform — 22 jobs across the two workflows now fail with the same traceback.
That change was made on my predecessor's finding 6, which asked for a plain install. That finding was wrong and I am withdrawing it. The reasoning ("an editable install is further from what a developer does") was right about intent and wrong about this package: editable is currently the only mode in which EoSim works at all.
Findings
| # | Severity | File:line | Finding | Recommended fix |
|---|---|---|---|---|
| 1 | High | .github/workflows/eosim-sanity.yml:122,152,177,204,232; simulation-test.yml:53,84 |
pip install <path> instead of pip install -e <path> makes eosim run impossible. EoSim's eosim/cli/main.py:14 computes EOSIM_ROOT = Path(__file__).parent.parent.parent and PLATFORMS_DIR = EOSIM_ROOT / "platforms". platforms/ is a top-level directory of 199 files, not part of the eosim package; pyproject.toml has [tool.setuptools.packages.find] include = ["eosim*"] and there is no MANIFEST.in at the pinned commit, so a non-editable install ships none of it and EOSIM_ROOT resolves to site-packages. Measured on the pinned tree: editable layout → EOSIM_ROOT=<clone>, platforms/ present with 150 entries; installed layout → EOSIM_ROOT=<site-packages>, platforms/ absent. That is exactly the CI failure, on all 22 jobs: FileNotFoundError: [Errno 2] No such file or directory: '/opt/hostedtoolcache/Python/3.12.14/x64/lib/python3.12/site-packages/platforms' at _find_platform → PLATFORMS_DIR.iterdir(). Seven Nested Simulation, three Guest OS Install, EoSim Sanity Gate, eleven Simulate (…) and Simulation Gate. |
Restore pip install -e at all five sites, with a comment saying why editable is load-bearing — otherwise the next reviewer removes it again for the same good-sounding reason I did. Then raise the real defect against EoSim: platforms/ must move under eosim/ (or be declared as package data with a MANIFEST.in + include-package-data), and PLATFORMS_DIR must resolve through importlib.resources, not __file__.parent.parent.parent. Until that lands, a plain pip install of EoSim cannot work for anyone. |
| 2 | High | .github/workflows/eosim-sanity.yml:143 ("Validate all platform configs": eosim list && eosim doctor); simulation-test.yml:56 ("Validate platform": eosim list) |
The validation step passes on an installation with zero platforms. Run directly against the pinned code in a non-editable layout: eosim list prints Available platforms (0): and an empty table, and returns 0. eosim doctor likewise — both are in an && chain and the job is green. That is why nine Install & Validate legs are green while every job that actually simulates is red, and why the PR body can record eosim doctor -> rc 0 as evidence of a working install. eosim/cli/main.py:45 builds PlatformRegistry(str(PLATFORMS_DIR)) over a directory that does not exist and reports success. A step named "Validate all platform configs" that validates none and exits 0 is the fail-open shape .ai/security.md puts first — a verification that could not run must fail, not pass. This one is inside this PR's own job, and it is what let a broken install look validated. |
Assert non-emptiness, not exit status. eosim list --format json and check the array is non-empty, or at minimum eosim list | grep -q x86_64-linux. This should fail on the current head — which is the point: it is the check that would have caught finding 1 in the same job rather than three jobs later. |
| 3 | Medium | PR body, "Verified against the real remote" | The body's evidence is for a version of this change that no longer exists. It records clone v1.5.0 + pip install -e -> rc 0 and eosim doctor -> rc 0; the head pins a SHA and installs without -e. It also still says "Bumped to 1.5.0" and quotes EOSIM_VERSION, while both files now carry EOSIM_COMMIT. The 2026-09-03T17:17 comment supersedes parts of it, but the body is what a reviewer reads first, and here the one token it differs by — -e — is the whole of finding 1. Separately, eosim doctor -> rc 0 is not evidence of anything given finding 2. |
Rewrite the body against the current head, and cite the CI runs now that both workflows execute on the PR — that is far stronger than a local table. |
| 4 | Medium | .github/workflows/eosim-sanity.yml:76-101, :238 |
published-artifact-watch can fail the workflow, and the gate with it. The comment at :70 and the PR comment both say it "cannot fail the workflow, deliberately". It is in sanity-gate's needs: (:238), and the gate fails on any non-success result. The job body runs set -euo pipefail and then pypi=$(curl -s -o /dev/null -w '%{http_code}' https://pypi.org/pypi/eosim/json) — curl without -f still exits 7 on a connection or DNS failure, and a failing command substitution in an assignment terminates the script under set -e. gh api … --paginate over every release is the same shape. So an outage at pypi.org, or a token hiccup, turns a tracking signal into a red gate on a workflow this PR is trying to make requirable. |
Give the two probes an explicit floor: pypi=$(curl -s -o /dev/null -w '%{http_code}' … || echo 000) and assets=$(gh api … || echo ""), and end the step with exit 0. If it genuinely must never gate, take it out of sanity-gate's needs: as well — with the floor in place it always succeeds, so its presence there is decoration that can only ever hurt. |
| 5 | Low | .github/workflows/simulation-test.yml:20-24 |
The comment above EOSIM_COMMIT in this file still describes a tag pin — "v1.5.0 is the release marked Latest, and is what eosim-sanity.yml already pins" — beside a variable that holds a SHA, and it omits the reason the SHA exists. The full rationale (the tag/commit/pyproject/__init__ table, and "v1.5.0 is a label rather than a version") lives only in eosim-sanity.yml:26-46. A reader of this file is told the pin follows a tag that is Latest, which is the belief the SHA was introduced to prevent. |
Point at the other file explicitly — "pinned by commit; see the table in eosim-sanity.yml for why the tag namespace cannot be trusted" — or repeat the two-line summary. Two files, one fact, and only one of them currently states it correctly. |
Carried from the previous round and unchanged: tests/CMakeLists.txt still carries #127's test_crypto_ed25519_loworder registration, declared in the body as a stack. Fine as-is; drop it when #127 lands.
What I verified rather than took on trust
-
Finding 1's root cause, measured on the pinned tree (
git archive 7dec3460 eosim platforms):editable EOSIM_ROOT = /tmp/ar129 platforms/ exists, 150 entries installed EOSIM_ROOT = /tmp/ar129/sp platforms/ absentand
git ls-tree -r 7dec3460 -- platforms | wc -l→ 199 files, with noMANIFEST.inandinclude = ["eosim*"]inpyproject.toml. So this is structural, not a runner accident. -
Finding 2, executed. With only the
eosimpackage onPYTHONPATH(the installed layout),eosim listprintedAvailable platforms (0):and returned 0. -
The CI picture, from the check-runs API rather than the bundle's snapshot — the bundle was cut mid-flight and shows several jobs as
pendingthat have since finished:success Install & Validate (3 OS × 3 Python, all 9) success Windows Sanity, macOS Sanity success EoSim publishes no installable artifact (tracking) failure Nested Simulation (all 7 platforms) failure Guest OS Install (all 3 guests) failure Simulate (all 11 platforms) failure EoSim Sanity Gate, Simulation GateEvery failure carries the identical traceback.
Full-stack integration summaryandCross-compile ARM64 kernelare the usual inherited eBoot-master breakage; theCross-compile ARM Cortex-M4pair ofsuccess+cancelledis a superseded run, not a failure. -
This is not a pre-existing red being surfaced.
masterinstalls with-eat every clone site (eosim-sanity.yml:39,70,98;simulation-test.yml:45,74). The scheduled runs never reached the simulate jobs — the last five nightlyEoSim Sanityruns all failed atInstall & Validateand skipped everything downstream — soeosim runhas never been observed green here. But under-eit resolvesplatforms/correctly by construction, and under a plain install it cannot. The change from one to the other is in this diff. -
The install half genuinely works. Nine green
Install & Validatelegs across ubuntu/windows/macos × Python 3.10/3.11/3.12 mean the--filter=blob:noneclone pluscheckout --detach <sha>pluspip installall succeed, and thatEOSIM_EXPECTED_DIST: "3.0.1"is the value pip actually resolves at that commit — the assertion added for finding 3 passes rather than passing vacuously.${{ runner.temp }}(finding 5 last round) is correct on the Windows leg. That is real progress and I do not want it lost in the above. -
The
sanity-gaterewrite is right. IteratingtoJSON(needs)rather than testinginstall-validatealone is what makes the gate red here instead of printing "All EoSim sanity checks passed" over ten failed jobs — which is precisely whatmaster's version would have done on this run. Worth stating plainly: the old gate would have called this green.
Architecture conformance
§17, EoSim as an adoption primitive — this PR is where the section stops being satisfiable. §17 says a user "must be able to experience EmbeddedOS before purchasing or configuring hardware" and sets "Discover → Install → ebuild run --sim", target "first simulated application in minutes". §39's success state requires an independent developer to "install the SDK, run EoS in simulation". Findings 1 and 2 together say: EoSim has no published artifact (13 releases, zero wheels, zero sdists, not on PyPI — verified last round and by this PR's own tracking job), and the artifact it would publish would not work, because platforms/ is not packaged. The Install step of §17 does not exist today by either route except git clone plus an editable install, which is a developer-mode workflow, not an install.
§10 / .ai/platform.md, "repositories are not the dependency API" — this is the concrete instance. eos now depends on EoSim by git URL and commit SHA, in two workflow files. The SHA pin is the correct thing to do given that a git URL is the only route (it is honest and immutable, unlike the tag), but it is the platform-services non-negotiable being violated because nothing better exists. .ai/platform.md is explicit: "A consumer depends on a component with a version range, never on a git URL or a path into another repo's tree."
§28, status and evidence — finding 2 is a §28 violation in miniature: eosim doctor -> rc 0 was recorded as evidence of a working install, and the check that produced it could not have failed.
§21 / §5.1 — not engaged. Workflow files plus the stacked tests/CMakeLists.txt hunk; nothing crosses a tier.
Weakened checks — none added, and two removed: the fail-open sanity-gate and, per the previous round, the eosim --version constant. Finding 2 is a fail-open check this PR inherits and relies on, not one it introduces.
I have appended an architecture proposal covering §17's missing Install step — see .ai/autoreview/proposals/2026-09.md.
Proposed changes
- Finding 1 first, and it is a revert:
pip install -eat all five sites. Everything else here is secondary to the fact that the workflows are 22-jobs red and this is the whole reason. - Finding 2 in the same push, because otherwise the next round of this has the same blind spot: make the validate step assert a non-empty platform list. Expect it to be red until step 1 lands — that is the correct sequence, test first.
- Finding 4 — two
||floors and anexit 0. - Findings 3 and 5 — text.
- Then re-run. Both workflows now execute on the PR, so for the first time the claim "this fixes EoSim CI" can be settled by the checks rather than by a local table.
- Open the EoSim issue named in finding 1. It is the one that actually matters: until
platforms/is packaged and located throughimportlib.resources, no wheel EoSim ever publishes will work, so the packaging debt the tracking job watches for is larger than "publish a wheel".
I opened no fix PR. Finding 1 is High and the fix is one word at five sites, but the sites are inside workflow files this PR rewrites — a branch cut from master would conflict with it, and the brief forbids touching a branch belonging to an open PR. Finding 2's fix has to land together with it, and I cannot run either through Actions from here.
Not checked
- I did not install EoSim with pip in either mode.
piphas no network in this sandbox. Finding 1 rests on (a) the 22 CI failures with thesite-packages/platformspath in the traceback, which is direct evidence for the non-editable half, and (b) the path arithmetic ofPath(__file__).parent.parent.parentmeasured against the two real directory layouts, which is deterministic — but I did not observe an editable install succeed ateosim run. eosim doctorwas not run in the empty-platforms layout. It is in an&&chain aftereosim listin a job that CI reports green, so it exits 0 there; I confirmedeosim listdirectly and inferreddoctorfrom the job result.- Whether
platforms/undereosim/is the right fix for EoSim is a recommendation, not a verified design. EoSim was not built or tested; I readpyproject.tomlandeosim/cli/main.pyat7dec3460only. The local EoSim checkout is dirty and was left untouched. - Finding 4 was reasoned, not reproduced.
set -eterminating on a failed command substitution is standard bash behaviour andcurlwithout-fexits 7 on a connection failure, but I did not force a network failure in Actions to watch the gate go red. simulation-test.yml'sCross-PlatformandSimulation Gatejobs are pre-existing and unmodified by this PR; the threeCross-Platformlegs are green. I did not read their bodies.- The 11 vs 12
Simulate (…)legs: the bundle'schecks.txtlists twelve and the check-runs API eleven distinct names for the completed run. I did not chase the discrepancy; every one of them fails identically, so it does not affect any finding.
Automated architecture review of 731331d4591e — scheduled, model claude-opus-5, checked against the EmbeddedOS Master Design v2.0. Advisory only: this reviewer never approves, requests changes, or merges. Reply here to discuss or push back — a wrong finding is a bug worth reporting.
srpatcha
left a comment
There was a problem hiding this comment.
Review — eos#129 "ci: finish the EoSim fix — a tag that exists, and no wheel anywhere"
head: 71e038d author: Kartikey1306 ci: fail (2 of 40 — both cross-repo; all 17 EoSim jobs pass)
Verdict: The fix works and the process behind it was the right one — the author added a pull_request trigger so the workflows would actually execute, ran them four times, and found three blockers that were not visible by reading, including noticing that the -e flag they were asked to drop had been masking the platforms/ packaging bug. All 17 EoSim jobs pass on this head, for the first time in these workflows' history. Two things need attention: the new sanity-gate hand-rolls the same gate rule eos#121 extracts into a shared script specifically so it would not be duplicated, and the hand-rolled copy is weaker; and adding a pull_request trigger to two workflows breaks a test eos#121 introduces.
Findings
| # | Severity | File:line | Finding | Recommended fix |
|---|---|---|---|---|
| 1 | Medium | .github/workflows/eosim-sanity.yml:355-369 | sanity-gate re-implements eos#121's gate rule inline, and the copy fails open on empty input. The jq expression is character-for-character #121's to_entries[] | select(.value.result != "success"), but the inline version omits both the set -euo pipefail and the empty/null guard that .github/scripts/ci-gate-check.sh has. Executed side-by-side comparison:input #129 inline #121 scriptSo an empty RESULTS prints "All EoSim sanity jobs succeeded." and exits 0 here, where #121's script refuses. null produces jq: error (at <stdin>:0): null (null) has no keys rather than a diagnosis. (The {}/[] hole is shared and I have reported it separately on eos#121 — {} is what toJSON(needs) renders for a job with no needs:.) The comment on :348-354 is right that this form "cannot fall out of step with needs:", which was the real defect; the issue is that it is now the second implementation of the rule, and #121's test_every_gate_invokes_the_shared_script only covers the six workflows in its registry, so nothing holds this copy to the same behaviour. |
Once #121 lands, replace the inline body with the shared script — it is a one-line run: plus a checkout step and a GATE_NAME, exactly as #121's three gates do:- uses: actions/checkout@v4- env: {RESULTS: '${{ toJSON(needs) }}', GATE_NAME: "EoSim Sanity Gate"} run: printf '%s' "$RESULTS" | .github/scripts/ci-gate-check.shIf #129 lands first, add set -euo pipefail and the empty/null guard to the inline body now, and open a follow-up to converge. Either way the two must not stay divergent. |
| 2 | Medium | .github/workflows/eosim-sanity.yml:5-8, .github/workflows/simulation-test.yml:7-11 | Merge interaction with eos#121 that neither PR mentions: test_every_pull_request_workflow_is_classified will fail once both land. #121's test globs .github/workflows/*.yml, keeps those with a pull_request trigger, and asserts every one appears in REQUIRED_CHECKS or NOT_REQUIRED. Its registry (verified by reading tests/unit/test_ci_gate.py at #121's head) covers ci.yml, build.yml, eos-simulation.yml, codeql.yml, python-tests.yml, third-party.yml, auto-assign.yml, claude-code-review.yml and book-build.yml — grep for eosim or simulation-test in that file returns nothing. This PR gives both eosim-sanity.yml and simulation-test.yml a pull_request trigger, so after both merge each is an unclassified pull-request workflow and that test fails. |
Add both to NOT_REQUIRED with a paths-filter reason, which is the same shape #121 already uses for book-build.yml ("its pull_request trigger is filtered to paths: …"). Because both new triggers do carry paths:, they also satisfy #121's test_workflows_recorded_as_not_requirable_really_are_not, so the fix is two dictionary entries and nothing else. Coordinate with #121 on which PR carries them. |
| 3 | Low | .github/workflows/eosim-sanity.yml (5 sites), .github/workflows/simulation-test.yml:51-66, :93-108 | The five-line install recipe is copy-pasted at seven sites, and has already needed three rounds of per-site fixes. The identical git clone --filter=blob:none / checkout --detach / pip install / derive SITE_PACKAGES / cp -r platforms block appears at every install point. The PR history shows the cost directly: -e had to be dropped "at all five sites, not two", ${{ runner.temp }} at all of them, and shell: bash at several — each a separate round because each site is a separate edit. The python -c "import importlib.metadata …" version assertion is likewise triplicated. |
Extract a composite action, .github/actions/install-eosim/action.yml, taking commit and expected-dist as inputs, and replace all seven sites with - uses: ./.github/actions/install-eosim. That also gives the platforms/ workaround and the version assertion one place to be deleted from when EoSim fixes its packaging. |
| 4 | Low | .github/workflows/eosim-sanity.yml:73-99 (with :344) | published-artifact-watch cannot fail, and it is in sanity-gate's needs. Both branches exit 0 — ::notice:: if an artifact appears, ::warning:: if not — so it contributes no signal to an all-must-succeed gate while occupying a slot in it. It also surfaces as a passing check literally named EoSim publishes no installable artifact (tracking) (confirmed in checks.txt: pass), i.e. a green tick whose name asserts a deficiency. I agree with the author's reasoning that eos must not gate its merges on another repository's release process, and the debt is real and worth tracking — this is about where it sits, not whether it should exist. |
Drop it from sanity-gate's needs — it is a notice, not a check — and rename it so the check name is not a claim, e.g. EoSim artifact publication (informational). Consider moving it to the scheduled trigger only, so it does not add a permanently-green status to every pull request that touches these files. |
| 5 | Low | .github/workflows/eosim-sanity.yml (5 sites), simulation-test.yml:63, :105 | SITE_PACKAGES is derived as os.path.dirname(os.path.dirname(eosim.__file__)), which depends on import resolution finding the installed package rather than a same-named directory on sys.path. eBoot's copy of this workflow — which the author cites as where this fix came from — uses python -c 'import sysconfig; print(sysconfig.get_paths()["purelib"])', which needs no import and cannot be shadowed. Two repositories now solve the same problem two different ways. It works today because the clone goes to ${{ runner.temp }} and the step's cwd is the eos checkout, so nothing shadows it. |
Match eBoot: SITE_PACKAGES=$(python -c 'import sysconfig; print(sysconfig.get_paths()["purelib"])'). Fold into finding 3's composite action so it is stated once. |
| 6 | Low | .github/workflows/simulation-test.yml:20-24 | The comment on EOSIM_COMMIT still argues the superseded rationale — "v1.5.0 is the release marked Latest, and is what eosim-sanity.yml already pins" — after the pin became a SHA. A reader now sees a SHA explained by an argument about which tag to choose. The # v1.5.0 as of 2026-09-03 trailing comment carries the useful part. |
Rewrite to state why a SHA rather than a tag: EoSim's v1.5.0 tag reports dist version 3.0.1 and its v3.0.1 tag reports __init__ 2.0.0, so neither tag name is a reliable identifier and the commit is pinned instead. That is the actual reason and it is more useful than the tag-selection history. |
| 7 | Low | (CI) | 2 of 40 checks fail, neither from this diff: Cross-compile ARM64 kernel fails building eBoot (eBoot/include/eos_image.h:135:23: error: 'eos_image_header_t' has no member named 'reserved'), and Full-stack integration summary as its dependent. Confirmed pre-existing — same failure on origin/master at feee2726 and on eos#121, #126 and #134. The body names eBoot#94 as the repair while a later comment on this PR names eBoot#97 as superseding that diagnosis; the two attributions disagree within the same PR. |
Nothing beyond reconciling the eBoot PR number. I did not look at eBoot. |
Architecture conformance
Conforms. Master design §21 places CI templates under Infrastructure, and EoSim in Tier 1 — Foundation alongside eos, eBoot and ebuild. This diff touches only two workflow files plus the shared tests/CMakeLists.txt hunk; no runtime code, no import, no link line, no manifest entry, so §5.1's dependency direction is untouched and §21.1 is not in play.
It serves §17 directly, which is the section that matters here: "EoSim should be treated as essential to developer onboarding and CI, because a user must be able to experience EmbeddedOS before purchasing or configuring hardware", and "CI tests sharing the same application artifacts used on real hardware". The author's framing of the open debt is exactly right against that section — with no wheel and no PyPI presence, §17's Discover → Install → ebuild run --sim has no Install step for anyone outside the org, and every job in the org installs from a git clone. That gap is already recorded as a proposal ("§17 makes Install an adoption step and nothing requires a tool to be installable" in .ai/autoreview/proposals/2026-09.md), so I am not appending a duplicate.
Checks were strengthened, not weakened. sanity-gate moves from testing install-validate alone while printing "All EoSim sanity checks passed" to iterating every dependency — that was a genuine fail-open gate and the fix is correct in substance (finding 1 is about the implementation being duplicated, not about the direction). pip install -e becomes a plain install at every site, which is stricter and, as the author observed, is what exposed the platforms/ bug that the editable install had been accidentally papering over. EOSIM_EXPECTED_DIST adds an assertion where there was none.
The tests/CMakeLists.txt hunk is disclosed here, unlike on eos#126: the body opens with "Stacked on #127 so this branch is not red for the unrelated test_crypto_ed25519_loworder reason. Review the second commit." That is the right way to carry it. It is still the same block #118, #119, #121, #126 and #127 all add at the same anchor, so the merge-order note applies.
Two process points worth recording as correct, because they are the reason this PR found real problems: the author noticed that before the pull_request trigger existed their own earlier commits never ran the workflow they were editing, so the green checks on those commits were other workflows — "a check that does not run is not a check", one level up. And they identified that -e was masking the platforms/ dependency rather than treating its removal as unrelated churn. Both are the kind of thing that only surfaces by running the thing.
Proposed changes
In order:
- Finding 2 first — two entries in #121's
NOT_REQUIRED, decided jointly with that PR. Cheapest and prevents a brokenmaster. - Finding 1 — converge on the shared script, or add the missing guards now if this lands first.
- Finding 3 + 5 together — one composite action, which is also where finding 5's
sysconfigchange belongs:
# .github/actions/install-eosim/action.yml
inputs:
commit: { required: true }
expected-dist: { required: true }
runs:
using: composite
steps:
- shell: bash
run: |
set -euo pipefail
git clone --filter=blob:none https://github.com/embeddedos-org/EoSim.git "${{ runner.temp }}/EoSim"
git -C "${{ runner.temp }}/EoSim" checkout --detach "${{ inputs.commit }}"
pip install "${{ runner.temp }}/EoSim"
# EoSim ships only eosim/platforms/__init__.py but resolves
# PLATFORMS_DIR to <site-packages>/platforms. Delete when fixed upstream.
SITE_PACKAGES=$(python -c 'import sysconfig; print(sysconfig.get_paths()["purelib"])')
cp -r "${{ runner.temp }}/EoSim/platforms" "$SITE_PACKAGES/"
python - <<'PY'
import importlib.metadata as m, os, sys
got, want = m.version("eosim"), "${{ inputs.expected-dist }}"
print(f"eosim dist version: {got}")
sys.exit(0 if got == want else f"expected {want}, got {got}")
PY- Findings 4 and 6 — cosmetic but cheap.
On the three EoSim issues the author says they cannot file (publish a wheel/sdist, single-source __version__, reconcile or delete the v1.5.0 tag): all three are well-evidenced and worth filing. They are EoSim's to fix, and a maintainer with write access should open them; the diagnosis in this PR is enough to write them from.
Not checked
tests/unit/test_ci_gate.py— I could not execute it. Itimport yamlandpyyamlis not installed in my environment. Finding 2 is therefore Observed, not Verified by execution: I read #121'sREQUIRED_CHECKS/NOT_REQUIREDliterals and confirmed bygrepthat neither workflow appears in that file, and I read the newpull_request:triggers in this diff. I did not run the combined tree and watch the test fail. The reasoning depends only on the registry contents and the trigger presence, both of which are plain text, but I have not seen the red.- I did not run either workflow. Per the run's rules I pushed nothing. The four runs and the two green run URLs in the comments are the author's evidence, and
checks.txton this head independently corroborates the outcome: all 17 EoSim jobs pass —EoSim Sanity Gate,EoSim publishes no installable artifact (tracking), 3 ×Guest OS Install, 6 ×Nested Simulation, 3 ×Cross-Platform, 3 ×Simulate. - YAML validity I did not verify — no
pyyaml, so the body's "both files still parse as YAML" is unconfirmed by me. That the jobs ran on CI is strong indirect evidence. - Every remote claim — no
v0.1.0tag, 13 releases with no wheel or sdist,pypi.org/pypi/eosim/json→ 404,refs/tags/v1.5.0→ commit7dec3460, and the version table (v1.5.0→ pyproject 3.0.1,v3.0.1→__init__2.0.0) — I did not re-verify against the EoSim remote. I read the author's commands and they are the right commands, but the values are theirs, not mine. The SHA pin7dec3460cba76083b6645eb4a867e39aa2af97dfin particular I did not confirm corresponds tov1.5.0. - Windows and macOS behaviour. Findings about
shell: bashandpwshI take from the author's run logs; my host is Linux and I ran nothing on those platforms. - Whether
eosim runproduces correct output rather than merely exiting 0 — the author flags this explicitly under "Not run" and it remains open. Also their note thateosim listprints(0)whilediscover_platforms()returns 149 on the same directory, and exits 0: if accurate that meansValidate platform/Validate all platformsare steps that cannot fail. I did not verify it, but it would make three of these jobs' validation steps vacuous and is worth checking before treating these greens as evidence about simulation correctness. - What I did run: the side-by-side probe in finding 1 — the inline
sanity-gatebody extracted verbatim into a script and invoked underbash -eagainst five inputs, compared against.github/scripts/ci-gate-check.shfrom eos#121's head, withjqpresent. Nothing else in this PR is locally executable.
Automated architecture review of 71e038d8c7d0 — scheduled, model claude-opus-5, checked against the EmbeddedOS Master Design v2.0. Advisory only: this reviewer never approves, requests changes, or merges. Reply here to discuss or push back — a wrong finding is a bug worth reporting.
…atch that stops posing as a check Answers the overnight review on embeddedos-org#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 embeddedos-org#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 embeddedos-org#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 embeddedos-org#121's classification test fails. Posted on both threads with the exact entries; not folded in, because that registry lives on embeddedos-org#121's branch and this PR должен 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 embeddedos-org#129
…atch that stops posing as a check Answers the overnight review on embeddedos-org#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 embeddedos-org#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 embeddedos-org#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 embeddedos-org#121's classification test fails. Posted on both threads with the exact entries; not folded in, because that registry lives on embeddedos-org#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 embeddedos-org#129
344cb54 to
54c9f05
Compare
|
All six findings addressed at the new head — composite Finding 2 is deliberately not folded in, because the registry it changes lives on eos#121's branch and this PR should not carry another PR's hunks. The exact fix, for whichever of the two lands second, is two entries in "eosim-sanity.yml":
"pull_request trigger is filtered to paths: [its own file and "
"simulation-test.yml], so it reports only on changes to itself; "
"requiring it would block every other PR on a status that never arrives",
"simulation-test.yml":
"same paths-filtered self-test trigger as eosim-sanity.yml",Both new triggers carry Also fixed while in the file: |
Every open eos pull request is currently red on `EoS Full-Stack Simulation`, and none of them caused it. The kernel job checks out embeddedos-org/eBoot at `ref: master`, and eBoot master does not compile: include/eos_image.h:135 and :142 static-assert offsetof(eos_image_header_t, reserved) for a member that became tlv_len + tlv_hash, and core/ed25519_verify.c redefines point_is_identity -- two pairs of PRs that each merged clean and broke the build together. eBoot embeddedos-org#94 repairs it and waits on review; until it lands, no change to any eos PR can make this job pass, and after it lands the next broken eBoot master does the same thing again. A floating `ref: master` makes every eos PR's CI depend on the moment-to- moment state of another repository's default branch. Pinning the two compiled-against checkouts makes the job a function of this repository plus two recorded SHAs -- red means the PR broke something, green means it did not, and a cross-repo bump is a reviewable one-line diff with CI on it. EBOOT_COMMIT a172a6d6e1e6 the newest eBoot master ancestor that compiles; found by walking master back and building each candidate. Its child 8a015b2 already carries the ed25519 redefinition. EBUILD_COMMIT e5d8052f3e2c ebuild's current master, frozen as-is. Both of its checkouts feed steps that are commented out (eFab does not exist), so this pin only removes the float. The middleware matrix (10 repos) still floats; it runs those repos' own tests rather than compiling eos against them, and 10 more pins deserve their own decision. When eBoot embeddedos-org#94 merges, bump EBOOT_COMMIT to the merge SHA. Verified before pushing, master vs pin under the job's own configuration (-DEBLDR_BOARD=qemu_arm64 -DEBLDR_VERIFY_STAGE1=OFF -DCMAKE_BUILD_TYPE=Release, cross-compile mode): eBoot@master fails at the eos_image.h asserts, the exact error in the job log; eBoot@a172a6d builds every target, rc=0. Both pinned SHAs verified reachable from their repos' master. Workflow parses; the three `ref:` edits and the env block are the whole diff. This PR's own simulation run is the live test: it uses this branch's workflow and should go green while every master-based PR stays red. Carried onto this branch from embeddedos-org#135 (001128d) so this PR's own simulation job runs against a buildable eBoot: each pull request executes its own copy of the workflow, so the fix greens a PR only once its branch contains it. Byte-identical to embeddedos-org#135's commit -- git drops it as already-upstream the moment embeddedos-org#135 merges. The pin itself is embeddedos-org#135's to review.
srpatcha
left a comment
There was a problem hiding this comment.
Review — eos#129 "ci: finish the EoSim fix — a tag that exists, and no wheel anywhere"
head: d3b5852 author: Kartikey1306 ci: pass (64 check runs, 0 failures, 2 skipped by design)
Verdict: This is the right shape and the evidence behind it is real, not asserted. I pulled the authoritative check-run list for this head: 64 runs, 62 success, 2 skipped — all 11 Simulate (…) legs, all 23 EoSim sanity jobs, both gates, and all three Cross-Platform legs including windows-latest. EoSim artifact publication (informational) is skipped on pull_request, exactly as its if: intends. The composite action removes seven copies of a recipe that had drifted three times, and the two gate rewrites replace bodies that asserted more than they checked. Two things need work: the new empty-input guard misses the value GitHub actually produces, and the comments describing the extracted recipe were left behind at the sites it was extracted from.
Findings
| # | Severity | File:line | Finding | Recommended fix |
|---|---|---|---|---|
| 1 | Medium | .github/workflows/eosim-sanity.yml (sanity-gate), .github/workflows/simulation-test.yml (sanity-gate/"Simulation Gate") |
The guard does not cover the empty case that can actually occur. toJSON(needs) for a job with an empty or deleted needs: renders {} — not "" and not null — so [ -z "${RESULTS:-}" ] || [ "$RESULTS" = "null" ] passes it through, jq 'to_entries[]' over {} emits nothing, bad is empty, and the gate prints "All EoSim sanity jobs succeeded." and exits 0. Deleting one line of needs: restores the exact fail-open behaviour this rewrite replaced, and the comment above it claims the opposite ("Refuse to conclude anything from nothing"). Related: the gate now iterates whatever it is handed, so it cannot notice a job removed from needs: either — which is how the previous body came to test one dependency out of five. |
Guard on the object's size, not its string form: n=$(printf '%s' "$RESULTS" | jq 'length'); if [ "${n:-0}" -eq 0 ]; then echo "::error::gate received no results to check"; exit 1; fi. Then pin the expected count — [ "$n" -eq 5 ] for the sanity gate, -eq 2 for the simulation gate — so a shrinking needs: fails instead of passing quietly. Both gates, and the same two guards belong in #121's ci-gate-check.sh so the inline copies and the script still cannot disagree. |
| 2 | Low | .github/workflows/eosim-sanity.yml (windows-sanity, macos-sanity run: bodies) |
The recipe moved into the composite action; its documentation did not. Both run: bodies still carry ten lines describing the wheel-404 install and the platforms/ copy — neither of which is in those bodies any more — followed by # dist-version assert lives in the install-eosim composite action above a bare eosim --version. The action exists to stop this recipe drifting across seven sites; leaving the recipe's comments at those sites re-creates the same drift in prose, and a reader now finds a platforms/ explanation attached to code that does not copy anything. windows-sanity's copied shell: bash comment also says "this job also runs on windows-latest", which is true of install-validate's three-OS matrix and not of a job whose runs-on: is only windows-latest. |
Delete the moved comments from both bodies, leaving one line pointing at ./.github/actions/install-eosim. Drop the duplicated shell: bash rationale from install-validate's two steps down to one, and reword the windows-sanity copy. |
| 3 | Low | .github/actions/install-eosim/action.yml (clone step, dist-assert step) |
Both run: bodies interpolate ${{ inputs.* }} directly into the script text: git checkout --detach "${{ inputs.commit }}" and want = "${{ inputs.expected-dist }}" inside the <<'PYEOF' heredoc. Template substitution happens before the shell or Python sees the text, so the quoting around it is not protection — a value containing " breaks out of the shell word or the Python string literal. Not a live vulnerability: both call sites pass workflow-level env constants that no PR can influence. It becomes one the first time an input is wired to github.event.*, a fork-controlled matrix value, or anything from an issue body — and this file is deliberately the one place the recipe lives, so it will be called from more places than these seven. |
env: INPUT_COMMIT: ${{ inputs.commit }} on the step, then git checkout --detach "$INPUT_COMMIT"; and env: EXPECTED_DIST: ${{ inputs.expected-dist }} with want = os.environ["EXPECTED_DIST"]. Same cost, and it is GitHub's documented hardening rule for run: bodies. |
| 4 | Low | .github/actions/install-eosim/action.yml ("Stage platform data") |
cp -r "${RUNNER_TEMP}/EoSim/platforms" "$SITE_PACKAGES/" is unguarded under set -euo pipefail, so the day EoSim fixes its packaging and stops shipping a top-level platforms/, every job in both workflows fails with a bare cp: cannot stat. Failing closed is right, but the action's own description calls itself "the one place to delete the platforms/ workaround from when EoSim fixes its packaging" and the failure will not say that. |
Guard on -d and emit ::notice::EoSim no longer ships a top-level platforms/; this step and the note in the env block can be deleted when it is absent, letting the following eosim list/eosim run be the real check. If you would rather keep it fatal, keep it fatal but name both possibilities in the message. |
Architecture conformance
Conforms. §21: both workflows and the new composite action are Tier-1 eos infrastructure. §5.1 applied to CI dependencies as the recorded proposal reads it: eos consuming EoSim (Tier 1) is sibling-tier, so the direction is legal and only the ref was ever wrong. §17 is the section this PR serves — EoSim as an adoption primitive, "CI tests sharing the same application artifacts used on real hardware" — and the honest answer the PR gives is that the published-artifact half of that cannot be tested because no artifact exists; the informational job keeps that visible instead of letting a clone-based green stand in for it. That is the correct call under .ai/reviewer.md (a check that cannot fail is not a check) and it is why the job sits outside needs: and outside pull_request.
Two design gaps this PR sits on are already recorded, so nothing new is filed: the SHA-instead-of-released-ref pin, with the required record and the mandatory non-required drift job, in .ai/autoreview/proposals/2026-09.md (2026-09-04 addendum, triggered by eos#135 — the EOSIM_COMMIT block here is a good instance of the record that addendum asks for, and better than the eBoot one in #127 because it names what it evaluated and why it rejected it); and §17's missing Install step, i.e. that no released EoSim artifact is installable outside the org, in the 2026-09-03 entry "§17 makes Install an adoption step and nothing requires a tool to be installable". Your three EoSim-side items (publish a wheel/sdist, single-source __version__, reconcile or delete v1.5.0) are the concrete work behind that entry and are worth filing as EoSim issues regardless of write access — a maintainer can act on an issue, not on a PR comment in another repo.
Note on scope, not a finding against the diff
This head also carries d3b5852 "ci: pin the eBoot and ebuild checkouts instead of floating on master", which is the entire subject of eos#135 and is also on eos#127 — three open PRs with the same hunk — plus 2a26424, the test_crypto_ed25519_loworder restore that is additionally on #119, #122, #127 and #135. The body's "Stacked on #127 … Review the second commit" is stale: there are eight commits and the second is no longer the interesting one. The pin is correct and I verified it independently in the #127 review, so this is a merge-order problem, not a correctness one — but it does mean whichever of #127/#129/#135 lands second gets a conflict or an empty hunk, and it means a reviewer told to read "the second commit" reads none of the CI work.
Verified in this review
- 64 check runs on this head: 62 success, 2 skipped, 0 failure. The skips are
Create GitHub ReleaseandEoSim artifact publication (informational); the latter is theif: schedule || workflow_dispatchbehaving as designed. This is the first eos PR in this batch where the EoSim workflows report at all, which is the trigger change doing its job. - All 11
Simulate (<platform>)legs,Simulation Gate, all 23 sanity jobs,EoSim Sanity Gate,Windows Sanity,macOS Sanityand all threeCross-Platformlegs are green — so the composite action, thesysconfigpurelib copy andshell: bashwork on ubuntu, macOS and Windows in practice, not just in argument. Cross-compile ARM64 kernelis green here at 06:37:45Z while the same job failed on #119 at 06:35:46Z and on #122 — the difference being the eBoot pin this branch carries. Same-day, six minutes apart.shell: bashinsimulation-test.ymlis on theValidate all platformsrun:step, not on the adjacentuses:step — I checked, becauseshellalongsideusesis a hard workflow-parse error. It is correct.
Not checked
- Nothing was executed locally; no workflow was run from here. Everything above is either read from the files at
d3b5852or read from the GitHub check-run API for that SHA. - The bundle's
checks.txtis incomplete for this PR — 40 rows against the API's 64 — so a reviewer working only from the bundle would not see theSimulate (…)matrix,Simulation Gate,Windows Sanityor the informational job's skip. Flagging because the same undercount would hide a failure on some other PR. - Green means the jobs completed. It does not mean the simulations are correct:
eosim run --headlessexiting 0 andeosim test <platform>exiting 0 are the assertions, and you already recorded thateosim listprints(0)whilediscover_platforms()finds 149 on the same directory — a CLI that miscounts and exits 0 is not evidence about the platform data. Not assessed here. EOSIM_EXPECTED_DIST: "3.0.1"is asserted againstimportlib.metadata, which is the right source, but I did not confirm that7dec3460'spyproject.tomldeclares3.0.1— the EoSim clone under the working root is dirty (179 files) so the sync step left it untouched and I did not read it.- The
paths:-filteredpull_requesttrigger means these two workflows still do not run on an ordinary code change; the nightly cron covers regressions within 24 hours. So 64 green checks here should not be read as "simulation now gates eos". YourNOT_REQUIREDentries cross-posted to #121 are the right follow-through and I did not review #121. - Windows path handling in
cp -r "$SITE_PACKAGES/"(aC:\…path under Git Bash) is load-bearing and is proven only by the greenWindows SanityandInstall & Validate (windows-latest, …)runs on this head. It was not reasoned about, and it is the kind of thing that breaks on a runner-image change.
Automated architecture review of d3b5852e392d — scheduled, model claude-opus-5, checked against the EmbeddedOS Master Design v2.0. Advisory only: this reviewer never approves, requests changes, or merges. Reply here to discuss or push back — a wrong finding is a bug worth reporting.
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.
Simulation TestandEoSim Sanityare both red on master. #106 fixed this class ineosim-sanity.ymlbut did not reach every site — there were two more, and they fail for two different reasons.1.
simulation-test.ymlpins a tag that does not existEoSim has no
v0.1.0tag — its releases run v1.0.0 to v3.0.1 — so every run failed at the clone:Bumped to
1.5.0, the release marked Latest and the oneeosim-sanity.ymlalready pins.2.
eosim-sanity.ymlstill installs a wheel on two legsNo EoSim release ships a wheel at any version, so a version bump alone could never have fixed these two — that is why they survived #106. The other three jobs in the same workflow already install from a clone; the Windows and macOS legs were missed. Converted to the identical clone +
pip install -e.Verified against the real remote, not inferred from the error text
Both files still parse as YAML (3 and 6 jobs respectively).
One oddity worth recording rather than hiding: the
v1.5.0tag reports its version as2.0.0. That is EoSim's own inconsistency, not a mis-pin —v1.5.0is what its releases page marks Latest, and it installs and runs cleanly.