mirror of
https://github.com/morluto/rea.git
synced 2026-11-07 05:56:19 -10:00
feat(harmony): disclose the unread pkgContextInfo.json declaration (#9096)
* feat(harmony): disclose the unread pkgContextInfo.json declaration A package that ships pkgContextInfo.json shows the entry in unclassified_components, yet nothing said its isSO declarations were skipped: bridge_candidates came only from the libs/<abi>/lib<name>.so path rule. State that gap in limitations when the entry is inventoried, so a path-convention hypothesis is not read as a package declaration. The declaration-parsing half of #4489 needs a decision about who owns reading inventoried file content; this projection receives identity and digests only, and the shared inventory carrier guard rejects other Evidence. No content is parsed here. * test(harmony): assert the disclosure by its names, not its sentence docs/testing.md requires asserting structured facts instead of freezing an explanatory sentence, and says a replaced prose assertion must still pass on a harmless wording change while failing when the fact is lost. The first version of this test compared the whole limitation string verbatim, so any rewording of the disclosure would have failed CI. The assertions now require exactly one limitation naming pkgContextInfo.json, carrying bridge_candidates, isSO, and the not-read/not-narrowed fact, and keep the structural claim that the same package without the declaration file yields identical bridge candidates and no such limitation.
This commit is contained in:
3 files changed
+75
-1
No files matched your search
@@ -82,6 +82,12 @@ boundaries are out of scope for this increment and stay in the projection's
|
||||
`limitations` array until a dedicated HarmonyOS provider exists. REA fails
|
||||
closed on packages whose inventory cannot be authenticated.
|
||||
|
||||
`pkgContextInfo.json` is inventoried and listed as an unclassified component but
|
||||
never read, so its `isSO` declarations do not narrow `bridge_candidates`. When
|
||||
that entry is present the projection adds a limitation naming the unread
|
||||
declaration, so a path-convention hypothesis is not mistaken for a package
|
||||
declaration.
|
||||
|
||||
## Verification lane
|
||||
|
||||
`npm run fixtures:harmony` downloads the SHA-256-pinned 1.0.1 HAP release
|
||||
|
||||
@@ -71,6 +71,15 @@ export type HarmonyApplicationProjectionResult = z.infer<
|
||||
>;
|
||||
type Component = z.infer<typeof projectedComponentSchema>;
|
||||
|
||||
const PKG_CONTEXT_NOT_READ =
|
||||
"pkgContextInfo.json is inventoried but not read, so bridge_candidates are not narrowed by its isSO declarations; narrowing requires parsed file content that this projection does not receive.";
|
||||
|
||||
/** Whether the package carries the build-output declaration this projection cannot read. */
|
||||
const pkgContextDeclared = (
|
||||
files: readonly ProjectedInventoryEntry[],
|
||||
): boolean =>
|
||||
files.some(({ path }) => /(?:^|\/)pkgContextInfo\.json$/iu.test(path));
|
||||
|
||||
/** Project exact HarmonyOS package paths and hashes without decoding or executing target code. */
|
||||
export const projectHarmonyInventory = (
|
||||
source: ParsedInventorySet,
|
||||
@@ -122,6 +131,7 @@ export const projectHarmonyInventory = (
|
||||
"Runtime families are inferred from inventory formats and paths; filename suffixes do not establish valid Ark bytecode.",
|
||||
"The signing component lists META-INF certificate-entry suffixes only; signatures applied by the packaging tool live in the ZIP signing block rather than archive entries, so an empty signing list does not mean the package is unsigned.",
|
||||
"Bridge candidates are path-based hypotheses, not decoded N-API declarations or observed runtime calls.",
|
||||
...(pkgContextDeclared(fileEntries) ? [PKG_CONTEXT_NOT_READ] : []),
|
||||
...(rootFormat === "app-pack"
|
||||
? [
|
||||
"App Pack child packages are listed by inventory path only; their contents are not recursively inventoried by this projection.",
|
||||
|
||||
@@ -17,7 +17,10 @@ import {
|
||||
|
||||
import { projectHarmonyApplicationEvidence } from "../../../../src/application/harmony/HarmonyApplicationService.js";
|
||||
import { createDirectAnalysis } from "../../../../src/composition/directAnalysis.js";
|
||||
import { harmonyApplicationProjectionResultSchema } from "../../../../src/domain/harmony/harmonyApplication.js";
|
||||
import {
|
||||
harmonyApplicationProjectionResultSchema,
|
||||
type HarmonyApplicationProjectionResult,
|
||||
} from "../../../../src/domain/harmony/harmonyApplication.js";
|
||||
import { artifactInventoryResultSchema } from "../../../../src/domain/artifactGraph.js";
|
||||
import { createEvidence } from "../../../../src/domain/evidence.js";
|
||||
import { parseBinaryTarget } from "../../../../src/application/BinaryTargetResolver.js";
|
||||
@@ -39,6 +42,61 @@ const stageHapEntries = [
|
||||
{ path: "META-INF/FIXTURE.p7b", content: "opaque signing" },
|
||||
] as const;
|
||||
|
||||
const PKG_CONTEXT_NAME = "pkgContextInfo.json";
|
||||
|
||||
const unreadDeclarationLimitations = (
|
||||
limitations: readonly string[],
|
||||
): string[] => limitations.filter((text) => text.includes(PKG_CONTEXT_NAME));
|
||||
|
||||
const projectStageHap = async (
|
||||
label: string,
|
||||
entries: readonly { path: string; content: string | Uint8Array }[],
|
||||
): Promise<HarmonyApplicationProjectionResult> => {
|
||||
const path = await writeApplicationZip(label, "Fixture.hap", entries);
|
||||
return requireDeterministicProjection(
|
||||
projectHarmonyApplicationEvidence,
|
||||
await inventoryApplicationPackage(path),
|
||||
harmonyApplicationProjectionResultSchema,
|
||||
);
|
||||
};
|
||||
|
||||
const stageHapWithDeclaration = stageHapEntries.map(({ path, content }) => ({
|
||||
path,
|
||||
content,
|
||||
}));
|
||||
|
||||
describe("HarmonyOS N-API declaration disclosure", () => {
|
||||
it("discloses the unread declaration by its names, not its wording", async () => {
|
||||
const projection = await projectStageHap("rea-harmony-pkg-context-", [
|
||||
...stageHapWithDeclaration,
|
||||
{
|
||||
path: PKG_CONTEXT_NAME,
|
||||
content: '{"libentry.so":{"isSO":true}}',
|
||||
},
|
||||
]);
|
||||
const control = await projectStageHap(
|
||||
"rea-harmony-pkg-context-control-",
|
||||
stageHapWithDeclaration,
|
||||
);
|
||||
|
||||
const disclosed = unreadDeclarationLimitations(projection.limitations);
|
||||
expect(disclosed).toHaveLength(1);
|
||||
expect(disclosed[0]).toContain("bridge_candidates");
|
||||
expect(disclosed[0]).toMatch(/\bisSO\b/);
|
||||
expect(disclosed[0]).toMatch(/not read|not narrowed/iu);
|
||||
expect(projection.bridge_candidates).toEqual(control.bridge_candidates);
|
||||
expect(unreadDeclarationLimitations(control.limitations)).toEqual([]);
|
||||
});
|
||||
|
||||
it("does not disclose a declaration the package does not carry", async () => {
|
||||
const projection = await projectStageHap(
|
||||
"rea-harmony-no-pkg-context-",
|
||||
stageHapWithDeclaration,
|
||||
);
|
||||
expect(unreadDeclarationLimitations(projection.limitations)).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("HarmonyOS unclassified inventory", () => {
|
||||
it("retains distinct unclassified paths sharing content with a classified file", async () => {
|
||||
const path = await writeApplicationZip(
|
||||
|
||||
Reference in new issue
Block a user