diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1f851393e..d1bb5bef5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -311,7 +311,7 @@ jobs: - name: Real binary metadata and Interface Builder integration run: npm run verify:apple-dispatch && npm run verify:interface-builder - name: Native plist CLI selection regressions - run: npm run test:focused -- tests/boundary/cli/nativePlistCli.test.ts + run: npm run test:focused -- tests/boundary/cli/nativePlistCli.test.ts tests/boundary/cli/nativePlistPrototypeKeyCli.test.ts tests/boundary/cli/nativePlistSelectionFailureCli.test.ts reconstruction-readiness: name: Reconstruction readiness diff --git a/docs/capability-migration.md b/docs/capability-migration.md index 5761279c1..3789591d6 100644 --- a/docs/capability-migration.md +++ b/docs/capability-migration.md @@ -356,3 +356,32 @@ repository-root default. Both point to `_reference/firmware-integration/generate when invoked through the documented npm commands. Optional ext4/Ghidra lanes keep their own prerequisites. Source-fixture generation and synthetic producer tests do not establish real Binwalk/Unblob analysis or another host's execution support. + +## Apple artifact capability ownership + +`src/domain/apple/` owns Apple application anatomy/projection, asset catalog +facts, Interface Builder graphs, keyed archive semantics and plist values. +`src/application/apple/AppleApplicationService.ts` retains the shared workflow +that projects authenticated IPA/macOS inventory Evidence. + +`src/artifacts/apple/` owns the actual producer boundary: directory acquisition, +assetutil invocation, plist/NIB decoding, encoded view-parent interpretation and +keyed archive inspection. These helpers are used by ArtifactProvider, retain +its existing typed analysis boundary, injected assetutil seam and original reader +cleanup, and do not require a new port for each helper. Producer code no longer +lives in the application layer. Guards reject application/composition/caller +imports from this owner while admitting pure facts and shared artifact readers. +Adapter-source tests retain their assertions in the existing forked adapter lane. + +`scripts/verify/apple/` groups archive, asset catalog, Interface Builder, +Objective-C/Swift dispatch and macOS bundle verification. The macOS bundle fixture +builder lives in `scripts/fixtures/apple/`; NIB byte fixtures stay beside their +artifact decoder. Existing format-specific conformance sources and goldens keep +their locations, and relocated verifiers resolve them against their own file +URLs. Public npm entrypoints and CLI/MCP contracts stay stable; generated-document +cache inputs include the relocated runtime scripts and fixture builder. + +Portable decoder and injected producer tests establish their format/boundary +claims. Actual Swift/Xcode, assetutil, native NIB, bundle signing and DMG lifecycle +claims require the real Apple lane. Native binary API/value-flow semantics and +shared cross-domain contract aggregates keep their separate owners. diff --git a/docs/testing.md b/docs/testing.md index e76e09f10..e7f406c9f 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -89,6 +89,12 @@ then checks that `inspect-artifact` plus `project-apple-application-graph` report the same bundle anatomy for all three through the CLI, with stdio MCP parity. It also checks that the DMG is detached afterwards. It runs in macOS CI. +Apple artifact verifiers live in `scripts/verify/apple/`, with the macOS bundle +builder under `scripts/fixtures/apple/` and NIB byte fixtures beside the decoder +in `src/artifacts/apple/`. The npm entrypoints are unchanged. Format-specific +Swift/XIB/asset sources and goldens retain their locations; real Apple workflows +resolve them from the verifier file URL and run in the macOS CI lane. + MCP SDK transport tests with recording providers remain integration tests. They are useful for schema drift and failure projection but do not prove that Hopper, Ghidra or another substituted engine works. `verify:package` proves diff --git a/package.json b/package.json index ad1e9cab9..bfdb50a05 100644 --- a/package.json +++ b/package.json @@ -121,17 +121,17 @@ "typecheck": "tsc --noEmit", "verify:test-temp-hygiene": "npm run build:cached && node scripts/verify-test-temp-hygiene.mjs", "verify:hopper": "npm run verify:fixtures && npm run build:cached && node scripts/verify-real-hopper.mjs", - "verify:apple-dispatch": "npm run build:cached && node scripts/verify-apple-dispatch.mjs", + "verify:apple-dispatch": "npm run build:cached && node scripts/verify/apple/dispatch.mjs", "verify:native-ui": "npm run build:cached && node scripts/verify-native-ui.mjs", - "verify:keyed-archive": "npm run build:cached && node scripts/verify-keyed-archive.mjs", - "verify:macos-bundle": "npm run build:cached && node scripts/verify-macos-bundle.mjs", + "verify:keyed-archive": "npm run build:cached && node scripts/verify/apple/keyed-archive.mjs", + "verify:macos-bundle": "npm run build:cached && node scripts/verify/apple/macos-bundle.mjs", "verify:ghidra": "npm run build:cached && node scripts/verify-real-ghidra.mjs", "verify:ida": "npm run build:cached && node scripts/verify-real-ida.mjs", "verify:ghidra:switch": "npm run build:cached && node scripts/verify-real-ghidra-switch.mjs", "verify:ghidra:dos": "npm run build:cached && node scripts/verify-real-ghidra-dos.mjs", "verify:ghidra:com": "npm run build:cached && node scripts/verify-real-ghidra-com.mjs", "verify:ghidra:aarch64-jump-table": "npm run build:cached && node scripts/verify-real-ghidra.mjs --aarch64-jump-table", - "verify:interface-builder": "npm run build:cached && node scripts/verify-interface-builder.mjs", + "verify:interface-builder": "npm run build:cached && node scripts/verify/apple/interface-builder.mjs", "verify:ghidra:cross-format": "npm run build:cached && node scripts/verify-real-ghidra.mjs --cross-format", "verify:ghidra:windows": "npm run build:cached && npm run build:ghidra-windows-fixture && node scripts/verify-real-ghidra-windows.mjs", "verify:browser": "npm run build:cached && node scripts/verify-real-browser.mjs", @@ -171,7 +171,7 @@ "docs:error-schema": "npm run build:cached && node scripts/generate-error-schema.mjs", "prepack": "node scripts/prepack.mjs", "prepare": "node scripts/prepare.mjs", - "verify:asset-catalog": "npm run build:cached && node scripts/verify-asset-catalog.mjs", + "verify:asset-catalog": "npm run build:cached && node scripts/verify/apple/asset-catalog.mjs", "verify:native-ui:permissions": "npm run build:cached && node scripts/verify-native-ui.mjs --allow-permission-denial", "test:focused": "node scripts/run-development-tests.mjs focused", "build:windows-native": "node scripts/build-windows-native.mjs", diff --git a/scripts/lib/macos-bundle-fixture.mjs b/scripts/fixtures/apple/macos-bundle.mjs similarity index 100% rename from scripts/lib/macos-bundle-fixture.mjs rename to scripts/fixtures/apple/macos-bundle.mjs diff --git a/scripts/lib/module-boundaries.mjs b/scripts/lib/module-boundaries.mjs index e43ccd44c..ca1e4d933 100644 --- a/scripts/lib/module-boundaries.mjs +++ b/scripts/lib/module-boundaries.mjs @@ -69,6 +69,7 @@ const failedBoundary = (file, target) => { return "provider-construction"; if ( (file.startsWith("src/artifacts/javascript/") || + file.startsWith("src/artifacts/apple/") || /^src\/artifacts\/ArtifactHash\.(?:js|ts)$/u.test(file)) && (["application", "composition", "server", "cli", "main"].includes( targetLayer, diff --git a/scripts/verify-asset-catalog.mjs b/scripts/verify/apple/asset-catalog.mjs similarity index 93% rename from scripts/verify-asset-catalog.mjs rename to scripts/verify/apple/asset-catalog.mjs index 84087f371..8a62396f1 100644 --- a/scripts/verify-asset-catalog.mjs +++ b/scripts/verify/apple/asset-catalog.mjs @@ -10,7 +10,7 @@ import { artifactCli, artifactMcpResult, withArtifactMcp, -} from "./lib/artifact-e2e.mjs"; +} from "../../lib/artifact-e2e.mjs"; if (process.platform !== "darwin") throw new Error( @@ -30,7 +30,7 @@ try { "clang", fileURLToPath( new URL( - "../tests/conformance/interface-builder/fixture.c", + "../../../tests/conformance/interface-builder/fixture.c", import.meta.url, ), ), @@ -44,7 +44,10 @@ try { await exec("/usr/bin/xcrun", [ "actool", fileURLToPath( - new URL("../tests/conformance/native/Assets.xcassets", import.meta.url), + new URL( + "../../../tests/conformance/native/Assets.xcassets", + import.meta.url, + ), ), "--compile", resources, diff --git a/scripts/verify-apple-dispatch.mjs b/scripts/verify/apple/dispatch.mjs similarity index 96% rename from scripts/verify-apple-dispatch.mjs rename to scripts/verify/apple/dispatch.mjs index 25e80c279..3e108c10e 100644 --- a/scripts/verify-apple-dispatch.mjs +++ b/scripts/verify/apple/dispatch.mjs @@ -4,8 +4,8 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { fileURLToPath } from "node:url"; import { promisify } from "node:util"; -import { parseBinaryTarget } from "../dist/application/BinaryTargetResolver.js"; -import { inspectAppleDispatchMetadata } from "../dist/native/AppleDispatchMetadata.js"; +import { parseBinaryTarget } from "../../../dist/application/BinaryTargetResolver.js"; +import { inspectAppleDispatchMetadata } from "../../../dist/native/AppleDispatchMetadata.js"; if (process.platform !== "darwin") throw new Error( @@ -14,7 +14,7 @@ if (process.platform !== "darwin") const exec = promisify(execFile); const source = (name) => fileURLToPath( - new URL(`../tests/conformance/native/${name}`, import.meta.url), + new URL(`../../../tests/conformance/native/${name}`, import.meta.url), ); /** Both link modes: legacy LC_DYLD_INFO binds and the default chained fixups. */ diff --git a/scripts/verify-interface-builder.mjs b/scripts/verify/apple/interface-builder.mjs similarity index 93% rename from scripts/verify-interface-builder.mjs rename to scripts/verify/apple/interface-builder.mjs index 350411dd0..1f8ec323e 100644 --- a/scripts/verify-interface-builder.mjs +++ b/scripts/verify/apple/interface-builder.mjs @@ -8,8 +8,8 @@ import { join } from "node:path"; import { fileURLToPath } from "node:url"; import { promisify } from "node:util"; -import { parseBinaryTarget } from "../dist/application/BinaryTargetResolver.js"; -import { ArtifactProvider } from "../dist/artifacts/ArtifactProvider.js"; +import { parseBinaryTarget } from "../../../dist/application/BinaryTargetResolver.js"; +import { ArtifactProvider } from "../../../dist/artifacts/ArtifactProvider.js"; const exec = promisify(execFile); const fixtureRoot = await mkdtemp(join(tmpdir(), "rea-interface-builder-")); @@ -18,7 +18,7 @@ const resourcesPath = join(appPath, "Contents", "Resources"); const executablePath = join(appPath, "Contents", "MacOS", "Fixture"); const nibPath = join(resourcesPath, "Main.nib"); const sourceRoot = fileURLToPath( - new URL("../tests/conformance/interface-builder/", import.meta.url), + new URL("../../../tests/conformance/interface-builder/", import.meta.url), ); try { diff --git a/scripts/verify-keyed-archive.mjs b/scripts/verify/apple/keyed-archive.mjs similarity index 94% rename from scripts/verify-keyed-archive.mjs rename to scripts/verify/apple/keyed-archive.mjs index b9f9aa480..924c3bb0e 100644 --- a/scripts/verify-keyed-archive.mjs +++ b/scripts/verify/apple/keyed-archive.mjs @@ -10,7 +10,7 @@ import { artifactCli, artifactMcpResult, withArtifactMcp, -} from "./lib/artifact-e2e.mjs"; +} from "../../lib/artifact-e2e.mjs"; if (process.platform !== "darwin") throw new Error( @@ -25,7 +25,7 @@ try { join(root, "modules"), fileURLToPath( new URL( - "../tests/conformance/native/keyed-archive.swift", + "../../../tests/conformance/native/keyed-archive.swift", import.meta.url, ), ), @@ -66,7 +66,7 @@ try { const expected = JSON.parse( await readFile( new URL( - "../tests/fixtures/golden/keyed-archive/graph.json", + "../../../tests/fixtures/golden/keyed-archive/graph.json", import.meta.url, ), "utf8", @@ -124,7 +124,7 @@ try { join(root, "modules"), fileURLToPath( new URL( - "../tests/conformance/native/keyed-archive-uid-root.swift", + "../../../tests/conformance/native/keyed-archive-uid-root.swift", import.meta.url, ), ), diff --git a/scripts/verify-macos-bundle.mjs b/scripts/verify/apple/macos-bundle.mjs similarity index 98% rename from scripts/verify-macos-bundle.mjs rename to scripts/verify/apple/macos-bundle.mjs index 58fbffd27..bc4522b73 100644 --- a/scripts/verify-macos-bundle.mjs +++ b/scripts/verify/apple/macos-bundle.mjs @@ -9,11 +9,11 @@ import { artifactCliEvidence, artifactMcpResult, withArtifactMcp, -} from "./lib/artifact-e2e.mjs"; +} from "../../lib/artifact-e2e.mjs"; import { buildMacosBundleFixture, preflightMacosBundleFixture, -} from "./lib/macos-bundle-fixture.mjs"; +} from "../../fixtures/apple/macos-bundle.mjs"; const exec = promisify(execFile); diff --git a/src/application/NativeUiActionTrace.ts b/src/application/NativeUiActionTrace.ts index f94051a12..25e9b9dc3 100644 --- a/src/application/NativeUiActionTrace.ts +++ b/src/application/NativeUiActionTrace.ts @@ -3,7 +3,7 @@ import { z } from "zod"; import type { AnalysisOperationPort } from "./AnalysisProvider.js"; import type { EnhancedResult } from "./EnhancedToolTypes.js"; import type { enhancedInputSchemas } from "../contracts/enhancedInputs.js"; -import { interfaceBuilderAnalysisSchema } from "../domain/interfaceBuilderGraph.js"; +import { interfaceBuilderAnalysisSchema } from "../domain/apple/interfaceBuilderGraph.js"; import { nativeInvestigationGraphSchema, nativeInvestigationTraceSchema, diff --git a/src/application/AppleApplicationService.ts b/src/application/apple/AppleApplicationService.ts similarity index 66% rename from src/application/AppleApplicationService.ts rename to src/application/apple/AppleApplicationService.ts index 75c24540c..a30cb654c 100644 --- a/src/application/AppleApplicationService.ts +++ b/src/application/apple/AppleApplicationService.ts @@ -1,12 +1,12 @@ -import type { AnalysisError } from "../domain/analysisErrorBase.js"; -import type { Evidence } from "../domain/evidence.js"; +import type { AnalysisError } from "../../domain/analysisErrorBase.js"; +import type { Evidence } from "../../domain/evidence.js"; import { appleApplicationProjectionInputSchema, projectAppleApplication, -} from "../domain/appleApplication.js"; -import type { Result } from "../domain/result.js"; -import { APPLE_APPLICATION_PROVIDER } from "./InvestigationProviders.js"; -import { projectInventoryEvidence } from "./InventoryProjectionEvidence.js"; +} from "../../domain/apple/appleApplication.js"; +import type { Result } from "../../domain/result.js"; +import { APPLE_APPLICATION_PROVIDER } from "../InvestigationProviders.js"; +import { projectInventoryEvidence } from "../InventoryProjectionEvidence.js"; const OPERATION = "project_apple_application_graph" as const; diff --git a/src/artifacts/ArtifactProvider.ts b/src/artifacts/ArtifactProvider.ts index bc0dced1b..54d9d3826 100644 --- a/src/artifacts/ArtifactProvider.ts +++ b/src/artifacts/ArtifactProvider.ts @@ -7,12 +7,12 @@ import { type ProviderIdentity, type ExecutionOptions, } from "../application/AnalysisProvider.js"; -import { inspectBundleKeyedArchive } from "./KeyedArchiveReader.js"; +import { inspectBundleKeyedArchive } from "./apple/KeyedArchiveReader.js"; import { basename, dirname } from "node:path"; import { inventoryArtifact } from "../application/ArtifactInventory.js"; import { extractArtifact } from "../application/ArtifactExtraction.js"; -import { analyzeInterfaceBuilderBundle } from "../application/InterfaceBuilderAnalysis.js"; -import { analyzeAppleAssetCatalogs } from "../application/AppleAssetCatalogAnalysis.js"; +import { analyzeInterfaceBuilderBundle } from "./apple/InterfaceBuilderAnalysis.js"; +import { analyzeAppleAssetCatalogs } from "./apple/AppleAssetCatalogAnalysis.js"; import { ARTIFACT_ANALYSIS_OPERATIONS, artifactInventoryInputSchema, @@ -24,7 +24,7 @@ import { AnalysisCapabilityUnavailableError } from "../domain/analysisErrorCore. import { ArtifactOperationError } from "../domain/artifactOperationError.js"; import { type AnalysisError } from "../domain/analysisErrorBase.js"; import type { JsonValue } from "../domain/jsonValue.js"; -import { interfaceBuilderLimitsSchema } from "../domain/interfaceBuilderGraph.js"; +import { interfaceBuilderLimitsSchema } from "../domain/apple/interfaceBuilderGraph.js"; import { err, ok } from "../domain/result.js"; import { ArtifactReaderFailure } from "./ArtifactReader.js"; import { diff --git a/src/application/AppleAssetCatalogAnalysis.test.ts b/src/artifacts/apple/AppleAssetCatalogAnalysis.test.ts similarity index 100% rename from src/application/AppleAssetCatalogAnalysis.test.ts rename to src/artifacts/apple/AppleAssetCatalogAnalysis.test.ts diff --git a/src/application/AppleAssetCatalogAnalysis.ts b/src/artifacts/apple/AppleAssetCatalogAnalysis.ts similarity index 95% rename from src/application/AppleAssetCatalogAnalysis.ts rename to src/artifacts/apple/AppleAssetCatalogAnalysis.ts index 8ba8b04c6..36b88eb13 100644 --- a/src/application/AppleAssetCatalogAnalysis.ts +++ b/src/artifacts/apple/AppleAssetCatalogAnalysis.ts @@ -1,14 +1,14 @@ import { createHash } from "node:crypto"; import { lstat } from "node:fs/promises"; -import { DirectoryArtifactReader } from "../artifacts/DirectoryArtifactReader.js"; -import { ArtifactReaderFailure } from "../artifacts/ArtifactReader.js"; +import { DirectoryArtifactReader } from "../DirectoryArtifactReader.js"; +import { ArtifactReaderFailure } from "../ArtifactReader.js"; import { analyzeInterfaceBuilderBundle } from "./InterfaceBuilderAnalysis.js"; import { appleAssetCatalogInputSchema, parseAppleAssetCatalogRecords, projectAppleAssetCatalogPage, -} from "../domain/appleAssetCatalog.js"; -import { execFileOutput } from "../process/ExecFileOutput.js"; +} from "../../domain/apple/appleAssetCatalog.js"; +import { execFileOutput } from "../../process/ExecFileOutput.js"; const ASSETUTIL = "/usr/bin/assetutil"; const MAX_CATALOG_BYTES = 64 * 1024 * 1024; @@ -105,7 +105,7 @@ export const analyzeAppleAssetCatalogs = async (input: { const inspectAssetCatalogEntry = async (input: { readonly reader: DirectoryArtifactReader; - readonly entry: import("../artifacts/ArtifactReader.js").ArtifactEntry; + readonly entry: import("../ArtifactReader.js").ArtifactEntry; readonly runAssetUtil: ( path: string, signal?: AbortSignal, @@ -237,7 +237,7 @@ const runAssetUtil = async ( const digestBounded = async ( reader: DirectoryArtifactReader, - entry: import("../artifacts/ArtifactReader.js").ArtifactEntry, + entry: import("../ArtifactReader.js").ArtifactEntry, signal?: AbortSignal, ): Promise => { if (entry.declaredSize === null || entry.declaredSize > MAX_CATALOG_BYTES) diff --git a/src/application/InterfaceBuilderAnalysis.ts b/src/artifacts/apple/InterfaceBuilderAnalysis.ts similarity index 96% rename from src/application/InterfaceBuilderAnalysis.ts rename to src/artifacts/apple/InterfaceBuilderAnalysis.ts index 3dc03463f..6966bbe9c 100644 --- a/src/application/InterfaceBuilderAnalysis.ts +++ b/src/artifacts/apple/InterfaceBuilderAnalysis.ts @@ -1,28 +1,25 @@ import { createHash } from "node:crypto"; import { parseBinary } from "plist"; -import { DirectoryArtifactReader } from "../artifacts/DirectoryArtifactReader.js"; -import { projectPlistValue } from "../domain/plistValue.js"; +import { DirectoryArtifactReader } from "../DirectoryArtifactReader.js"; +import { projectPlistValue } from "../../domain/apple/plistValue.js"; import { omittedPrototypeKeysLimitation, parseXmlPropertyList, -} from "../domain/propertyListKeys.js"; -import { - decodeNibArchive, - type NibArchiveDocument, -} from "../artifacts/NibArchive.js"; -import type { ArtifactEntry } from "../artifacts/ArtifactReader.js"; +} from "../../domain/propertyListKeys.js"; +import { decodeNibArchive, type NibArchiveDocument } from "./NibArchive.js"; +import type { ArtifactEntry } from "../ArtifactReader.js"; import { buildInterfaceBuilderAnalysis, interfaceBuilderLimitsSchema, type InterfaceBuilderDocumentInput, -} from "../domain/interfaceBuilderGraph.js"; +} from "../../domain/apple/interfaceBuilderGraph.js"; import { mergeNibHierarchies, projectNibViewHierarchy, type NibHierarchyNode, } from "./NibViewHierarchy.js"; -import { jsonValueSchema, type JsonValue } from "../domain/jsonValue.js"; +import { jsonValueSchema, type JsonValue } from "../../domain/jsonValue.js"; const MAX_DOCUMENT_BYTES = 64 * 1024 * 1024; diff --git a/src/artifacts/KeyedArchiveReader.test.ts b/src/artifacts/apple/KeyedArchiveReader.test.ts similarity index 100% rename from src/artifacts/KeyedArchiveReader.test.ts rename to src/artifacts/apple/KeyedArchiveReader.test.ts diff --git a/src/artifacts/KeyedArchiveReader.ts b/src/artifacts/apple/KeyedArchiveReader.ts similarity index 92% rename from src/artifacts/KeyedArchiveReader.ts rename to src/artifacts/apple/KeyedArchiveReader.ts index b96253d5d..e94861997 100644 --- a/src/artifacts/KeyedArchiveReader.ts +++ b/src/artifacts/apple/KeyedArchiveReader.ts @@ -2,19 +2,19 @@ import { createHash } from "node:crypto"; import { TextDecoder } from "node:util"; import { parseBinary } from "plist"; import { z } from "zod"; -import type { JsonValue } from "../domain/jsonValue.js"; -import { projectPlistValue } from "../domain/plistValue.js"; +import type { JsonValue } from "../../domain/jsonValue.js"; +import { projectPlistValue } from "../../domain/apple/plistValue.js"; import { omittedPrototypeKeysLimitation, parseXmlPropertyList, -} from "../domain/propertyListKeys.js"; +} from "../../domain/propertyListKeys.js"; import { keyedArchiveInputSchema, keyedArchiveResultSchema, projectKeyedArchive, -} from "../domain/keyedArchive.js"; -import { DirectoryArtifactReader } from "./DirectoryArtifactReader.js"; -import { ArtifactReaderFailure } from "./ArtifactReader.js"; +} from "../../domain/apple/keyedArchive.js"; +import { DirectoryArtifactReader } from "../DirectoryArtifactReader.js"; +import { ArtifactReaderFailure } from "../ArtifactReader.js"; const MAX_BYTES = 64 * 1024 * 1024; diff --git a/src/artifacts/NibArchive.fixture.ts b/src/artifacts/apple/NibArchive.fixture.ts similarity index 100% rename from src/artifacts/NibArchive.fixture.ts rename to src/artifacts/apple/NibArchive.fixture.ts diff --git a/src/artifacts/NibArchive.test.ts b/src/artifacts/apple/NibArchive.test.ts similarity index 100% rename from src/artifacts/NibArchive.test.ts rename to src/artifacts/apple/NibArchive.test.ts diff --git a/src/artifacts/NibArchive.ts b/src/artifacts/apple/NibArchive.ts similarity index 99% rename from src/artifacts/NibArchive.ts rename to src/artifacts/apple/NibArchive.ts index 23d991bed..c383922d1 100644 --- a/src/artifacts/NibArchive.ts +++ b/src/artifacts/apple/NibArchive.ts @@ -1,4 +1,4 @@ -import type { JsonValue } from "../domain/jsonValue.js"; +import type { JsonValue } from "../../domain/jsonValue.js"; import { TextDecoder } from "node:util"; const MAGIC = Buffer.from("NIBArchive", "ascii"); diff --git a/src/application/NibViewHierarchy.test.ts b/src/artifacts/apple/NibViewHierarchy.test.ts similarity index 100% rename from src/application/NibViewHierarchy.test.ts rename to src/artifacts/apple/NibViewHierarchy.test.ts diff --git a/src/application/NibViewHierarchy.ts b/src/artifacts/apple/NibViewHierarchy.ts similarity index 97% rename from src/application/NibViewHierarchy.ts rename to src/artifacts/apple/NibViewHierarchy.ts index 231f456b6..61c669e53 100644 --- a/src/application/NibViewHierarchy.ts +++ b/src/artifacts/apple/NibViewHierarchy.ts @@ -1,5 +1,5 @@ -import type { NibArchiveDocument } from "../artifacts/NibArchive.js"; -import type { JsonValue } from "../domain/jsonValue.js"; +import type { NibArchiveDocument } from "./NibArchive.js"; +import type { JsonValue } from "../../domain/jsonValue.js"; export type NibHierarchyNode = { objectID: string; diff --git a/src/cli/applicationCommands.ts b/src/cli/applicationCommands.ts index 967ecbf6c..f4a6fb33b 100644 --- a/src/cli/applicationCommands.ts +++ b/src/cli/applicationCommands.ts @@ -2,7 +2,7 @@ import { reconstructionCoverageEvaluationInputSchema } from "../domain/reconstru import { Cli, z } from "incur"; import { projectAndroidApplicationEvidence } from "../application/android/AndroidApplicationService.js"; -import { projectAppleApplicationEvidence } from "../application/AppleApplicationService.js"; +import { projectAppleApplicationEvidence } from "../application/apple/AppleApplicationService.js"; import { compareApplicationVersionsEvidenceValidated, compareJavaScriptExportShapesEvidenceValidated, @@ -19,7 +19,7 @@ import { AnalysisInputError } from "../domain/analysisErrorCore.js"; import { projectAnalysisError } from "../domain/analysisErrorProjection.js"; import { type AnalysisError } from "../domain/analysisErrorBase.js"; import { androidApplicationProjectionInputSchema } from "../domain/android/androidApplication.js"; -import { appleApplicationProjectionInputSchema } from "../domain/appleApplication.js"; +import { appleApplicationProjectionInputSchema } from "../domain/apple/appleApplication.js"; import { jsonValueSchema, type JsonValue } from "../domain/jsonValue.js"; import type { Logger } from "../logger.js"; import { traceApplicationFeatureInputSchema } from "../domain/javascript/javascriptFeatureTraceSchemas.js"; diff --git a/src/contracts/applicationToolContracts.ts b/src/contracts/applicationToolContracts.ts index 763e6632a..6ef806c36 100644 --- a/src/contracts/applicationToolContracts.ts +++ b/src/contracts/applicationToolContracts.ts @@ -23,7 +23,7 @@ import { import { appleApplicationProjectionInputSchema, appleApplicationProjectionResultSchema, -} from "../domain/appleApplication.js"; +} from "../domain/apple/appleApplication.js"; import type { ToolContract } from "./toolContracts.js"; import { toolContractMetadata } from "./toolEffects.js"; import { evidenceResultOf } from "./toolOutputSchemas.js"; diff --git a/src/contracts/artifactToolContracts.ts b/src/contracts/artifactToolContracts.ts index bd529d8ba..85fb8936b 100644 --- a/src/contracts/artifactToolContracts.ts +++ b/src/contracts/artifactToolContracts.ts @@ -5,8 +5,8 @@ import { artifactOutputSchemas } from "./toolOutputSchemas.js"; import { jsonValueSchema } from "../domain/jsonValue.js"; import { toolContractMetadata } from "./toolEffects.js"; import { requireOutputSchema } from "./toolOutputSchemaPrimitives.js"; -import { appleAssetCatalogInputSchema } from "../domain/appleAssetCatalog.js"; -import { keyedArchiveInputSchema } from "../domain/keyedArchive.js"; +import { appleAssetCatalogInputSchema } from "../domain/apple/appleAssetCatalog.js"; +import { keyedArchiveInputSchema } from "../domain/apple/keyedArchive.js"; /** Exact caller boundary for deterministic artifact inventory. */ export const artifactInventoryInputSchema = z.strictObject({ integrity_policy: z.enum(["fail", "record-and-continue"]).default("fail"), diff --git a/src/contracts/toolOutputSchemaGroups.ts b/src/contracts/toolOutputSchemaGroups.ts index c4ef6b770..eb0fb000e 100644 --- a/src/contracts/toolOutputSchemaGroups.ts +++ b/src/contracts/toolOutputSchemaGroups.ts @@ -30,9 +30,9 @@ import { } from "../domain/nativeInspection.js"; import { artifactExtractionResultSchema } from "../domain/artifactGraph.js"; import { artifactInspectionResultSchema } from "../domain/artifactInspection.js"; -import { interfaceBuilderAnalysisSchema } from "../domain/interfaceBuilderGraph.js"; -import { keyedArchiveResultSchema } from "../domain/keyedArchive.js"; -import { appleAssetCatalogResultSchema } from "../domain/appleAssetCatalog.js"; +import { interfaceBuilderAnalysisSchema } from "../domain/apple/interfaceBuilderGraph.js"; +import { keyedArchiveResultSchema } from "../domain/apple/keyedArchive.js"; +import { appleAssetCatalogResultSchema } from "../domain/apple/appleAssetCatalog.js"; import { managedArtifactInspectionSchema, managedMemberInspectionSchema, diff --git a/src/domain/appleApplication.test.ts b/src/domain/apple/appleApplication.test.ts similarity index 98% rename from src/domain/appleApplication.test.ts rename to src/domain/apple/appleApplication.test.ts index 47c45a746..ea5419dc9 100644 --- a/src/domain/appleApplication.test.ts +++ b/src/domain/apple/appleApplication.test.ts @@ -3,9 +3,9 @@ import { createHash } from "node:crypto"; import { describe, expect, it } from "vitest"; import { projectAppleApplication } from "./appleApplication.js"; -import { canonicalDigest } from "./comparisonSemantics.js"; -import { createEvidence, type Evidence } from "./evidence.js"; -import { jsonValueSchema } from "./jsonValue.js"; +import { canonicalDigest } from "../comparisonSemantics.js"; +import { createEvidence, type Evidence } from "../evidence.js"; +import { jsonValueSchema } from "../jsonValue.js"; type EntryKind = "file" | "directory" | "symlink"; type RootFormat = "ipa" | "zip" | "dmg" | "directory" | "asar"; diff --git a/src/domain/appleApplication.ts b/src/domain/apple/appleApplication.ts similarity index 98% rename from src/domain/appleApplication.ts rename to src/domain/apple/appleApplication.ts index 42bc970b6..bb88a8c31 100644 --- a/src/domain/appleApplication.ts +++ b/src/domain/apple/appleApplication.ts @@ -14,10 +14,10 @@ import { platformsOf, type AppleInventoryEntry, } from "./appleBundleAnatomy.js"; -import { parseArtifactInventoryEvidence } from "./artifactInventoryEvidence.js"; -import { evidenceSchema } from "./evidence.js"; -import { digestSchema } from "./../domain/digests.js"; -import { prefixedDigestSchema } from "./../domain/digests.js"; +import { parseArtifactInventoryEvidence } from "../artifactInventoryEvidence.js"; +import { evidenceSchema } from "../evidence.js"; +import { digestSchema } from "../digests.js"; +import { prefixedDigestSchema } from "../digests.js"; const evidenceIdSchema = prefixedDigestSchema("ev"); const pathSchema = z.string().min(1); diff --git a/src/domain/appleAssetCatalog.test.ts b/src/domain/apple/appleAssetCatalog.test.ts similarity index 100% rename from src/domain/appleAssetCatalog.test.ts rename to src/domain/apple/appleAssetCatalog.test.ts diff --git a/src/domain/appleAssetCatalog.ts b/src/domain/apple/appleAssetCatalog.ts similarity index 98% rename from src/domain/appleAssetCatalog.ts rename to src/domain/apple/appleAssetCatalog.ts index 12439fce0..2954ebda8 100644 --- a/src/domain/appleAssetCatalog.ts +++ b/src/domain/apple/appleAssetCatalog.ts @@ -1,7 +1,7 @@ import { z } from "zod"; -import { jsonValueSchema } from "./jsonValue.js"; -import { AnalysisOutputError } from "./analysisErrorCore.js"; +import { jsonValueSchema } from "../jsonValue.js"; +import { AnalysisOutputError } from "../analysisErrorCore.js"; /** Caller-controlled page bounds for compiled Apple asset metadata. */ export const appleAssetCatalogInputSchema = z.strictObject({ diff --git a/src/domain/appleBundleAnatomy.ts b/src/domain/apple/appleBundleAnatomy.ts similarity index 99% rename from src/domain/appleBundleAnatomy.ts rename to src/domain/apple/appleBundleAnatomy.ts index f98def7c6..224c5ff49 100644 --- a/src/domain/appleBundleAnatomy.ts +++ b/src/domain/apple/appleBundleAnatomy.ts @@ -1,6 +1,6 @@ import { z } from "zod"; -import { digestSchema, prefixedDigestSchema } from "./digests.js"; +import { digestSchema, prefixedDigestSchema } from "../digests.js"; const pathSchema = z.string().min(1); diff --git a/src/domain/interfaceBuilderGraph.test.ts b/src/domain/apple/interfaceBuilderGraph.test.ts similarity index 100% rename from src/domain/interfaceBuilderGraph.test.ts rename to src/domain/apple/interfaceBuilderGraph.test.ts diff --git a/src/domain/interfaceBuilderGraph.ts b/src/domain/apple/interfaceBuilderGraph.ts similarity index 99% rename from src/domain/interfaceBuilderGraph.ts rename to src/domain/apple/interfaceBuilderGraph.ts index 2e882ab26..22d8a544b 100644 --- a/src/domain/interfaceBuilderGraph.ts +++ b/src/domain/apple/interfaceBuilderGraph.ts @@ -10,7 +10,7 @@ import { nativeInvestigationGraphSchema, nativeInvestigationNodeSchema, nativeInvestigationTraceLimitsSchema, -} from "./nativeInvestigationGraph.js"; +} from "../nativeInvestigationGraph.js"; export { parseInterfaceBuilderRecords } from "./interfaceBuilderKeyedArchive.js"; diff --git a/src/domain/interfaceBuilderKeyedArchive.ts b/src/domain/apple/interfaceBuilderKeyedArchive.ts similarity index 99% rename from src/domain/interfaceBuilderKeyedArchive.ts rename to src/domain/apple/interfaceBuilderKeyedArchive.ts index 602392c22..d1ee5c3ea 100644 --- a/src/domain/interfaceBuilderKeyedArchive.ts +++ b/src/domain/apple/interfaceBuilderKeyedArchive.ts @@ -1,6 +1,6 @@ import { z } from "zod"; -import { jsonValueSchema } from "./jsonValue.js"; +import { jsonValueSchema } from "../jsonValue.js"; const recordSchema = z.record(z.string(), z.unknown()); diff --git a/src/domain/keyedArchive.ts b/src/domain/apple/keyedArchive.ts similarity index 99% rename from src/domain/keyedArchive.ts rename to src/domain/apple/keyedArchive.ts index 32e855f18..2fc6a792f 100644 --- a/src/domain/keyedArchive.ts +++ b/src/domain/apple/keyedArchive.ts @@ -1,5 +1,5 @@ import { z } from "zod"; -import { jsonValueSchema, type JsonValue } from "./jsonValue.js"; +import { jsonValueSchema, type JsonValue } from "../jsonValue.js"; /** Select one archive within the active bundle and optionally one named root. */ export const keyedArchiveInputSchema = z.strictObject({ diff --git a/src/domain/plistValue.test.ts b/src/domain/apple/plistValue.test.ts similarity index 100% rename from src/domain/plistValue.test.ts rename to src/domain/apple/plistValue.test.ts diff --git a/src/domain/plistValue.ts b/src/domain/apple/plistValue.ts similarity index 97% rename from src/domain/plistValue.ts rename to src/domain/apple/plistValue.ts index 6c427a8ad..dc6e4d0ea 100644 --- a/src/domain/plistValue.ts +++ b/src/domain/apple/plistValue.ts @@ -1,6 +1,6 @@ import { z } from "zod"; -import type { JsonValue } from "./jsonValue.js"; +import type { JsonValue } from "../jsonValue.js"; const MAX_PLIST_DEPTH = 128; diff --git a/src/domain/nativeInvestigationGraph.test.ts b/src/domain/nativeInvestigationGraph.test.ts index f11aef020..160d29660 100644 --- a/src/domain/nativeInvestigationGraph.test.ts +++ b/src/domain/nativeInvestigationGraph.test.ts @@ -8,7 +8,7 @@ import { import { buildInterfaceBuilderAnalysis, interfaceBuilderLimitsSchema, -} from "./interfaceBuilderGraph.js"; +} from "./apple/interfaceBuilderGraph.js"; const evidence = [ { diff --git a/src/native/parsers/plist.ts b/src/native/parsers/plist.ts index 977e7909a..b27f67b1e 100644 --- a/src/native/parsers/plist.ts +++ b/src/native/parsers/plist.ts @@ -5,7 +5,7 @@ import type { JsonValue } from "../../domain/jsonValue.js"; import { projectPlistValue, type ProjectedPlistValue, -} from "../../domain/plistValue.js"; +} from "../../domain/apple/plistValue.js"; import { omitPrototypeKeys, omittedPrototypeKeysLimitation, diff --git a/src/server/registerApplicationTools/projectMobileApplicationGraph.ts b/src/server/registerApplicationTools/projectMobileApplicationGraph.ts index 573eee331..cac99a638 100644 --- a/src/server/registerApplicationTools/projectMobileApplicationGraph.ts +++ b/src/server/registerApplicationTools/projectMobileApplicationGraph.ts @@ -1,7 +1,7 @@ import type { McpServer } from "@modelcontextprotocol/server"; import { projectAndroidApplicationEvidence } from "../../application/android/AndroidApplicationService.js"; -import { projectAppleApplicationEvidence } from "../../application/AppleApplicationService.js"; +import { projectAppleApplicationEvidence } from "../../application/apple/AppleApplicationService.js"; import { APPLICATION_TOOL_CONTRACTS, applicationToolContract, diff --git a/tests/boundary/filesystem/interfaceBuilderBundle.test.ts b/tests/boundary/filesystem/interfaceBuilderBundle.test.ts index df2173003..a66984272 100644 --- a/tests/boundary/filesystem/interfaceBuilderBundle.test.ts +++ b/tests/boundary/filesystem/interfaceBuilderBundle.test.ts @@ -7,8 +7,8 @@ import { promisify } from "node:util"; import { buildBinary } from "plist"; import { describe, expect, it } from "vitest"; -import { analyzeInterfaceBuilderBundle } from "../../../src/application/InterfaceBuilderAnalysis.js"; -import { encodeNibArchiveFixture } from "../../../src/artifacts/NibArchive.fixture.js"; +import { analyzeInterfaceBuilderBundle } from "../../../src/artifacts/apple/InterfaceBuilderAnalysis.js"; +import { encodeNibArchiveFixture } from "../../../src/artifacts/apple/NibArchive.fixture.js"; import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; const compile = promisify(execFile); diff --git a/tests/boundary/filesystem/nativeArtifactGoldens.test.ts b/tests/boundary/filesystem/nativeArtifactGoldens.test.ts index 30ea71470..7387b2aeb 100644 --- a/tests/boundary/filesystem/nativeArtifactGoldens.test.ts +++ b/tests/boundary/filesystem/nativeArtifactGoldens.test.ts @@ -1,10 +1,10 @@ import { readFile } from "node:fs/promises"; import { describe, expect, it } from "vitest"; -import { decodeKeyedArchiveBytes } from "../../../src/artifacts/KeyedArchiveReader.js"; +import { decodeKeyedArchiveBytes } from "../../../src/artifacts/apple/KeyedArchiveReader.js"; import { parseAppleAssetCatalogRecords, projectAppleAssetCatalogPage, -} from "../../../src/domain/appleAssetCatalog.js"; +} from "../../../src/domain/apple/appleAssetCatalog.js"; const golden = (path: string) => new URL(`../../fixtures/golden/${path}`, import.meta.url); diff --git a/tests/boundary/filesystem/nibNativeHierarchy.test.ts b/tests/boundary/filesystem/nibNativeHierarchy.test.ts index 83b025b79..360b7e190 100644 --- a/tests/boundary/filesystem/nibNativeHierarchy.test.ts +++ b/tests/boundary/filesystem/nibNativeHierarchy.test.ts @@ -6,8 +6,8 @@ import { promisify } from "node:util"; import { describe, expect, it } from "vitest"; import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; -import { decodeNibArchive } from "../../../src/artifacts/NibArchive.js"; -import { analyzeInterfaceBuilderBundle } from "../../../src/application/InterfaceBuilderAnalysis.js"; +import { decodeNibArchive } from "../../../src/artifacts/apple/NibArchive.js"; +import { analyzeInterfaceBuilderBundle } from "../../../src/artifacts/apple/InterfaceBuilderAnalysis.js"; const compile = promisify(execFile); diff --git a/tests/boundary/mcp/artifactMcp.inlineInventory.test.ts b/tests/boundary/mcp/artifactMcp.inlineInventory.test.ts index b879b7bd9..b099a3825 100644 --- a/tests/boundary/mcp/artifactMcp.inlineInventory.test.ts +++ b/tests/boundary/mcp/artifactMcp.inlineInventory.test.ts @@ -7,7 +7,7 @@ import { expect, it } from "vitest"; import { z } from "zod"; import { parseConfig } from "../../../src/config.js"; import { createBinarySession } from "../../../src/application/runtime.js"; -import { keyedArchiveResultSchema } from "../../../src/domain/keyedArchive.js"; +import { keyedArchiveResultSchema } from "../../../src/domain/apple/keyedArchive.js"; import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; import { createTestBinarySession } from "../../fixtures/binarySession.js"; diff --git a/tests/boundary/providers/native/keyedArchiveEncoding.test.ts b/tests/boundary/providers/native/keyedArchiveEncoding.test.ts index 31db384c1..c32a6e037 100644 --- a/tests/boundary/providers/native/keyedArchiveEncoding.test.ts +++ b/tests/boundary/providers/native/keyedArchiveEncoding.test.ts @@ -5,7 +5,7 @@ import { join } from "node:path"; import { buildBinary, parse } from "plist"; import { expect, it } from "vitest"; -import { inspectBundleKeyedArchive } from "../../../../src/artifacts/KeyedArchiveReader.js"; +import { inspectBundleKeyedArchive } from "../../../../src/artifacts/apple/KeyedArchiveReader.js"; import { nativeFixture } from "../../../fixtures/nativeCommands.js"; import { createTestTempDirectory } from "../../../fixtures/temporaryDirectory.js"; diff --git a/tests/composition/analysis-sessions/appleApplication.test.ts b/tests/composition/analysis-sessions/appleApplication.test.ts index 3f38ec9ae..8f870ad47 100644 --- a/tests/composition/analysis-sessions/appleApplication.test.ts +++ b/tests/composition/analysis-sessions/appleApplication.test.ts @@ -16,9 +16,9 @@ import { requireSuccessfulProjection, } from "../../support/applicationSessionFixture.js"; -import { projectAppleApplicationEvidence } from "../../../src/application/AppleApplicationService.js"; +import { projectAppleApplicationEvidence } from "../../../src/application/apple/AppleApplicationService.js"; import { runProviderAnalysis } from "../../../src/application/DirectAnalysis.js"; -import { appleApplicationProjectionResultSchema } from "../../../src/domain/appleApplication.js"; +import { appleApplicationProjectionResultSchema } from "../../../src/domain/apple/appleApplication.js"; import { parseEvidence } from "../../../src/domain/evidence.js"; // Real ZIP entries cross the former component and bridge-candidate limits. diff --git a/tests/evaluation/moduleBoundaries.test.ts b/tests/evaluation/moduleBoundaries.test.ts index b9b1146d8..4b808f331 100644 --- a/tests/evaluation/moduleBoundaries.test.ts +++ b/tests/evaluation/moduleBoundaries.test.ts @@ -272,3 +272,22 @@ describe("test lane import boundaries", () => { if (!allowed) expect(result.stdout).toContain("no-restricted-imports"); }); }); + +describe("Apple artifact producer ownership", () => { + it.each([ + ["../../application/Workflow.js", false], + ["../../composition/android.js", false], + ["../../cli.js", false], + ["../../domain/apple/plistValue.js", true], + ["../DirectoryArtifactReader.js", true], + ])("checks the producer dependency %s", (dependency, allowed) => { + const violations = inspectModuleBoundaries( + "src/artifacts/apple/probe.ts", + `import type { Value } from ${JSON.stringify(dependency)};`, + process.cwd(), + ); + + expect(violations.length === 0).toBe(allowed); + if (!allowed) expect(violations[0]?.boundary).toBe("artifact-acquisition"); + }); +}); diff --git a/turbo.json b/turbo.json index 086d4db29..62e4f0ade 100644 --- a/turbo.json +++ b/turbo.json @@ -111,6 +111,8 @@ "scripts/lib/*.mjs", "scripts/verify/managed/*.mjs", "scripts/fixtures/managed/*.mjs", + "scripts/verify/apple/*.mjs", + "scripts/fixtures/apple/*.mjs", "src/generated*.ts", "src/contracts/**/*.ts", "docs/**",