[GH-ISSUE #3349] [Bug] Mobile application graphs classify known directories as bytecode and native libraries #1218

Closed
opened 2026-10-16 21:02:55 +00:00 by bsakaguchi · 1 comment
Owner

Originally created by @coygeek on GitHub (Oct 14, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/3349

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Evidence, artifacts, or reconstruction

Observed behavior

project-harmony-application-graph and project-android-application-graph classify authenticated directory entries as bytecode and native-library components when their names have file-like suffixes. They emit N-API or JNI bridge candidates and runtime hints between those directories. The result preserves format: "directory" and reports complete coverage for these false component relationships.

Every inventory and projection command in the reproduction exited with code 0. The Harmony fixture contains only empty directories. Its graph includes ets/modules.abc as bytecode, libs/arm64-v8a/libentry.so as a native library, an ark runtime hint, and a napi-library-convention bridge. The Android graph includes classes.dex and lib/arm64-v8a/libentry.so with format: "directory", a dalvik-art runtime hint, and a jni-library-convention bridge between them.

Renaming the bytecode and native directories to ordinary directory names removed the component rows, runtime hints, and bridge candidates. These observations concern known directory kinds and do not require validation of opaque DEX, Ark, or ELF bytes.

Reproduction

  1. Start from a clean checkout at 8dde6370d0809ad04cda3342d4e6f290f48aa063 and prepare the runtime:
npm ci
npm run build:cached
  1. From the repository root, create ZIP packages containing the named directories, inventory both packages, wrap each inventory as projection input, and run both public graph commands. The Python blocks create every input file inline:
case_dir=$(mktemp -d)
mkdir "$case_dir/home"
python3 - "$case_dir" <<'PYTHON'
import pathlib, sys, zipfile
root = pathlib.Path(sys.argv[1])
with zipfile.ZipFile(root / "sample.hap", "w") as archive:
    archive.writestr("module.json/", "")
    archive.writestr("ets/modules.abc/", "")
    archive.writestr("libs/arm64-v8a/libentry.so/", "")
with zipfile.ZipFile(root / "sample.apk", "w") as archive:
    archive.writestr("AndroidManifest.xml", "manifest")
    archive.writestr("classes.dex/", "")
    archive.writestr("lib/arm64-v8a/libentry.so/", "")
PYTHON
for family in harmony android; do
  if [ "$family" = harmony ]; then package=sample.hap; else package=sample.apk; fi
  HOME="$case_dir/home" TMPDIR="$case_dir" \
    node scripts/rea.mjs inspect-artifact "$case_dir/$package" \
    --format json > "$case_dir/$family.inventory.json"
  python3 - "$case_dir/$family.inventory.json" "$case_dir/$family.input.json" <<'PYTHON'
import json, sys
with open(sys.argv[2], "w") as output:
    json.dump({"inventory_evidence": [json.load(open(sys.argv[1]))]}, output)
PYTHON
  HOME="$case_dir/home" TMPDIR="$case_dir" \
    node scripts/rea.mjs "project-$family-application-graph" \
    "$case_dir/$family.input.json" --format json > "$case_dir/$family.result.json"
  python3 - "$case_dir/$family.result.json" <<'PYTHON'
import json, sys
result = json.load(open(sys.argv[1]))["normalized_result"]
print(json.dumps({
    "root_format": result["root_format"],
    "managed": [{"path": row["path"], "format": row["format"]}
                for row in result["components"].get("bytecode", result["components"].get("dex", []))],
    "native_libraries": [{"path": row["path"], "format": row["format"]}
                         for row in result["components"]["native_libraries"]],
    "runtime_families": result["runtime_families"],
    "bridge_candidates": result["bridge_candidates"],
    "coverage": result["coverage"]
}, indent=2))
PYTHON
done
  1. Observe the directory formats retained in the managed and native component rows, the associated runtime hints and bridge candidates, and complete coverage. Repeat after renaming the bytecode and native directories to ordinary directory names; the observed control removed those rows, hints, and candidates.

The reproduced commands used an already prepared dependency installation and compiled runtime. npm ci and npm run build:cached above are the clean-checkout prerequisites.

Expected behavior

The component and bridge inventory should preserve the producer's known entry kind. A directory is not an inventoried bytecode file or native library. These inputs should produce no bytecode/native-library rows or JNI/N-API candidates for the named directories. Path-based inference need not decode opaque file contents, but it should not override an authenticated directory format.

docs/harmony-analysis.md:37 describes exact bytecode and native-library component identities, and docs/harmony-analysis.md:45 describes bridges between them. docs/android-analysis.md:242 specifies a DEX/JVM-class by native-library product. src/artifacts/inventory/ArtifactGraphConstruction.ts:164 distinguishes directory-tree identities from file content.

Impact and scope

P2, supported. Both execution-free application graphs return false component and bridge candidates for known directories while reporting complete coverage. The affected inputs are packages containing file-like directory names; the raw directory format remains visible.

A workaround is available. Callers can check component format against the retained authenticated inventory and discard directory endpoints when interpreting bridges. The reproduction returns the directory format and preserves all component paths.

REA version

6.4.0 (main at 8dde6370d0)

Runtime and platform

Node.js v26.10.0; macOS 27.0.1 arm64. Checkout dependencies and the compiled runtime were already present.

Target and artifact context

Two locally generated ZIP packages, sample.hap and sample.apk, inspected through inspect-artifact to obtain authenticated inventory Evidence. The HAP contains empty directories module.json/, ets/modules.abc/, and libs/arm64-v8a/libentry.so/. The APK contains the regular file AndroidManifest.xml with content manifest and empty directories classes.dex/ and lib/arm64-v8a/libentry.so/. The graph input wraps each returned inventory in inventory_evidence.

Interface

CLI

Earliest failure stage

Transport or result projection

Relevant output or evidence

The observed Harmony result excerpt is:

{
  "root_format": "hap",
  "managed": [{"path": "ets/modules.abc", "format": "directory"}],
  "native_libraries": [{"path": "libs/arm64-v8a/libentry.so", "format": "directory"}],
  "runtime_families": ["ark"],
  "bridge_candidates": [{
    "managed_path": "ets/modules.abc",
    "native_path": "libs/arm64-v8a/libentry.so",
    "basis": "napi-library-convention"
  }],
  "coverage": {"status": "complete-within-inventory", "inventory_complete": true}
}

The Android result likewise includes classes.dex and lib/arm64-v8a/libentry.so with format: "directory", a dalvik-art runtime hint, and a jni-library-convention bridge. All inventory and projection commands exited with code 0.

Typed outcome or recovery expected

Repeat both workflows. The known directories must not appear as bytecode or native-library components or supply the associated runtime hints and bridges. Preserve their identities in the source inventory. The renamed-directory control must continue to omit those rows, hints, and candidates.

Regular-file controls with the same relevant names should continue to receive the existing path-based projections and limitations. Restoration must not require implementing bytecode or native-library decoding.

Analysis or suspected cause

Source-derived reasoning: src/domain/mobileApplicationGraphProjection.ts:83 carries directory occurrences into projected components and preserves their format. The suffix classifiers at src/domain/harmony/harmonyApplication.ts:173 and src/domain/android/androidApplication.ts:160 admit them to bytecode/native collections without excluding the known directory kind.

The public CLI results and renamed-directory controls are the primary evidence. Source inspection explains how the known directory format reaches the classifiers. The failure was locally reproduced with complete reproduction inputs.

Additional context

Related closed issue #1489 concerns framework substring matches that mislabel component pairs; this report concerns directory endpoints that should not be file components at all.

Related closed issue #2603 concerns omitted unread components with unavailable identity; this report concerns false components for known directories with retained authenticated identity.

Related closed issue #1491 concerns container-family classification before checking bytes; this report concerns graph projection overriding a known directory kind after inventory.

Originally created by @coygeek on GitHub (Oct 14, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/3349 ### Before submitting - [x] I searched the open and closed issues for a related report. - [x] I removed credentials, tokens, private paths, and sensitive target data. ### Area Evidence, artifacts, or reconstruction ### Observed behavior `project-harmony-application-graph` and `project-android-application-graph` classify authenticated directory entries as bytecode and native-library components when their names have file-like suffixes. They emit N-API or JNI bridge candidates and runtime hints between those directories. The result preserves `format: "directory"` and reports complete coverage for these false component relationships. Every inventory and projection command in the reproduction exited with code 0. The Harmony fixture contains only empty directories. Its graph includes `ets/modules.abc` as bytecode, `libs/arm64-v8a/libentry.so` as a native library, an `ark` runtime hint, and a `napi-library-convention` bridge. The Android graph includes `classes.dex` and `lib/arm64-v8a/libentry.so` with `format: "directory"`, a `dalvik-art` runtime hint, and a `jni-library-convention` bridge between them. Renaming the bytecode and native directories to ordinary directory names removed the component rows, runtime hints, and bridge candidates. These observations concern known directory kinds and do not require validation of opaque DEX, Ark, or ELF bytes. ### Reproduction 1. Start from a clean checkout at `8dde6370d0809ad04cda3342d4e6f290f48aa063` and prepare the runtime: ```sh npm ci npm run build:cached ``` 2. From the repository root, create ZIP packages containing the named directories, inventory both packages, wrap each inventory as projection input, and run both public graph commands. The Python blocks create every input file inline: ```sh case_dir=$(mktemp -d) mkdir "$case_dir/home" python3 - "$case_dir" <<'PYTHON' import pathlib, sys, zipfile root = pathlib.Path(sys.argv[1]) with zipfile.ZipFile(root / "sample.hap", "w") as archive: archive.writestr("module.json/", "") archive.writestr("ets/modules.abc/", "") archive.writestr("libs/arm64-v8a/libentry.so/", "") with zipfile.ZipFile(root / "sample.apk", "w") as archive: archive.writestr("AndroidManifest.xml", "manifest") archive.writestr("classes.dex/", "") archive.writestr("lib/arm64-v8a/libentry.so/", "") PYTHON for family in harmony android; do if [ "$family" = harmony ]; then package=sample.hap; else package=sample.apk; fi HOME="$case_dir/home" TMPDIR="$case_dir" \ node scripts/rea.mjs inspect-artifact "$case_dir/$package" \ --format json > "$case_dir/$family.inventory.json" python3 - "$case_dir/$family.inventory.json" "$case_dir/$family.input.json" <<'PYTHON' import json, sys with open(sys.argv[2], "w") as output: json.dump({"inventory_evidence": [json.load(open(sys.argv[1]))]}, output) PYTHON HOME="$case_dir/home" TMPDIR="$case_dir" \ node scripts/rea.mjs "project-$family-application-graph" \ "$case_dir/$family.input.json" --format json > "$case_dir/$family.result.json" python3 - "$case_dir/$family.result.json" <<'PYTHON' import json, sys result = json.load(open(sys.argv[1]))["normalized_result"] print(json.dumps({ "root_format": result["root_format"], "managed": [{"path": row["path"], "format": row["format"]} for row in result["components"].get("bytecode", result["components"].get("dex", []))], "native_libraries": [{"path": row["path"], "format": row["format"]} for row in result["components"]["native_libraries"]], "runtime_families": result["runtime_families"], "bridge_candidates": result["bridge_candidates"], "coverage": result["coverage"] }, indent=2)) PYTHON done ``` 3. Observe the directory formats retained in the managed and native component rows, the associated runtime hints and bridge candidates, and complete coverage. Repeat after renaming the bytecode and native directories to ordinary directory names; the observed control removed those rows, hints, and candidates. The reproduced commands used an already prepared dependency installation and compiled runtime. `npm ci` and `npm run build:cached` above are the clean-checkout prerequisites. ### Expected behavior The component and bridge inventory should preserve the producer's known entry kind. A directory is not an inventoried bytecode file or native library. These inputs should produce no bytecode/native-library rows or JNI/N-API candidates for the named directories. Path-based inference need not decode opaque file contents, but it should not override an authenticated directory format. `docs/harmony-analysis.md:37` describes exact bytecode and native-library component identities, and `docs/harmony-analysis.md:45` describes bridges between them. `docs/android-analysis.md:242` specifies a DEX/JVM-class by native-library product. `src/artifacts/inventory/ArtifactGraphConstruction.ts:164` distinguishes directory-tree identities from file content. ### Impact and scope P2, supported. Both execution-free application graphs return false component and bridge candidates for known directories while reporting complete coverage. The affected inputs are packages containing file-like directory names; the raw directory format remains visible. A workaround is available. Callers can check component format against the retained authenticated inventory and discard directory endpoints when interpreting bridges. The reproduction returns the directory format and preserves all component paths. ### REA version 6.4.0 (main at 8dde6370d0809ad04cda3342d4e6f290f48aa063) ### Runtime and platform Node.js v26.10.0; macOS 27.0.1 arm64. Checkout dependencies and the compiled runtime were already present. ### Target and artifact context Two locally generated ZIP packages, `sample.hap` and `sample.apk`, inspected through `inspect-artifact` to obtain authenticated inventory Evidence. The HAP contains empty directories `module.json/`, `ets/modules.abc/`, and `libs/arm64-v8a/libentry.so/`. The APK contains the regular file `AndroidManifest.xml` with content `manifest` and empty directories `classes.dex/` and `lib/arm64-v8a/libentry.so/`. The graph input wraps each returned inventory in `inventory_evidence`. ### Interface CLI ### Earliest failure stage Transport or result projection ### Relevant output or evidence The observed Harmony result excerpt is: ```text { "root_format": "hap", "managed": [{"path": "ets/modules.abc", "format": "directory"}], "native_libraries": [{"path": "libs/arm64-v8a/libentry.so", "format": "directory"}], "runtime_families": ["ark"], "bridge_candidates": [{ "managed_path": "ets/modules.abc", "native_path": "libs/arm64-v8a/libentry.so", "basis": "napi-library-convention" }], "coverage": {"status": "complete-within-inventory", "inventory_complete": true} } ``` The Android result likewise includes `classes.dex` and `lib/arm64-v8a/libentry.so` with `format: "directory"`, a `dalvik-art` runtime hint, and a `jni-library-convention` bridge. All inventory and projection commands exited with code 0. ### Typed outcome or recovery expected Repeat both workflows. The known directories must not appear as bytecode or native-library components or supply the associated runtime hints and bridges. Preserve their identities in the source inventory. The renamed-directory control must continue to omit those rows, hints, and candidates. Regular-file controls with the same relevant names should continue to receive the existing path-based projections and limitations. Restoration must not require implementing bytecode or native-library decoding. ### Analysis or suspected cause Source-derived reasoning: `src/domain/mobileApplicationGraphProjection.ts:83` carries directory occurrences into projected components and preserves their format. The suffix classifiers at `src/domain/harmony/harmonyApplication.ts:173` and `src/domain/android/androidApplication.ts:160` admit them to bytecode/native collections without excluding the known directory kind. The public CLI results and renamed-directory controls are the primary evidence. Source inspection explains how the known directory format reaches the classifiers. The failure was locally reproduced with complete reproduction inputs. ### Additional context Related closed issue [#1489](https://github.com/morluto/rea/issues/1489) concerns framework substring matches that mislabel component pairs; this report concerns directory endpoints that should not be file components at all. Related closed issue [#2603](https://github.com/morluto/rea/issues/2603) concerns omitted unread components with unavailable identity; this report concerns false components for known directories with retained authenticated identity. Related closed issue [#1491](https://github.com/morluto/rea/issues/1491) concerns container-family classification before checking bytes; this report concerns graph projection overriding a known directory kind after inventory.
Author
Owner

@morluto commented on GitHub (Oct 17, 2026):

Fixed on main by a425b3e52 (followed by the independent runtime-summary correction in fc935d0df). Adapted @flyl1u's proposal and public adapter controls from #3830, extending the shared fix and fixtures to Apple. This is not a PR merge/readiness attestation.

The shared owner now selects authenticated file/slice occurrences before classification. Directory/link identity remains in source inventory, and Apple anatomy still consumes all entries. Removed the three independent component-extraction paths; preserved ordering, multiplicity and existing unavailable facts. Directories no longer produce code roles, packaging-model hints, child packages or bridge endpoints.

All four authored package regressions fail before this fix (Android, Apple, Harmony HAP and App Pack). Controls cross real inspect-artifact → projection CLI and production stdio MCP, validate converted schemas/results, retain source Evidence and original ZIP bytes, and distinguish explicit directories, ordinary files and files nested inside file-like directories. A separate selector control covers slices, links, order and repeated occurrences. Initial focused suite:80checks. Final combined validation at fc935d0df:91checks in9files; build,check:fast/typecheck/lint,docs:check (5README checks) and hooks pass. Two read-only source reviews found no blocker.

72 completed tiny source projection tasks used12 genuine authored inventories. Four regular-file controls preserve complete reply bytes and IDs. First processes were separated from2alternating repeats. Directory-task repeated-process medians:

Task Reply tokens Elapsed ms CPU ms Peak MB
Android-directories 669→385 47.99→48.18 61.19→61.62 75.42→75.16
Apple-directories 641→448 80.09→86.93 99.40→95.11 75.12→74.86
Harmony-directories 851→436 92.28→81.79 98.37→97.05 75.14→75.32
HarmonyPack-directories 903→498 62.29→68.38 80.71→84.89 75.41→75.42

Full source replies use cl100k_base estimates, CPU/peakRSS use OSwait4 for each known single-process source task. Static checks overlapped part of this exploratory measurement; no speed improvement or true-cold claim. Whole-agent/catalog, full-public-task, large-input and failure costs were not measured for this fix. No target execution, deep provider, heavy profiling or published npm verification.

<!-- gh-comment-id:6233662175 --> @morluto commented on GitHub (Oct 17, 2026): Fixed on main by a425b3e52 (followed by the independent runtime-summary correction in fc935d0df). Adapted @flyl1u's proposal and public adapter controls from #3830, extending the shared fix and fixtures to Apple. This is not a PR merge/readiness attestation. The shared owner now selects authenticated file/slice occurrences before classification. Directory/link identity remains in source inventory, and Apple anatomy still consumes all entries. Removed the three independent component-extraction paths; preserved ordering, multiplicity and existing unavailable facts. Directories no longer produce code roles, packaging-model hints, child packages or bridge endpoints. All four authored package regressions fail before this fix (Android, Apple, Harmony HAP and App Pack). Controls cross real inspect-artifact → projection CLI and production stdio MCP, validate converted schemas/results, retain source Evidence and original ZIP bytes, and distinguish explicit directories, ordinary files and files nested inside file-like directories. A separate selector control covers slices, links, order and repeated occurrences. Initial focused suite:80checks. Final combined validation at fc935d0df:91checks in9files; build,check:fast/typecheck/lint,docs:check (5README checks) and hooks pass. Two read-only source reviews found no blocker. 72 completed tiny source projection tasks used12 genuine authored inventories. Four regular-file controls preserve complete reply bytes and IDs. First processes were separated from2alternating repeats. Directory-task repeated-process medians: | Task | Reply tokens | Elapsed ms | CPU ms | Peak MB | | --- | --- | --- | --- | --- | | Android-directories | 669→385 | 47.99→48.18 | 61.19→61.62 | 75.42→75.16 | | Apple-directories | 641→448 | 80.09→86.93 | 99.40→95.11 | 75.12→74.86 | | Harmony-directories | 851→436 | 92.28→81.79 | 98.37→97.05 | 75.14→75.32 | | HarmonyPack-directories | 903→498 | 62.29→68.38 | 80.71→84.89 | 75.41→75.42 | Full source replies use cl100k_base estimates, CPU/peakRSS use OSwait4 for each known single-process source task. Static checks overlapped part of this exploratory measurement; no speed improvement or true-cold claim. Whole-agent/catalog, full-public-task, large-input and failure costs were not measured for this fix. No target execution, deep provider, heavy profiling or published npm verification.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#1218
No description provided.