[GH-ISSUE #6638] [Bug] Harmony raw resource names override the package model #4020

Closed
opened 2026-10-31 00:46:06 +00:00 by bsakaguchi · 0 comments
Owner

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

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

A raw resource named module.json overrides the actual FA-layout manifest when projecting a Harmony application graph. In two synthetic HAP archives with identical root config.json bytes and identical resource bytes, changing only the resource path from assets/entry/resources/rawfile/notes.json to assets/entry/resources/rawfile/module.json changes packaging_model from fa to stage. The resource also appears in components.manifests, while remaining correctly present in components.resources.

Observed by executing the production source inventory boundary (createDirectAnalysis().runProviderAnalysis(..., "inventory_artifact", {})) and projectHarmonyApplicationEvidence. This is a source-boundary runtime reproduction, not an executed CLI/MCP reproduction. Both inspections and projections succeeded. A broader initial local probe and the reduced two-fixture probe produced this mismatch before this report was written.

Reproduction

From the source checkout below, with locked dependencies already installed, run this script using Node.js v24.22.1 and Python 3. It creates two small ZIP-format fixtures and saves the actual inventory/projection results in its disposable directory. No SDK, package compiler, external provider, or runtime build is required.

set -eu
work_dir=$(mktemp -d)
python3 - "$work_dir" <<'PY'
import sys, zipfile
from pathlib import Path
root = Path(sys.argv[1])
for name, resource in [('control.hap', 'notes.json'), ('resource.hap', 'module.json')]:
    with zipfile.ZipFile(root / name, 'w') as archive:
        archive.writestr('config.json', '{"app":{"bundleName":"com.example.fixture"},"module":{"package":"com.example.fixture","name":"entry"}}')
        archive.writestr('assets/entry/resources/rawfile/' + resource,
                         '{"note":"ordinary bundled data"}')
PY
PROBE_ROOT="$work_dir" node --import tsx --input-type=module <<'JS'
import { writeFile } from 'node:fs/promises';
import { join } from 'node:path';
import { createDirectAnalysis } from './src/composition/directAnalysis.ts';
import { projectHarmonyApplicationEvidence } from './src/application/harmony/HarmonyApplicationService.ts';
const { runProviderAnalysis } = createDirectAnalysis({});
for (const fixture of ['control.hap', 'resource.hap']) {
  const inventory = await runProviderAnalysis(join(process.env.PROBE_ROOT, fixture), 'inventory_artifact', {});
  const projected = projectHarmonyApplicationEvidence({ inventory_evidence: [inventory] });
  if (!projected.ok) throw projected.error;
  const graph = projected.value.evidence.normalized_result;
  await writeFile(join(process.env.PROBE_ROOT, fixture + '.inventory.json'), JSON.stringify(inventory, null, 2));
  await writeFile(join(process.env.PROBE_ROOT, fixture + '.projection.json'), JSON.stringify(projected, null, 2));
  console.log(JSON.stringify({ fixture, packaging_model: graph.packaging_model,
    manifests: graph.components.manifests.map(x => x.path),
    resources: graph.components.resources.map(x => x.path), coverage: graph.coverage }));
}
JS

Expected behavior

The model hint should remain fa for both fixtures, using the package's root config.json. The nested raw resource should remain a resource and should not become a package manifest solely because its basename is module.json. Resolving paths is sufficient; decoding JSON semantics is unnecessary for this distinction.

Contract evidence:

Impact and scope

P2: a bounded filename collision produces the wrong packaging-model hint and an extra manifest row in successful output. The original archive, root manifest identity, resource identity, and source inventory remain intact. Both observed projections report complete inventory coverage; that status is context, not a claim that manifest semantics were decoded.

A practical workaround for the demonstrated case is to use the retained root config.json occurrence to derive the FA hint and ignore the nested resource in the manifest list. The saved inventories preserve both paths and their hashes. The resource need not be renamed or removed. Real-package prevalence, SDK-produced archives, CLI/MCP execution, and other package layouts were not established. These are synthetic FA-layout ZIP fixtures, not an assertion that an SDK would accept their minimal manifest as an installable application.

REA version

rea-agents 6.7.0; source revision 3ed9f4b7e4ccd5b009bfbe6cf80383165199530b.

Runtime and platform

Node.js v24.22.1; Linux x64; kernel 7.2.8-9-omarchy; Python 3. No Hopper, Ghidra, Harmony SDK, or external analysis provider involved.

Target and artifact context

Two local ZIP-format .hap files containing root config.json and one regular-file JSON resource under assets/entry/resources/rawfile/. Root manifest bytes are equal between fixtures; resource bytes are equal; only the resource basename differs. Inventory Evidence comes from the real source inventory workflow rather than manually constructed Evidence.

Interface

Other

Earliest failure stage

Transport or result projection

Relevant output or evidence

The reduced script exited 0 and printed:

{"fixture":"control.hap","packaging_model":"fa","manifests":["config.json"],"resources":["assets/entry/resources/rawfile/notes.json"],"coverage":{"status":"complete-within-inventory","inventory_complete":true}}
{"fixture":"resource.hap","packaging_model":"stage","manifests":["assets/entry/resources/rawfile/module.json","config.json"],"resources":["assets/entry/resources/rawfile/module.json"],"coverage":{"status":"complete-within-inventory","inventory_complete":true}}

Both source inventories report root format hap and coverage: {"scope":"container-entries","status":"complete"}. A separate comparison of the saved ZIPs established equal root configuration and resource contents. The script preserves full inventory/projection JSON for inspection. No file content is missing or unavailable in this case.

Typed outcome or recovery expected

Return packaging_model: "fa" for both fixtures. Retain the raw resource's exact path and identity in components.resources, and retain the package's actual config.json in components.manifests.

Analysis or suspected cause

Source-derived diagnosis: the manifest classifier accepts module.json, config.json, and pack.info at any depth. The model classifier uses the same basename matching and gives Stage precedence. A resource path therefore supplies a false Stage signal even when the root FA configuration remains present. The existing limitation about path-presence heuristics does not distinguish a package manifest location from an arbitrary raw resource name.

Additional context

The open-issue snapshot was screened before probing. Related #5748 concerns unavailable encrypted manifest/child identities, #5413 concerns signing suffixes, and #4489 concerns unclassified files and declared N-API modules. This reproduction uses readable resources that are already classified; it concerns a false package-model signal. The submitter searches open and closed issues immediately before filing.

Prepared with AI assistance. NOT_ENOUGH_INFO: the last known-good version and real-package occurrence rate are unknown. No production source was changed and no restoration is claimed.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: Renaming one ordinary raw resource changes the observed FA packaging hint to Stage and adds a false manifest row; the affected package structure and source evidence otherwise remain available.
  • Workaround status: available
  • Workaround basis: The saved actual inventory retains the root config.json occurrence and both resource paths with authenticated identities; a caller can derive the demonstrated FA hint from that root path without altering or losing package data.
Originally created by @coygeek on GitHub (Oct 23, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/6638 ### 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 A raw resource named `module.json` overrides the actual FA-layout manifest when projecting a Harmony application graph. In two synthetic HAP archives with identical root `config.json` bytes and identical resource bytes, changing only the resource path from `assets/entry/resources/rawfile/notes.json` to `assets/entry/resources/rawfile/module.json` changes `packaging_model` from `fa` to `stage`. The resource also appears in `components.manifests`, while remaining correctly present in `components.resources`. Observed by executing the production source inventory boundary (`createDirectAnalysis().runProviderAnalysis(..., "inventory_artifact", {})`) and `projectHarmonyApplicationEvidence`. This is a source-boundary runtime reproduction, not an executed CLI/MCP reproduction. Both inspections and projections succeeded. A broader initial local probe and the reduced two-fixture probe produced this mismatch before this report was written. ### Reproduction From the source checkout below, with locked dependencies already installed, run this script using Node.js v24.22.1 and Python 3. It creates two small ZIP-format fixtures and saves the actual inventory/projection results in its disposable directory. No SDK, package compiler, external provider, or runtime build is required. ```sh set -eu work_dir=$(mktemp -d) python3 - "$work_dir" <<'PY' import sys, zipfile from pathlib import Path root = Path(sys.argv[1]) for name, resource in [('control.hap', 'notes.json'), ('resource.hap', 'module.json')]: with zipfile.ZipFile(root / name, 'w') as archive: archive.writestr('config.json', '{"app":{"bundleName":"com.example.fixture"},"module":{"package":"com.example.fixture","name":"entry"}}') archive.writestr('assets/entry/resources/rawfile/' + resource, '{"note":"ordinary bundled data"}') PY PROBE_ROOT="$work_dir" node --import tsx --input-type=module <<'JS' import { writeFile } from 'node:fs/promises'; import { join } from 'node:path'; import { createDirectAnalysis } from './src/composition/directAnalysis.ts'; import { projectHarmonyApplicationEvidence } from './src/application/harmony/HarmonyApplicationService.ts'; const { runProviderAnalysis } = createDirectAnalysis({}); for (const fixture of ['control.hap', 'resource.hap']) { const inventory = await runProviderAnalysis(join(process.env.PROBE_ROOT, fixture), 'inventory_artifact', {}); const projected = projectHarmonyApplicationEvidence({ inventory_evidence: [inventory] }); if (!projected.ok) throw projected.error; const graph = projected.value.evidence.normalized_result; await writeFile(join(process.env.PROBE_ROOT, fixture + '.inventory.json'), JSON.stringify(inventory, null, 2)); await writeFile(join(process.env.PROBE_ROOT, fixture + '.projection.json'), JSON.stringify(projected, null, 2)); console.log(JSON.stringify({ fixture, packaging_model: graph.packaging_model, manifests: graph.components.manifests.map(x => x.path), resources: graph.components.resources.map(x => x.path), coverage: graph.coverage })); } JS ``` ### Expected behavior The model hint should remain `fa` for both fixtures, using the package's root `config.json`. The nested raw resource should remain a resource and should not become a package manifest solely because its basename is `module.json`. Resolving paths is sufficient; decoding JSON semantics is unnecessary for this distinction. Contract evidence: - [REA's Harmony guide](https://github.com/morluto/rea/blob/3ed9f4b7e4ccd5b009bfbe6cf80383165199530b/docs/harmony-analysis.md#L34-L59) promises a Stage/FA packaging-model hint and FA resources under `assets/<module>/resources/`, while explicitly limiting claims to paths. - [The existing FA boundary fixture](https://github.com/morluto/rea/blob/3ed9f4b7e4ccd5b009bfbe6cf80383165199530b/tests/boundary/filesystem/harmony/application.test.ts#L203-L230) uses root `config.json` plus `assets/entry/resources/` and expects `fa`. - [OpenHarmony's FA package structure](https://github.com/openharmony/docs/blob/f41b9345badd47c7ab0c263344cd7f4b5a549afb/en/application-dev/quick-start/application-package-structure-fa.md#L15-L21) distinguishes the application configuration file from resources under `assets`. - [OpenHarmony's resource rules](https://github.com/openharmony/docs/blob/f41b9345badd47c7ab0c263344cd7f4b5a549afb/en/application-dev/quick-start/resource-categories-and-access.md#L15-L54) allow custom raw resource filenames and package them directly. Thus `module.json` is an ordinary resource name, not evidence of a Stage package at that location. ### Impact and scope P2: a bounded filename collision produces the wrong packaging-model hint and an extra manifest row in successful output. The original archive, root manifest identity, resource identity, and source inventory remain intact. Both observed projections report complete inventory coverage; that status is context, not a claim that manifest semantics were decoded. A practical workaround for the demonstrated case is to use the retained root `config.json` occurrence to derive the FA hint and ignore the nested resource in the manifest list. The saved inventories preserve both paths and their hashes. The resource need not be renamed or removed. Real-package prevalence, SDK-produced archives, CLI/MCP execution, and other package layouts were not established. These are synthetic FA-layout ZIP fixtures, not an assertion that an SDK would accept their minimal manifest as an installable application. ### REA version `rea-agents` 6.7.0; source revision `3ed9f4b7e4ccd5b009bfbe6cf80383165199530b`. ### Runtime and platform Node.js v24.22.1; Linux x64; kernel `7.2.8-9-omarchy`; Python 3. No Hopper, Ghidra, Harmony SDK, or external analysis provider involved. ### Target and artifact context Two local ZIP-format `.hap` files containing root `config.json` and one regular-file JSON resource under `assets/entry/resources/rawfile/`. Root manifest bytes are equal between fixtures; resource bytes are equal; only the resource basename differs. Inventory Evidence comes from the real source inventory workflow rather than manually constructed Evidence. ### Interface Other ### Earliest failure stage Transport or result projection ### Relevant output or evidence The reduced script exited 0 and printed: ```json {"fixture":"control.hap","packaging_model":"fa","manifests":["config.json"],"resources":["assets/entry/resources/rawfile/notes.json"],"coverage":{"status":"complete-within-inventory","inventory_complete":true}} {"fixture":"resource.hap","packaging_model":"stage","manifests":["assets/entry/resources/rawfile/module.json","config.json"],"resources":["assets/entry/resources/rawfile/module.json"],"coverage":{"status":"complete-within-inventory","inventory_complete":true}} ``` Both source inventories report root format `hap` and `coverage: {"scope":"container-entries","status":"complete"}`. A separate comparison of the saved ZIPs established equal root configuration and resource contents. The script preserves full inventory/projection JSON for inspection. No file content is missing or unavailable in this case. ### Typed outcome or recovery expected Return `packaging_model: "fa"` for both fixtures. Retain the raw resource's exact path and identity in `components.resources`, and retain the package's actual `config.json` in `components.manifests`. ### Analysis or suspected cause Source-derived diagnosis: [the manifest classifier](https://github.com/morluto/rea/blob/3ed9f4b7e4ccd5b009bfbe6cf80383165199530b/src/domain/harmony/harmonyApplication.ts#L185-L194) accepts `module.json`, `config.json`, and `pack.info` at any depth. [The model classifier](https://github.com/morluto/rea/blob/3ed9f4b7e4ccd5b009bfbe6cf80383165199530b/src/domain/harmony/harmonyApplication.ts#L173-L182) uses the same basename matching and gives Stage precedence. A resource path therefore supplies a false Stage signal even when the root FA configuration remains present. The existing limitation about path-presence heuristics does not distinguish a package manifest location from an arbitrary raw resource name. ### Additional context The open-issue snapshot was screened before probing. Related #5748 concerns unavailable encrypted manifest/child identities, #5413 concerns signing suffixes, and #4489 concerns unclassified files and declared N-API modules. This reproduction uses readable resources that are already classified; it concerns a false package-model signal. The submitter searches open and closed issues immediately before filing. Prepared with AI assistance. NOT_ENOUGH_INFO: the last known-good version and real-package occurrence rate are unknown. No production source was changed and no restoration is claimed. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: Renaming one ordinary raw resource changes the observed FA packaging hint to Stage and adds a false manifest row; the affected package structure and source evidence otherwise remain available. - Workaround status: available - Workaround basis: The saved actual inventory retains the root config.json occurrence and both resource paths with authenticated identities; a caller can derive the demonstrated FA hint from that root path without altering or losing package data.
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#4020
No description provided.