Skip to content

ci: add Metro/Hermes bundle job - #52

Merged
Romulus-Sol merged 2 commits into
devnetfrom
ci-hermes-bundle-check
Aug 3, 2026
Merged

Romulus-Sol merged 2 commits into
devnetfrom
ci-hermes-bundle-check

Conversation

@Romulus-Sol

@Romulus-Sol Romulus-Sol commented Jul 31, 2026 •

Copy link
Copy Markdown
Owner

Why

tsc and node --test both run under Node's module resolver. Metro and Hermes do not.

This repo has shipped three releases where type-checking was green and the bundle was broken on device:

Release Failure
v1.13.0 raw account parser resolved to undefined under Hermes
v1.13.9 stocks classifier import vanished from the bundle
v1.13.16 Bequests picker showed only SOL — enrichment import undefined at runtime

No job in the existing pipeline can observe that class of bug. Every one of those reached a phone.

What it does

Produces the actual Hermes bytecode the APK embeds, then asserts:

  1. exactly one .hbc is emitted;
  2. it carries the Hermes bytecode magic (c61fbc03c103191f) — so Metro silently emitting plain JS fails rather than passing as a "successful export";
  3. app source reached the graph — the program id from release.manifest.json is present in the bytecode, so this is not an RN-runtime-only artifact built from a graph that dropped our code.

Each failure path was verified to trip, with a distinct message, before committing.

Correction made during review (7fb82a3)

The first version of assertion 3 counted occurrences and claimed the bundle proved no module was duplicated in the graph. That was wrong twice over, and the correction is worth recording:

  • grep -c counts matching lines, not matches. In binary bytecode, two occurrences with no newline between them return 1. The negative test meant to catch this only passed because echo inserted a newline — it validated the assumption rather than challenging it.
  • More fundamentally, Hermes interns identical string literals into a shared string table. A duplicated module still contributes one interned string, so no occurrence count taken from bytecode can evidence module-instance uniqueness. Fixing the counting would have yielded a correct number supporting an unsupportable conclusion.

Assertion 3 is now a presence check — what the bytecode can actually attest. The job's value was never in the count.

Module-instance uniqueness needs the source-map module list, which is a separate check and deliberately not approximated here.

What it is not

Not an APK build. No Gradle, no native code, no signing, no artifact published, not a release path. The authoritative release path remains release-tools/build-android-release.sh. Runtime ~90s.

Note on .env

Deliberately absent in CI, and the job needs no secrets. EXPO_PUBLIC_* values do not inline into the bundle — verified: the .hbc content hash is byte-identical with and without .env, which is exactly why src/utils/rpcConfig.ts derives them at runtime.

Follow-up

To make this gate the Phase 4 PR (#51), devnet must be merged into phase4-transactional-heartbeat after this lands — pull_request runs use the workflow from the PR's own branch.

tsc and `node --test` both run under Node's module resolver. Metro and Hermes do
not. Three releases have shipped where type-checking was green and the bundle was
broken on device — v1.13.0 (raw parser), v1.13.9 (stocks classifier) and v1.13.16
(Bequests picker showed only SOL because an enrichment import resolved to
undefined under Hermes). No existing job can see that class of failure.

This job produces the actual Hermes bytecode the APK embeds, then asserts it:

- exactly one .hbc is emitted;
- it carries the Hermes bytecode magic, so a silent fallback to plain JS fails
  rather than passing as a successful export;
- the program id from release.manifest.json appears exactly once, proving app
  source is in the graph and that no module was duplicated by being reached
  through two different specifiers.

All four failure paths were verified to trip before committing.

Not an APK build: no Gradle, no native code, no signing, no artifact published.
The authoritative release path is unchanged.

No .env is provided in CI, deliberately. EXPO_PUBLIC_* values do not inline into
the bundle — the .hbc content hash is byte-identical with and without .env, which
is why rpcConfig derives them at runtime.
@coderabbitai

coderabbitai Bot commented Jul 31, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The CI workflow adds an app-bundle job. The job exports the Android app through Metro and validates the Hermes bundle count, bytecode magic, and presence of the release manifest program ID.

Changes

Android app bundle CI

Layer / File(s) Summary
Bundle export and validation
.github/workflows/ci.yml
The workflow installs mobile dependencies, exports the Android app through Metro, checks for exactly one Hermes bundle, verifies its bytecode magic, and checks for the release manifest program ID.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the added Metro and Hermes bundle CI job.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ci-hermes-bundle-check

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
.github/workflows/ci.yml (1)

99-99: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Do not persist checkout credentials in this job.

This job runs dependency-provided install and export scripts. It has no shown Git operation that requires persisted credentials. Set persist-credentials: false on actions/checkout.

Proposed change
-      - uses: actions/checkout@v7
+      - uses: actions/checkout@v7
+        with:
+          persist-credentials: false
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml at line 99, Update the actions/checkout step in the
CI job to set persist-credentials to false, ensuring checkout credentials are
not retained while leaving the existing checkout behavior unchanged.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/ci.yml:
- Line 132: Update the hits calculation in the CI workflow to count each
fixed-string occurrence of program_id rather than matching lines: use grep’s
only-matching and fixed-string options, then pipe the results to wc -l while
preserving the existing no-match handling.

---

Nitpick comments:
In @.github/workflows/ci.yml:
- Line 99: Update the actions/checkout step in the CI job to set
persist-credentials to false, ensuring checkout credentials are not retained
while leaving the existing checkout behavior unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 14fedd69-757c-46e7-815d-54fda5dc1369

📥 Commits

Reviewing files that changed from the base of the PR and between cd0264b and e42bae1.

📒 Files selected for processing (1)
  • .github/workflows/ci.yml

Comment thread .github/workflows/ci.yml Outdated
# reached by two different specifiers and duplicated into the graph — which for a
# stateful module means two divergent instances at runtime.
program_id=$(node -p "require('../release.manifest.json').programId")
hits=$(grep -a -c -- "$program_id" "$bundle" || true)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

program_id='GXCu5964mvgAJDWmcMriZpzU3vDVqPzjYCM1sxCnsoEb'
bundle="$(mktemp)"
trap 'rm -f "$bundle"' EXIT

printf '%s%s' "$program_id" "$program_id" > "$bundle"

echo "grep -c result (incorrect occurrence count):"
grep -a -c -- "$program_id" "$bundle" || true

echo "grep -oF | wc -l result (correct occurrence count):"
(grep -a -oF -- "$program_id" "$bundle" || true) | wc -l

Repository: Romulus-Sol/DMV

Length of output: 254


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '%s\n' '--- workflow context ---'
sed -n '112,145p' .github/workflows/ci.yml

printf '%s\n' '--- all hits references ---'
rg -n -C 3 'hits=|program_id|bundle' .github/workflows/ci.yml

Repository: Romulus-Sol/DMV

Length of output: 5265


Count program ID occurrences, not matching lines.

grep -c counts matching lines. If one line contains two program ID values, duplicate module content passes the check. Count fixed-string matches with grep -a -oF and wc -l.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml at line 132, Update the hits calculation in the CI
workflow to count each fixed-string occurrence of program_id rather than
matching lines: use grep’s only-matching and fixed-string options, then pipe the
results to wc -l while preserving the existing no-match handling.

The previous assertion claimed the bundle proved no module was duplicated in the
graph. It proved neither half of that.

First, `grep -c` counts matching LINES, not matches. In binary bytecode two
occurrences separated by no newline return 1. The negative test that was supposed
to catch this only passed because `echo` inserted a newline between the two
copies — it validated the assumption instead of challenging it.

Second, and more fundamentally, Hermes interns identical string literals into a
shared string table. A duplicated module still contributes one interned string,
so no occurrence count taken from bytecode can evidence module-instance
uniqueness. Fixing the counting would have produced a correct number supporting
an unsupportable conclusion.

Now a presence check: app source reached the graph, so this is not an
RN-runtime-only artifact. That is what the bytecode can actually attest.

The job's real value is unchanged and was never in the count: it produces the
Hermes bytecode the APK embeds, so a resolution or Hermes-compile regression
fails in CI rather than on a phone.

Module-instance uniqueness needs the source-map module list. Left for a separate
check rather than approximated here.
Romulus-Sol added a commit that referenced this pull request Aug 1, 2026
STATUS.md asserted that the Phase 4 bundle proved agentFundingPolicy is
instantiated once rather than duplicated by its two import forms, and that marker
strings appear exactly once. Both rested on counting string occurrences in the
Hermes bytecode, which cannot evidence either claim: grep -c counts matching lines
rather than matches, and Hermes interns identical string literals into a shared
string table, so a duplicated module still contributes one interned string.

Re-measured against the artifact that can actually answer it — the source-map
module list. 1,528 modules, 116 app source, zero duplicated module paths.
agentFundingPolicy resolves to a single path, so the two import forms do dedupe.
The conclusion was right; the original evidence for it was not.

The bundle line now claims only what bytecode can attest: the graph bundles and
Hermes-compiles, and Metro resolves the explicit .ts specifier the Node test
runner requires. The method note is kept deliberately, so the next person does not
repeat the same inference.

Same correction applied to the CI job in PR #52 (7fb82a3).

Docs only.
@Romulus-Sol
Romulus-Sol merged commit 39cc3ef into devnet Aug 3, 2026
10 checks passed
Romulus-Sol added a commit that referenced this pull request Aug 3, 2026
Brings in the Metro/Hermes bundle CI job (#52, squashed as 39cc3ef) so PR #51 is
actually gated by it — pull_request runs use the workflow from the PR's own
branch, so the job could not appear on #51 until devnet was merged in here.

No application code, test plan or Phase 4 history is altered by this merge.
@Romulus-Sol
Romulus-Sol deleted the ci-hermes-bundle-check branch August 25, 2026 16:32
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.

1 participant